Commit 14d0d71c authored by Wei Han's avatar Wei Han

code update

parent 81ec60ad
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; 44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
4EC822DE2B433513F31AFB3E /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 40EB36954DDA100CF0424A89 /* libPods-example.a */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
EEFB98A2480FDDBFC790E56F /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 574BB54B207C088196A9FBBB /* libPods-example.a */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = example/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
574BB54B207C088196A9FBBB /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 31FDB50C78AE137D41CF411D /* 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>"; };
40EB36954DDA100CF0424A89 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; 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>"; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = "<group>"; };
85CE699693EE18A8E1C24E42 /* 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>"; }; 824AEC708BA04F5AD482851A /* 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>"; };
B696EC4DAEE5EDE78A1EEB18 /* 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; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EEFB98A2480FDDBFC790E56F /* libPods-example.a in Frameworks */, 4EC822DE2B433513F31AFB3E /* libPods-example.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */, ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
574BB54B207C088196A9FBBB /* libPods-example.a */, 40EB36954DDA100CF0424A89 /* libPods-example.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -92,8 +92,8 @@ ...@@ -92,8 +92,8 @@
BBD78D7AC51CEA395F1C20DB /* Pods */ = { BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
85CE699693EE18A8E1C24E42 /* Pods-example.debug.xcconfig */, 31FDB50C78AE137D41CF411D /* Pods-example.debug.xcconfig */,
B696EC4DAEE5EDE78A1EEB18 /* Pods-example.release.xcconfig */, 824AEC708BA04F5AD482851A /* Pods-example.release.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -105,13 +105,13 @@ ...@@ -105,13 +105,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */;
buildPhases = ( buildPhases = (
C87E7C1B2ADF35B0CBBA26BF /* [CP] Check Pods Manifest.lock */, B87419CC63186335BB42ACC9 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */, 13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */, 13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
DE4DC932E04B9540533EF987 /* [CP] Embed Pods Frameworks */, 7EA22330F9EEADB32B8DCA0C /* [CP] Embed Pods Frameworks */,
9DEA634DDB892A12B25520B4 /* [CP] Copy Pods Resources */, CF5916958D0840FF3C063407 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
...@@ -183,24 +183,24 @@ ...@@ -183,24 +183,24 @@
shellPath = /bin/sh; 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"; 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";
}; };
9DEA634DDB892A12B25520B4 /* [CP] Copy Pods Resources */ = { 7EA22330F9EEADB32B8DCA0C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Copy Pods Resources"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
C87E7C1B2ADF35B0CBBA26BF /* [CP] Check Pods Manifest.lock */ = { B87419CC63186335BB42ACC9 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
...@@ -222,21 +222,21 @@ ...@@ -222,21 +222,21 @@
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 = "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; showEnvVarsInLog = 0;
}; };
DE4DC932E04B9540533EF987 /* [CP] Embed Pods Frameworks */ = { CF5916958D0840FF3C063407 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
...@@ -255,7 +255,7 @@ ...@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = { 13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 85CE699693EE18A8E1C24E42 /* Pods-example.debug.xcconfig */; baseConfigurationReference = 31FDB50C78AE137D41CF411D /* Pods-example.debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
...@@ -283,7 +283,7 @@ ...@@ -283,7 +283,7 @@
}; };
13B07F951A680F5B00A75B9A /* Release */ = { 13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = B696EC4DAEE5EDE78A1EEB18 /* Pods-example.release.xcconfig */; baseConfigurationReference = 824AEC708BA04F5AD482851A /* Pods-example.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
......
...@@ -8,32 +8,32 @@ ...@@ -8,32 +8,32 @@
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string> <string>ios-arm64</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
<dict> <dict>
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64</string> <string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
......
...@@ -409,6 +409,10 @@ ...@@ -409,6 +409,10 @@
self.repInputsContainer.spacing = 8; self.repInputsContainer.spacing = 8;
self.repInputsContainer.translatesAutoresizingMaskIntoConstraints = NO; self.repInputsContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.repInputsContainer.hidden = YES; // Initially hidden self.repInputsContainer.hidden = YES; // Initially hidden
self.repInputsContainer.clipsToBounds = YES;
self.repInputsHeightConstraint =
[self.repInputsContainer.heightAnchor constraintEqualToConstant:0];
self.repInputsHeightConstraint.active = YES;
[self.contentView addSubview:self.repInputsContainer]; [self.contentView addSubview:self.repInputsContainer];
// --- Constraints --- // --- Constraints ---
...@@ -500,17 +504,26 @@ ...@@ -500,17 +504,26 @@
self.icPhotoView.hidden = NO; self.icPhotoView.hidden = NO;
self.letterPhotoView.hidden = NO; self.letterPhotoView.hidden = NO;
[UIView animateWithDuration:0.3 animations:^{ // Calculate height (approx. safe fixed height)
[self.contentView layoutIfNeeded]; CGFloat expandedHeight = 120 // image row
+ 8*4 // titles + fields spacing
+ 40*4; // estimated textfield heights
self.repInputsHeightConstraint.constant = expandedHeight;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}]; }];
} }
- (void)hideRepresentativeInputs { - (void)hideRepresentativeInputs {
[UIView animateWithDuration:0.3 animations:^{ self.repInputsHeightConstraint.constant = 0;
self.repInputsContainer.hidden = YES;
self.icPhotoView.hidden = YES; self.icPhotoView.hidden = YES;
self.letterPhotoView.hidden = YES; self.letterPhotoView.hidden = YES;
[self.contentView layoutIfNeeded];
[UIView animateWithDuration:0.25 animations:^{
self.repInputsContainer.hidden = YES;
[self.view layoutIfNeeded];
}]; }];
// Optional: clear fields // Optional: clear fields
...@@ -520,7 +533,6 @@ ...@@ -520,7 +533,6 @@
self.repContactField.text = @""; self.repContactField.text = @"";
} }
#pragma mark - helpers #pragma mark - helpers
- (void)cancelPressed:(id)sender { - (void)cancelPressed:(id)sender {
UIAlertController *a = [UIAlertController alertControllerWithTitle:@"Cancel" UIAlertController *a = [UIAlertController alertControllerWithTitle:@"Cancel"
......
// DashboardHeaderView.h
#import <UIKit/UIKit.h>
@interface DashboardHeaderView : UIView
@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *overlayImageView;
@property (nonatomic, strong) UILabel *projectNameLabel;
@property (nonatomic, strong) UILabel *unitNameLabel;
@property (nonatomic, strong) UIButton *infoButton;
- (void)configureWithProjectName:(NSString *)projectName
unitName:(NSString *)unitName
backgroundImage:(UIImage *)backgroundImage
overlayImage:(UIImage *)overlayImage;
@end
//DashboardHeaderView.mm
#import "DashboardHeaderView.h"
@implementation DashboardHeaderView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupUI];
}
return self;
}
- (void)setupUI {
self.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // #006D8D
// Background image
_backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
_backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_backgroundImageView];
// Overlay image
_overlayImageView = [[UIImageView alloc] initWithFrame:self.bounds];
_overlayImageView.contentMode = UIViewContentModeScaleAspectFill;
_overlayImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_overlayImageView.alpha = 0.4;
[self addSubview:_overlayImageView];
// Header container (for back, title, menu)
UIView *headerBar = [[UIView alloc] init];
headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:headerBar];
// Back button part of header now
// Project name (center title)
_projectNameLabel = [[UILabel alloc] init];
_projectNameLabel.textColor = [UIColor whiteColor];
_projectNameLabel.font = [UIFont boldSystemFontOfSize:18];
_projectNameLabel.textAlignment = NSTextAlignmentCenter;
_projectNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[headerBar addSubview:_projectNameLabel];
// Menu button
// Unit name (bottom left)
_unitNameLabel = [[UILabel alloc] init];
_unitNameLabel.textColor = [UIColor whiteColor];
_unitNameLabel.font = [UIFont boldSystemFontOfSize:20];
_unitNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_unitNameLabel];
// Info button (bottom right)
// _infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// _infoButton.translatesAutoresizingMaskIntoConstraints = NO;
// [_infoButton setImage:[UIImage systemImageNamed:@"info.circle"] forState:UIControlStateNormal];
// _infoButton.tintColor = [UIColor whiteColor];
// [self addSubview:_infoButton];
// Layout constraints
[NSLayoutConstraint activateConstraints:@[
// Header bar top
[headerBar.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor constant:0],
[headerBar.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:0],
[headerBar.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:0],
[headerBar.heightAnchor constraintEqualToConstant:44],
// Back button
// Menu button
// Title centered
[_projectNameLabel.centerXAnchor constraintEqualToAnchor:headerBar.centerXAnchor],
[_projectNameLabel.centerYAnchor constraintEqualToAnchor:headerBar.centerYAnchor],
// Unit name bottom-left
[_unitNameLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:16],
[_unitNameLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-16],
// Info button bottom-right
// [_infoButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-16],
// [_infoButton.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-18],
// [_infoButton.widthAnchor constraintEqualToConstant:24],
// [_infoButton.heightAnchor constraintEqualToConstant:24],
]];
}
- (void)configureWithProjectName:(NSString *)projectName
unitName:(NSString *)unitName
backgroundImage:(UIImage *)backgroundImage
overlayImage:(UIImage *)overlayImage {
self.projectNameLabel.text = projectName ?: @"Project Name";
self.unitNameLabel.text = unitName ?: @"Unit Name";
self.backgroundImageView.image = backgroundImage ?: [UIImage imageNamed:@"placeholder_header"];
self.overlayImageView.image = overlayImage ?: [UIImage imageNamed:@"placeholder_overlay"];
}
@end
// DashboardViewController.mm // DashboardViewController.mm
#import "DashboardViewController.h" #import "DashboardViewController.h"
#import "DashboardHeaderView.h"
#import <CoreGraphics/CoreGraphics.h> #import <CoreGraphics/CoreGraphics.h>
#import <objc/runtime.h> #import <objc/runtime.h>
#import "PlanViewController.h" #import "PlanViewController.h"
...@@ -10,6 +9,7 @@ ...@@ -10,6 +9,7 @@
#import "AppointmentViewController.h" #import "AppointmentViewController.h"
#import "AppointmentDetailsViewController.h" #import "AppointmentDetailsViewController.h"
#import "AppointmentViewMoreViewController.h" #import "AppointmentViewMoreViewController.h"
#import "GeneralInfoViewController.h"
@interface DashboardViewController () @interface DashboardViewController ()
...@@ -32,11 +32,16 @@ ...@@ -32,11 +32,16 @@
@property (nonatomic, strong) NSArray *announcements; @property (nonatomic, strong) NSArray *announcements;
@property (nonatomic, strong) UIView *loadingOverlay; @property (nonatomic, strong) UIView *loadingOverlay;
@property (nonatomic, strong) UIActivityIndicatorView *spinner; @property (nonatomic, strong) UIActivityIndicatorView *spinner;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIImageView *backgroundImageView;
@property (nonatomic, strong) UIImageView *overlayImageView;
@property (nonatomic, strong) UILabel *projectNameLabel;
@property (nonatomic, strong) UILabel *unitNameLabel;
@property (nonatomic, strong) UIButton *infoButton;
@end @end
@implementation DashboardViewController { @implementation DashboardViewController {
DashboardHeaderView *_headerView;
UIScrollView *_scrollView; UIScrollView *_scrollView;
UIView *_contentContainer, *_inspectionBanner; UIView *_contentContainer, *_inspectionBanner;
NSMutableDictionary *_sectionViews; NSMutableDictionary *_sectionViews;
...@@ -123,32 +128,133 @@ ...@@ -123,32 +128,133 @@
#pragma mark - Header #pragma mark - Header
- (void)setupHeader { - (void)setupHeader {
// Container height
CGFloat headerHeight = 220.0; CGFloat headerHeight = 220.0;
_headerView = [[DashboardHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, headerHeight)];
// Remote background image URL // Main container
self.headerView = [[UIView alloc] init];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
self.headerView.backgroundColor = [UIColor colorWithRed:0 green:0.43 blue:0.55 alpha:1.0];
[self.view addSubview:self.headerView];
[NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerView.heightAnchor constraintEqualToConstant:headerHeight]
]];
// -----------------------
// Background image
// -----------------------
self.backgroundImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.backgroundImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.headerView addSubview:self.backgroundImageView];
[NSLayoutConstraint activateConstraints:@[
[self.backgroundImageView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor],
[self.backgroundImageView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor],
[self.backgroundImageView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor],
[self.backgroundImageView.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor]
]];
// -----------------------
// Overlay image
// -----------------------
self.overlayImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.overlayImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.overlayImageView.contentMode = UIViewContentModeScaleAspectFill;
self.overlayImageView.alpha = 0.4;
[self.headerView addSubview:self.overlayImageView];
[NSLayoutConstraint activateConstraints:@[
[self.overlayImageView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor],
[self.overlayImageView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor],
[self.overlayImageView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor],
[self.overlayImageView.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor]
]];
// -----------------------
// Header Bar (top area)
// -----------------------
UIView *headerBar = [[UIView alloc] init];
headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:headerBar];
[NSLayoutConstraint activateConstraints:@[
[headerBar.topAnchor constraintEqualToAnchor:self.headerView.safeAreaLayoutGuide.topAnchor],
[headerBar.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor],
[headerBar.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor],
[headerBar.heightAnchor constraintEqualToConstant:44]
]];
// -----------------------
// Project name (center title)
// -----------------------
self.projectNameLabel = [[UILabel alloc] init];
self.projectNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.projectNameLabel.textColor = UIColor.whiteColor;
self.projectNameLabel.font = [UIFont boldSystemFontOfSize:18];
self.projectNameLabel.textAlignment = NSTextAlignmentCenter;
[headerBar addSubview:self.projectNameLabel];
[NSLayoutConstraint activateConstraints:@[
[self.projectNameLabel.centerXAnchor constraintEqualToAnchor:headerBar.centerXAnchor],
[self.projectNameLabel.centerYAnchor constraintEqualToAnchor:headerBar.centerYAnchor]
]];
// -----------------------
// Unit name bottom-left
// -----------------------
self.unitNameLabel = [[UILabel alloc] init];
self.unitNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.unitNameLabel.textColor = UIColor.whiteColor;
self.unitNameLabel.font = [UIFont boldSystemFontOfSize:20];
[self.headerView addSubview:self.unitNameLabel];
[NSLayoutConstraint activateConstraints:@[
[self.unitNameLabel.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:16],
[self.unitNameLabel.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor constant:-16]
]];
// -----------------------
// Info button bottom-right
// -----------------------
self.infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.infoButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoButton setImage:[UIImage systemImageNamed:@"info.circle"] forState:UIControlStateNormal];
self.infoButton.tintColor = UIColor.whiteColor;
[self.infoButton addTarget:self action:@selector(infoButtonTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.infoButton];
[NSLayoutConstraint activateConstraints:@[
[self.infoButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-16],
[self.infoButton.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor constant:-18],
[self.infoButton.widthAnchor constraintEqualToConstant:24],
[self.infoButton.heightAnchor constraintEqualToConstant:24]
]];
// -----------------------
// Load remote background image
// -----------------------
NSString *remoteHeaderURL = @"https://kitadev.commudesk.com/uploads/marketing_photo/164543837127.png"; NSString *remoteHeaderURL = @"https://kitadev.commudesk.com/uploads/marketing_photo/164543837127.png";
// Load image asynchronously so it doesn’t block UI
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:remoteHeaderURL]; NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:remoteHeaderURL]];
NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *img = data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"];
UIImage *remoteImage = nil;
if (data) {
remoteImage = [UIImage imageWithData:data];
}
self.headerBackgroundImage = remoteImage ?: [UIImage imageNamed:@"header_bg"];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[_headerView configureWithProjectName:@"Project A" self.backgroundImageView.image = img;
unitName:@"Unit 3A" self.overlayImageView.image = [UIImage imageNamed:@"overlay"];
backgroundImage:self.headerBackgroundImage
overlayImage:[UIImage imageNamed:@"overlay"]]; self.projectNameLabel.text = @"Project A";
self.unitNameLabel.text = @"Unit 3A";
}); });
}); });
[self.view addSubview:_headerView];
} }
- (void)handleBackButtonTapped { - (void)handleBackButtonTapped {
NSLog(@"🔙 Back button tapped"); NSLog(@"🔙 Back button tapped");
if (self.presentingViewController) { if (self.presentingViewController) {
...@@ -283,7 +389,6 @@ ...@@ -283,7 +389,6 @@
} }
#pragma mark - Inspection Checklist Banner #pragma mark - Inspection Checklist Banner
- (void)setupInspectionChecklistBanner { - (void)setupInspectionChecklistBanner {
BOOL blnContactlessHandover = NO; // mock GLOBALS.BLN_CONTACTLESS_HANDOVER == 1 BOOL blnContactlessHandover = NO; // mock GLOBALS.BLN_CONTACTLESS_HANDOVER == 1
BOOL blnInspChecklist = NO; // mock self.bln_InspChecklist BOOL blnInspChecklist = NO; // mock self.bln_InspChecklist
...@@ -371,7 +476,6 @@ ...@@ -371,7 +476,6 @@
NSNumber *paCount = mainData[@"pa_count"]; NSNumber *paCount = mainData[@"pa_count"];
NSNumber *poaCount = mainData[@"poa_count"]; NSNumber *poaCount = mainData[@"poa_count"];
NSString *accessMessage = mainData[@"access_item_message"]; NSString *accessMessage = mainData[@"access_item_message"];
NSString *accessTNC = mainData[@"access_item_terms_and_condition"];
NSString *handoverStatus = mainData[@"contactlessHandoverTempStatus"]; NSString *handoverStatus = mainData[@"contactlessHandoverTempStatus"];
NSString *handoverURL = mainData[@"contactlessHandoverTempURL"]; NSString *handoverURL = mainData[@"contactlessHandoverTempURL"];
...@@ -398,12 +502,11 @@ ...@@ -398,12 +502,11 @@
} }
} }
// If rectification count == pending access count, clear reminder
if (paCount.intValue == rectificationIssueCount) { if (paCount.intValue == rectificationIssueCount) {
hasPendingAccess = NO; hasPendingAccess = NO;
} }
// --- Store results --- // Store results
self.appointments = appointments; self.appointments = appointments;
self.issues = issues; self.issues = issues;
self.hasPendingAccess = hasPendingAccess; self.hasPendingAccess = hasPendingAccess;
...@@ -424,17 +527,16 @@ ...@@ -424,17 +527,16 @@
NSLog(@" • Block Handover: %d", blockHandover); NSLog(@" • Block Handover: %d", blockHandover);
NSLog(@" • Block Inspection: %d", blockInspection); NSLog(@" • Block Inspection: %d", blockInspection);
// ✅ UI updates must be on main thread
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[_headerView configureWithProjectName:self.projectName self.projectNameLabel.text = self.projectName;
unitName:self.projectCode self.unitNameLabel.text = self.projectCode;
backgroundImage:self.headerBackgroundImage self.overlayImageView.image = [UIImage imageNamed:@"overlay"];
overlayImage:[UIImage imageNamed:@"overlay"]];
[self refreshDashboardSectionsWithData:mainData]; [self refreshDashboardSectionsWithData:mainData];
}); });
} }
// DashboardViewController.m
- (void)processAnnouncementResponse:(NSDictionary *)data { - (void)processAnnouncementResponse:(NSDictionary *)data {
NSLog(@"Handling announcement: %@", data); NSLog(@"Handling announcement: %@", data);
...@@ -1054,6 +1156,14 @@ ...@@ -1054,6 +1156,14 @@
}]; }];
} }
-(void)infoButtonTapped{
GeneralInfoViewController *nextVC = [[GeneralInfoViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
nextVC.unitID = self.projectCode;
nextVC.unitName = self.projectName;
[self presentViewController:nextVC animated:YES completion:nil];
}
#pragma mark - Loading Overlay #pragma mark - Loading Overlay
- (void)showLoadingOverlay:(NSString *)message { - (void)showLoadingOverlay:(NSString *)message {
if (self.loadingOverlay.superview) return; // already showing if (self.loadingOverlay.superview) return; // already showing
......
// GeneralInfoViewController.h
#import <UIKit/UIKit.h>
@interface GeneralInfoViewController : UIViewController
@property (nonatomic, strong) NSString *unitID;
@property (nonatomic, strong) NSString *unitName;
@end
// GeneralInfoViewController.mm
#import <objc/runtime.h>
#import "GeneralInfoViewController.h"
#import "AppointmentAPIClient.h"
@interface GeneralInfoViewController () <UIDocumentInteractionControllerDelegate, UIScrollViewDelegate>
@property (nonatomic, strong) UIView *topBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UISegmentedControl *tabControl;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIStackView *ownerStack;
@property (nonatomic, strong) UIStackView *jointOwnersStack;
@property (nonatomic, strong) UIStackView *unitStack;
@property (nonatomic) NSLayoutConstraint *infoTop;
@property (nonatomic) NSLayoutConstraint *documentsTop;
@property (nonatomic) NSLayoutConstraint *formsTop;
@property (nonatomic) NSLayoutConstraint *infoBottom;
@property (nonatomic) NSLayoutConstraint *documentsBottom;
@property (nonatomic) NSLayoutConstraint *formsBottom;
// Sections for tabs
@property (nonatomic, strong) UIView *infoContainer;
@property (nonatomic, strong) UIView *documentsContainer;
@property (nonatomic, strong) UIView *formsContainer;
// Document collapsible sections
@property (nonatomic, strong) UIButton *generalDocHeader;
@property (nonatomic, strong) UIView *generalDocList;
@property (nonatomic, strong) UIButton *layoutDocHeader;
@property (nonatomic, strong) UIView *layoutDocList;
@property (nonatomic, strong) UIButton *unitDocHeader;
@property (nonatomic, strong) UIView *unitDocList;
@property (nonatomic, strong) UIStackView *generalDocStack;
@property (nonatomic, strong) UIStackView *layoutDocStack;
@property (nonatomic, strong) UIStackView *unitDocStack;
// Contact edit sheet
@property (nonatomic, strong) UIView *contactEditSheet;
@property (nonatomic, strong) UIView *contactDimView;
//api variables
@property (nonatomic, strong) NSArray *owners;
@property (nonatomic, strong) NSArray *jointOwners;
@property (nonatomic, strong) NSArray *generalDocuments;
@property (nonatomic, strong) NSArray *layoutDocuments;
@property (nonatomic, strong) NSArray *unitDocuments;
@property (nonatomic, strong) NSArray *forms;
@property (nonatomic, strong) NSString *layoutType;
@property (nonatomic, strong) NSString *layoutImage;
@property (nonatomic, strong) NSString *layoutThumbImage;
@property (nonatomic, strong) NSString *spaDate;
@property (nonatomic, strong) NSString *vpDate;
@property (nonatomic, strong) NSString *dlpStartDate;
@property (nonatomic, strong) NSString *dlpExpiryDate;
@property (nonatomic, strong) NSString *handoverDocument;
@property (nonatomic, strong) NSString *handoverStatus;
@property (nonatomic, strong) NSString *clearanceLetter;
@property (nonatomic, strong) UIStackView *formsStack;
@property (nonatomic, strong) NSLayoutConstraint *ownerSectionHeight;
typedef struct {
UIButton *button;
UIStackView *stack;
} SectionPair;
@end
@implementation GeneralInfoViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemGroupedBackgroundColor;
self.infoContainer = [[UIView alloc] init];
self.documentsContainer = [[UIView alloc] init];
self.formsContainer = [[UIView alloc] init];
self.infoContainer.clipsToBounds = YES;
self.documentsContainer.clipsToBounds = YES;
self.formsContainer.clipsToBounds = YES;
[self fetchGeneralInfo];
[self setupTopBar];
[self setupTabControl];
[self setupScrollView];
[self buildInfoTab];
[self buildDocumentsTab];
[self buildFormsTab];
[self setupContactEditSheet];
[self switchTab:0];
}
#pragma mark - Top Navigation Bar
- (void)setupTopBar {
self.topBar = [[UIView alloc] init];
self.topBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.42 blue:0.55 alpha:1];
self.topBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.topBar];
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"]
forState:UIControlStateNormal];
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
self.backButton.tintColor = UIColor.whiteColor;
[self.topBar addSubview:self.backButton];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = self.unitID ?: @"Unit Info";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:18];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.topBar addSubview:self.titleLabel];
// Constraints
[NSLayoutConstraint activateConstraints:@[
[self.topBar.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.topBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.topBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.topBar.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.topBar.leadingAnchor constant:12],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.topBar.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.topBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.topBar.centerYAnchor],
]];
}
#pragma mark - Tabs
- (void)setupTabControl {
self.tabControl = [[UISegmentedControl alloc] initWithItems:@[@"Info", @"Document", @"Forms"]];
self.tabControl.selectedSegmentIndex = 0;
self.tabControl.translatesAutoresizingMaskIntoConstraints = NO;
[self.tabControl addTarget:self
action:@selector(onTabChanged:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:self.tabControl];
[NSLayoutConstraint activateConstraints:@[
[self.tabControl.topAnchor constraintEqualToAnchor:self.topBar.bottomAnchor constant:12],
[self.tabControl.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.tabControl.widthAnchor constraintEqualToConstant:260],
[self.tabControl.heightAnchor constraintEqualToConstant:32],
]];
}
#pragma mark - Scroll View (Updated)
- (void)setupScrollView {
// ScrollView
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
[NSLayoutConstraint activateConstraints:@[
[self.scrollView.topAnchor constraintEqualToAnchor:self.tabControl.bottomAnchor constant:12],
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
// Content View inside scrollView
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
[NSLayoutConstraint activateConstraints:@[
[self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
[self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor]
]];
// Add the 3 tab containers
[self.contentView addSubview:self.infoContainer];
[self.contentView addSubview:self.documentsContainer];
[self.contentView addSubview:self.formsContainer];
// Horizontal constraints for all tabs
for (UIView *container in @[self.infoContainer, self.documentsContainer, self.formsContainer]) {
container.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[container.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[container.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor]
]];
}
//
// ⭐ TOP Constraints (only ONE is active at a time)
//
self.infoTop = [self.infoContainer.topAnchor constraintEqualToAnchor:self.contentView.topAnchor];
self.documentsTop = [self.documentsContainer.topAnchor constraintEqualToAnchor:self.contentView.topAnchor];
self.formsTop = [self.formsContainer.topAnchor constraintEqualToAnchor:self.contentView.topAnchor];
self.infoTop.active = YES; // default visible tab
self.documentsTop.active = NO;
self.formsTop.active = NO;
//
// ⭐ BOTTOM Constraints (only ONE is active at a time)
//
self.infoBottom = [self.infoContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.documentsBottom = [self.documentsContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.formsBottom = [self.formsContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.infoBottom.active = YES; // default tab
self.documentsBottom.active = NO;
self.formsBottom.active = NO;
}
#pragma mark - INFO TAB
- (void)buildInfoTab {
// --- Header ---
UILabel *header = [[UILabel alloc] init];
header.text = @"Owner Details";
header.font = [UIFont boldSystemFontOfSize:16];
header.textColor = UIColor.darkGrayColor;
header.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:header];
// --- Main Owner Stack ---
self.ownerStack = [[UIStackView alloc] init];
self.ownerStack.axis = UILayoutConstraintAxisVertical;
self.ownerStack.spacing = 8;
self.ownerStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:self.ownerStack];
// --- Joint Owners Title ---
UILabel *jointTitle = [[UILabel alloc] init];
jointTitle.text = @"Joint Owner Details";
jointTitle.font = [UIFont boldSystemFontOfSize:16];
jointTitle.textColor = UIColor.darkGrayColor;
jointTitle.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:jointTitle];
// --- Joint Owners Stack ---
self.jointOwnersStack = [[UIStackView alloc] init];
self.jointOwnersStack.axis = UILayoutConstraintAxisVertical;
self.jointOwnersStack.spacing = 8;
self.jointOwnersStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:self.jointOwnersStack];
// unit title
UILabel *unitTitle = [[UILabel alloc] init];
unitTitle.text = @"Unit Info";
unitTitle.font = [UIFont boldSystemFontOfSize:16];
unitTitle.textColor = UIColor.darkGrayColor;
unitTitle.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:unitTitle];
// unit stack
self.unitStack = [[UIStackView alloc] init];
self.unitStack.axis = UILayoutConstraintAxisVertical;
self.unitStack.spacing = 8;
self.unitStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.infoContainer addSubview:self.unitStack];
// --- Constraints ---
[NSLayoutConstraint activateConstraints:@[
[header.topAnchor constraintEqualToAnchor:self.infoContainer.topAnchor constant:12],
[header.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:12],
[self.ownerStack.topAnchor constraintEqualToAnchor:header.bottomAnchor constant:12],
[self.ownerStack.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:16],
[self.ownerStack.trailingAnchor constraintEqualToAnchor:self.infoContainer.trailingAnchor constant:-16],
[jointTitle.topAnchor constraintEqualToAnchor:self.ownerStack.bottomAnchor constant:20],
[jointTitle.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:16],
[self.jointOwnersStack.topAnchor constraintEqualToAnchor:jointTitle.bottomAnchor constant:12],
[self.jointOwnersStack.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:16],
[self.jointOwnersStack.trailingAnchor constraintEqualToAnchor:self.infoContainer.trailingAnchor constant:-16],
[unitTitle.topAnchor constraintEqualToAnchor:self.jointOwnersStack.bottomAnchor constant:20],
[unitTitle.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:16],
[self.unitStack.topAnchor constraintEqualToAnchor:unitTitle.bottomAnchor constant:12],
[self.unitStack.leadingAnchor constraintEqualToAnchor:self.infoContainer.leadingAnchor constant:16],
[self.unitStack.trailingAnchor constraintEqualToAnchor:self.infoContainer.trailingAnchor constant:-16],
[self.unitStack.bottomAnchor constraintEqualToAnchor:self.infoContainer.bottomAnchor constant:-20]
]];
self.infoBottom = [self.infoContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.infoBottom.active = YES;
}
#pragma mark - DOCUMENTS TAB
- (void)buildDocumentsTab {
self.documentsContainer.translatesAutoresizingMaskIntoConstraints = NO;
// 1️⃣ Clear old sections
for (UIView *v in self.documentsContainer.subviews) {
[v removeFromSuperview];
}
NSLog(@"documentsContainer subviews: %@", self.documentsContainer.subviews);
NSLog(@"BUILDING DOCUMENT TAB…");
// 2️⃣ Helper to build a section container
UIView* (^createSectionContainer)(NSString *, NSArray *) = ^UIView* (NSString *title, NSArray *items) {
if (!items || items.count == 0) return nil;
// Container
UIView *container = [[UIView alloc] init];
container.translatesAutoresizingMaskIntoConstraints = NO;
container.backgroundColor = UIColor.clearColor;
// Button (Header)
UIButton *btn = [self collapsibleHeaderWithTitle:title];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[container addSubview:btn];
// Stack
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 8;
stack.translatesAutoresizingMaskIntoConstraints = NO;
stack.backgroundColor = UIColor.whiteColor;
stack.layer.cornerRadius = 10;
stack.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
stack.layoutMarginsRelativeArrangement = YES;
stack.hidden = YES;
[container addSubview:stack];
// Bind stack to button
objc_setAssociatedObject(btn, "sectionStack", stack, OBJC_ASSOCIATION_RETAIN);
[btn addTarget:self action:@selector(toggleSection:) forControlEvents:UIControlEventTouchUpInside];
// Add document labels
for (NSDictionary *doc in items) {
UILabel *lbl = [self label:doc[@"name"]];
[stack addArrangedSubview:lbl];
}
// Constraints inside container
[NSLayoutConstraint activateConstraints:@[
[btn.topAnchor constraintEqualToAnchor:container.topAnchor constant:0],
[btn.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:0],
[btn.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:0],
[btn.heightAnchor constraintEqualToConstant:44],
[stack.topAnchor constraintEqualToAnchor:btn.bottomAnchor constant:4],
[stack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
[stack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor],
[stack.bottomAnchor constraintEqualToAnchor:container.bottomAnchor]
]];
// Save references for refresh if needed
if ([title isEqualToString:@"General"]) self.generalDocStack = stack;
if ([title isEqualToString:@"Layout"]) self.layoutDocStack = stack;
if ([title isEqualToString:@"Unit"]) self.unitDocStack = stack;
return container;
};
// 3️⃣ Create section containers
UIView *generalContainer = createSectionContainer(@"General", self.generalDocuments);
UIView *layoutContainer = createSectionContainer(@"Layout", self.layoutDocuments);
UIView *unitContainer = createSectionContainer(@"Unit", self.unitDocuments);
// 4️⃣ Add non-nil containers to documentsContainer
NSMutableArray<UIView *> *sections = [NSMutableArray array];
if (generalContainer) [sections addObject:generalContainer];
if (layoutContainer) [sections addObject:layoutContainer];
if (unitContainer) [sections addObject:unitContainer];
UIView *previous = nil;
for (UIView *section in sections) {
[self.documentsContainer addSubview:section];
[NSLayoutConstraint activateConstraints:@[
[section.leadingAnchor constraintEqualToAnchor:self.documentsContainer.leadingAnchor constant:16],
[section.trailingAnchor constraintEqualToAnchor:self.documentsContainer.trailingAnchor constant:-16],
]];
if (previous) {
[section.topAnchor constraintEqualToAnchor:previous.bottomAnchor constant:16].active = YES;
} else {
[section.topAnchor constraintEqualToAnchor:self.documentsContainer.topAnchor constant:16].active = YES;
}
previous = section;
}
// Pin last section to bottom so scroll view content size works
// if (previous) {
// [previous.bottomAnchor constraintEqualToAnchor:self.documentsContainer.bottomAnchor constant:-16].active = YES;
// }
self.documentsBottom = [self.documentsContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.documentsBottom.active = NO;
}
- (UIButton *)collapsibleHeaderWithTitle:(NSString *)title {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.backgroundColor = UIColor.blackColor;
btn.layer.cornerRadius = 12;
btn.clipsToBounds = YES;
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[btn setTitle:title forState:UIControlStateNormal];
btn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
btn.titleEdgeInsets = UIEdgeInsetsMake(0, 12, 0, 0);
// Chevron icon using SF Symbols
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"chevron.down"]];
arrow.tintColor = UIColor.whiteColor;
arrow.translatesAutoresizingMaskIntoConstraints = NO;
[btn addSubview:arrow];
// Attach arrow to the button object
objc_setAssociatedObject(btn, "arrowView", arrow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[NSLayoutConstraint activateConstraints:@[
[arrow.centerYAnchor constraintEqualToAnchor:btn.centerYAnchor],
[arrow.trailingAnchor constraintEqualToAnchor:btn.trailingAnchor constant:-12],
[arrow.heightAnchor constraintEqualToConstant:14],
[arrow.widthAnchor constraintEqualToConstant:10]
]];
return btn;
}
- (UIView *)collapsibleListPlaceholder {
UIView *box = [[UIView alloc] init];
box.backgroundColor = UIColor.whiteColor;
box.layer.cornerRadius = 10;
box.translatesAutoresizingMaskIntoConstraints = NO;
self.ownerSectionHeight = [box.heightAnchor constraintEqualToConstant:0];
self.ownerSectionHeight.active = YES; // collapsed at start
return box;
}
#pragma mark - FORMS TAB
- (void)buildFormsTab {
self.formsContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.formsContainer];
self.formsStack = [[UIStackView alloc] init];
self.formsStack.axis = UILayoutConstraintAxisVertical;
self.formsStack.spacing = 8;
self.formsStack.translatesAutoresizingMaskIntoConstraints = NO;
[self.formsContainer addSubview:self.formsStack];
[NSLayoutConstraint activateConstraints:@[
[self.formsStack.topAnchor constraintEqualToAnchor:self.formsContainer.topAnchor constant:16],
[self.formsStack.leadingAnchor constraintEqualToAnchor:self.formsContainer.leadingAnchor constant:16],
[self.formsStack.trailingAnchor constraintEqualToAnchor:self.formsContainer.trailingAnchor constant:-16],
[self.formsStack.bottomAnchor constraintEqualToAnchor:self.formsContainer.bottomAnchor constant:-16]
]];
self.formsBottom = [self.formsContainer.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor];
self.formsBottom.active = NO;
}
#pragma mark - Tab switching
- (void)onTabChanged:(UISegmentedControl *)sender {
[self switchTab:sender.selectedSegmentIndex];
}
- (void)switchTab:(NSInteger)index {
// Visibility
self.infoContainer.hidden = index != 0;
self.documentsContainer.hidden = index != 1;
self.formsContainer.hidden = index != 2;
// Only active tab receives interaction
self.infoContainer.userInteractionEnabled = (index == 0);
self.documentsContainer.userInteractionEnabled = (index == 1);
self.formsContainer.userInteractionEnabled = (index == 2);
// Disable all top constraints
self.infoTop.active = NO;
self.documentsTop.active = NO;
self.formsTop.active = NO;
// Disable all bottom constraints
self.infoBottom.active = NO;
self.documentsBottom.active = NO;
self.formsBottom.active = NO;
// Enable selected tab’s constraints
if (index == 0) {
self.infoTop.active = YES;
self.infoBottom.active = YES;
}
else if (index == 1) {
self.documentsTop.active = YES;
self.documentsBottom.active = YES;
}
else if (index == 2) {
self.formsTop.active = YES;
self.formsBottom.active = YES;
}
// Animate layout change
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
}
#pragma mark - Contact Edit Bottom Sheet
- (void)setupContactEditSheet {
self.contactDimView = [[UIView alloc] init];
self.contactDimView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
self.contactDimView.hidden = YES;
self.contactDimView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.contactDimView];
self.contactEditSheet = [[UIView alloc] init];
self.contactEditSheet.backgroundColor = UIColor.whiteColor;
self.contactEditSheet.layer.cornerRadius = 16;
self.contactEditSheet.translatesAutoresizingMaskIntoConstraints = NO;
self.contactEditSheet.hidden = YES;
[self.view addSubview:self.contactEditSheet];
[NSLayoutConstraint activateConstraints:@[
[self.contactDimView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.contactDimView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.contactDimView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.contactDimView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.contactEditSheet.heightAnchor constraintEqualToConstant:300],
[self.contactEditSheet.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.contactEditSheet.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.contactEditSheet.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
}
#pragma mark - api
- (void)fetchGeneralInfo {
[AppointmentAPIClient fetchGeneralInfo:^(NSDictionary *data, NSError *error) {
if (error || !data) {
NSLog(@"❌ General info error: %@", error.localizedDescription);
return;
}
NSLog(@"📦 Received General Info Data: %@", data);
NSDictionary *general = data[@"Data"];
// 1️⃣ Write to file for debugging (your existing logic)
NSString *plainText = [NSString stringWithFormat:@"%@", data];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"generalData.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 appointData saved as plain text to: %@", filePath);
// 2️⃣ Safely extract each field
self.owners = general[@"unit_owner"] ?: @[];
self.jointOwners = general[@"joint_unit_owner"] ?: @[];
self.unitDocuments = general[@"owner_document_unit"] ?: @[];
self.layoutDocuments = general[@"owner_document_phase"] ?: @[];
self.generalDocuments = general[@"owner_document_project"] ?: @[];
self.forms = general[@"contactless_form"] ?: @[];
self.layoutType = general[@"layout"] ?: @"";
self.layoutImage = general[@"image"] ?: @"";
self.layoutThumbImage = general[@"thumb_image"] ?: @"";
self.spaDate = general[@"spa_date"] ?: @"";
self.vpDate = general[@"vp_date"] ?: general[@"vp_date"][@"date"] ?: @"";
self.dlpStartDate = general[@"dlp_start_date"] ?: @"";
self.dlpExpiryDate = general[@"dlp_expiry_date"] ?: @"";
self.handoverDocument = general[@"handover_document"] ?: @"";
self.handoverStatus = general[@"handover_status"];
self.clearanceLetter = general[@"clearance_letter_attachment"] ?: @"";
// 3️⃣ Update UI on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshUI];
});
}];
}
- (void)refreshUI {
NSLog(@"refreshUI called");
// ------------------------
// 0️⃣ CLEAR EXISTING UI
// ------------------------
// Clear Owners
for (UIView *v in self.ownerStack.arrangedSubviews) {
[self.ownerStack removeArrangedSubview:v];
[v removeFromSuperview];
}
// Clear Joint Owners
for (UIView *v in self.jointOwnersStack.arrangedSubviews) {
[self.jointOwnersStack removeArrangedSubview:v];
[v removeFromSuperview];
}
// Clear Unit Info
for (UIView *v in self.unitStack.arrangedSubviews) {
[self.unitStack removeArrangedSubview:v];
[v removeFromSuperview];
}
// ------------------------
// 1️⃣ OWNERS SECTION
// ------------------------
NSArray *ownerTitles = @[@"Name", @"Email", @"Contact No."];
CGFloat maxTitleWidth = 0;
for (NSString *title in ownerTitles) {
CGSize size = [title sizeWithAttributes:@{NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}];
maxTitleWidth = MAX(maxTitleWidth, size.width);
}
maxTitleWidth += 6;
UIStackView* (^OwnerCard)(NSDictionary *) = ^UIStackView* (NSDictionary *owner) {
UIStackView *card = [[UIStackView alloc] init];
card.axis = UILayoutConstraintAxisVertical;
card.spacing = 8;
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 10;
card.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
card.layoutMarginsRelativeArrangement = YES;
card.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView* (^Row)(NSString *, NSString *) = ^UIStackView* (NSString *title, NSString *value) {
UIStackView *row = [[UIStackView alloc] init];
row.axis = UILayoutConstraintAxisHorizontal;
row.spacing = 8;
row.alignment = UIStackViewAlignmentFill;
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = [NSString stringWithFormat:@"%@:", title];
titleLabel.font = [UIFont boldSystemFontOfSize:14];
[titleLabel.widthAnchor constraintEqualToConstant:maxTitleWidth].active = YES;
UILabel *valueLabel = [[UILabel alloc] init];
valueLabel.text = value ?: @"-";
valueLabel.font = [UIFont systemFontOfSize:14];
valueLabel.numberOfLines = 0;
[row addArrangedSubview:titleLabel];
[row addArrangedSubview:valueLabel];
return row;
};
NSString *name = owner[@"name"] ?: @"-";
NSString *email = owner[@"email"] ?: @"-";
NSString *contact = owner[@"contact"] ?: @"-";
[card addArrangedSubview:Row(@"Name", name)];
[card addArrangedSubview:Row(@"Email", email)];
[card addArrangedSubview:Row(@"Contact No.", contact)];
return card;
};
// Add Owner cards
for (NSDictionary *owner in self.owners) {
[self.ownerStack addArrangedSubview:OwnerCard(owner)];
}
// Add Joint Owner cards
for (NSDictionary *owner in self.jointOwners) {
[self.jointOwnersStack addArrangedSubview:OwnerCard(owner)];
}
// ------------------------
// 2️⃣ UNIT INFO SECTION
// ------------------------
NSString *projectName = self.unitName ?: @"-";
NSString *projectId = self.unitID ?: @"-";
NSString *layoutType = self.layoutType ?: @"-";
NSString *expiryDate = self.dlpExpiryDate ?: @"-";
NSString *layoutImageURL = self.layoutImage ?: @"";
NSString *handoverDocURL = self.handoverDocument ?: @"";
UIStackView *unitCard = [[UIStackView alloc] init];
unitCard.axis = UILayoutConstraintAxisVertical;
unitCard.spacing = 12;
unitCard.backgroundColor = UIColor.whiteColor;
unitCard.layer.cornerRadius = 10;
unitCard.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
unitCard.layoutMarginsRelativeArrangement = YES;
unitCard.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView* (^TitleRow)(NSString*, UIView*) = ^UIStackView* (NSString *title, UIView *valueView) {
UIStackView *row = [[UIStackView alloc] init];
row.axis = UILayoutConstraintAxisHorizontal;
row.spacing = 8;
row.alignment = UIStackViewAlignmentTop; // better for multiline values
row.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = [NSString stringWithFormat:@"%@:", title];
titleLabel.font = [UIFont boldSystemFontOfSize:14];
titleLabel.numberOfLines = 0;
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
// 🔥 Apply same alignment width as Owners
[titleLabel.widthAnchor constraintEqualToConstant:maxTitleWidth].active = YES;
[row addArrangedSubview:titleLabel];
[row addArrangedSubview:valueView];
return row;
};
// Project Name
[unitCard addArrangedSubview:TitleRow(@"Project Name", [self label:projectName])];
// Unit No
[unitCard addArrangedSubview:TitleRow(@"Unit No.", [self label:projectId])];
// Layout Type
[unitCard addArrangedSubview:TitleRow(@"Layout Type", [self label:layoutType])];
// Layout Image
if (layoutImageURL.length > 0) {
UIImageView *imgView = [[UIImageView alloc] init];
imgView.translatesAutoresizingMaskIntoConstraints = NO;
imgView.contentMode = UIViewContentModeScaleAspectFit;
imgView.layer.cornerRadius = 8;
imgView.clipsToBounds = YES;
imgView.userInteractionEnabled = YES;
[imgView.widthAnchor constraintEqualToConstant:120].active = YES;
[imgView.heightAnchor constraintEqualToConstant:80].active = YES;
NSURL *url = [NSURL URLWithString:layoutImageURL];
if (url) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
imgView.image = img;
});
}
});
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showFullImage:)];
[imgView addGestureRecognizer:tap];
[unitCard addArrangedSubview:TitleRow(@"Layout Image", imgView)];
}
// DLP Expiry
[unitCard addArrangedSubview:TitleRow(@"DLP Expiry Date", [self label:expiryDate])];
// Handover PDF
if (handoverDocURL.length > 0) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setImage:[UIImage systemImageNamed:@"doc.text"] forState:UIControlStateNormal];
btn.tintColor = UIColor.systemBlueColor;
objc_setAssociatedObject(btn, "pdfURL", handoverDocURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Set a custom filename for the button PDF
NSString *pdfName = @"HandoverDocument"; // change this as needed
objc_setAssociatedObject(btn, "pdfName", pdfName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:@selector(openRemotePDFButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[unitCard addArrangedSubview:TitleRow(@"Handover Document", btn)];
}
[self.unitStack addArrangedSubview:unitCard];
// ------------------------
// DOCUMENTS TAB (Rebuild)
// ------------------------
[self refreshDocumentsUI];
// ------------------------
// 4️⃣ FORMS TAB
// ------------------------
for (UIView *v in self.formsStack.arrangedSubviews) {
[self.formsStack removeArrangedSubview:v];
[v removeFromSuperview];
}
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = @"dd MMM yyyy, hh:mm a";
df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
NSDateFormatter *displayFormatter = [[NSDateFormatter alloc] init];
displayFormatter.dateStyle = NSDateFormatterMediumStyle;
NSArray *sortedForms =
[self.forms sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *f1, NSDictionary *f2) {
NSDate *d1 = [df dateFromString:f1[@"datetime"] ?: @"01 Jan 1970, 12:00 AM"];
NSDate *d2 = [df dateFromString:f2[@"datetime"] ?: @"01 Jan 1970, 12:00 AM"];
return [d2 compare:d1];
}];
for (NSDictionary *form in sortedForms) {
NSString *name = form[@"name"] ?: @"-";
NSString *dateStr = form[@"datetime"] ?: @"";
NSDate *date = [df dateFromString:dateStr];
NSString *file = form[@"file"];
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
UIStackView *labelStack = [[UIStackView alloc] init];
labelStack.axis = UILayoutConstraintAxisVertical;
labelStack.spacing = 4;
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.text = name;
nameLabel.font = [UIFont boldSystemFontOfSize:14];
[labelStack addArrangedSubview:nameLabel];
UILabel *dateLabel = [[UILabel alloc] init];
dateLabel.text = date ? [NSString stringWithFormat:@"Submitted on %@", [displayFormatter stringFromDate:date]]
: @"Submitted on -";
dateLabel.font = [UIFont systemFontOfSize:12];
[labelStack addArrangedSubview:dateLabel];
[card addSubview:labelStack];
labelStack.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[labelStack.topAnchor constraintEqualToAnchor:card.topAnchor constant:8],
[labelStack.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[labelStack.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12],
[labelStack.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-8]
]];
if (file && ![file isKindOfClass:[NSNull class]] && ![file isEqualToString:@"<null>"]) {
objc_setAssociatedObject(card, "pdfURL", file, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Set a custom filename, e.g., based on the file or a label
NSString *pdfName = name; // change this as needed
objc_setAssociatedObject(card, "pdfName", pdfName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openPDFFromView:)];
[card addGestureRecognizer:tap];
}
[self.formsStack addArrangedSubview:card];
[card.heightAnchor constraintGreaterThanOrEqualToConstant:50].active = YES;
}
}
- (void)refreshDocumentsUI {
// 1. Clear old content
for (UIView *v in self.documentsContainer.subviews) {
[v removeFromSuperview];
}
SectionPair general = [self buildDocumentSection:@"General" items:self.generalDocuments];
SectionPair layout = [self buildDocumentSection:@"Layout" items:self.layoutDocuments];
SectionPair unit = [self buildDocumentSection:@"Unit" items:self.unitDocuments];
NSArray *sections = @[
general.button ? @[general.button, general.stack] : @[],
layout.button ? @[layout.button, layout.stack] : @[],
unit.button ? @[unit.button, unit.stack] : @[]
];
UIView *previous = nil;
for (NSArray *pair in sections) {
if (pair.count == 0) continue;
UIButton *btn = pair[0];
UIStackView *stack = pair[1];
[self.documentsContainer addSubview:btn];
[self.documentsContainer addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[btn.leadingAnchor constraintEqualToAnchor:self.documentsContainer.leadingAnchor constant:16],
[btn.trailingAnchor constraintEqualToAnchor:self.documentsContainer.trailingAnchor constant:-16],
[btn.heightAnchor constraintEqualToConstant:44],
[btn.topAnchor constraintEqualToAnchor:previous ? previous.bottomAnchor : self.documentsContainer.topAnchor
constant:16],
[stack.topAnchor constraintEqualToAnchor:btn.bottomAnchor constant:4],
[stack.leadingAnchor constraintEqualToAnchor:btn.leadingAnchor],
[stack.trailingAnchor constraintEqualToAnchor:btn.trailingAnchor],
]];
previous = stack;
}
if (previous) {
[NSLayoutConstraint activateConstraints:@[
[previous.bottomAnchor constraintEqualToAnchor:self.documentsContainer.bottomAnchor constant:-30]
]];
}
}
- (SectionPair)buildDocumentSection:(NSString *)title items:(NSArray *)items {
if (!items || items.count == 0) {
SectionPair empty = { nil, nil };
return empty;
}
UIButton *btn = [self collapsibleHeaderWithTitle:title];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[self.documentsContainer addSubview:btn];
UIStackView *stack = [[UIStackView alloc] init];
stack.axis = UILayoutConstraintAxisVertical;
stack.spacing = 8;
stack.hidden = NO;
stack.translatesAutoresizingMaskIntoConstraints = NO;
stack.backgroundColor = UIColor.whiteColor;
stack.layer.cornerRadius = 10;
stack.layoutMargins = UIEdgeInsetsMake(12, 12, 12, 12);
stack.layoutMarginsRelativeArrangement = YES;
[self.documentsContainer addSubview:stack];
objc_setAssociatedObject(btn, "sectionStack", stack, OBJC_ASSOCIATION_RETAIN);
[btn addTarget:self action:@selector(toggleSection:) forControlEvents:UIControlEventTouchUpInside];
UIImageView *arrow = objc_getAssociatedObject(btn, "arrowView");
if (arrow) {
arrow.transform = CGAffineTransformMakeRotation(M_PI); // expanded state (chevron.up)
}
for (NSDictionary *doc in items) {
NSString *fileURL = doc[@"file"];
NSString *name = doc[@"name"] ?: @"Document";
// Card container
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.borderWidth = 0.5;
card.layer.borderColor = UIColor.lightGrayColor.CGColor;
card.translatesAutoresizingMaskIntoConstraints = NO;
// Add label inside card
UILabel *lbl = [[UILabel alloc] init];
lbl.text = name;
lbl.font = [UIFont systemFontOfSize:15];
lbl.numberOfLines = 0;
[card addSubview:lbl];
lbl.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[lbl.topAnchor constraintEqualToAnchor:card.topAnchor constant:10],
[lbl.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[lbl.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12],
[lbl.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-10],
]];
// Attach PDF URL to card
if (fileURL && ![fileURL isKindOfClass:[NSNull class]] && fileURL.length > 0) {
objc_setAssociatedObject(card, "pdfURL", fileURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(card, "pdfName", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Add tap gesture
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(openPDFFromView:)];
card.userInteractionEnabled = YES;
[card addGestureRecognizer:tap];
}
[stack addArrangedSubview:card];
}
SectionPair pair = { btn, stack };
return pair;
}
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)toggleSection:(UIButton *)sender {
UIStackView *stack = objc_getAssociatedObject(sender, "sectionStack");
if (!stack) return;
UIImageView *arrow = objc_getAssociatedObject(sender, "arrowView");
if (!arrow) return;
BOOL isCollapsed = stack.isHidden;
// Animate the change
[UIView animateWithDuration:0.3 animations:^{
stack.hidden = !isCollapsed;
// Rotate the arrow: down = collapsed, up = expanded
arrow.transform = isCollapsed ? CGAffineTransformMakeRotation(M_PI) : CGAffineTransformIdentity;
// Force layout update so scroll view resizes properly
[self.documentsContainer layoutIfNeeded];
}];
}
- (UILabel *)label:(NSString *)text {
UILabel *lb = [[UILabel alloc] init];
lb.text = text;
lb.font = [UIFont systemFontOfSize:16];
return lb;
}
-(void) formTapped {
NSLog(@"Form tapped");
}
// Download remote PDF to temp file and open with UIDocumentInteractionController
- (void)openRemotePDF:(NSString *)remoteURLString fileName:(NSString *)fileName {
if (!remoteURLString || [remoteURLString isEqualToString:@""]) return;
if (!fileName || [fileName isEqualToString:@""]) fileName = @"Document";
// Ensure the filename ends with .pdf
if (![fileName.pathExtension.lowercaseString isEqualToString:@"pdf"]) {
fileName = [fileName stringByAppendingPathExtension:@"pdf"];
}
NSURL *remoteURL = [NSURL URLWithString:remoteURLString];
if (!remoteURL) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *downloadError = nil;
NSData *pdfData = [NSData dataWithContentsOfURL:remoteURL options:0 error:&downloadError];
if (!pdfData || downloadError) {
NSLog(@"Failed to download PDF: %@", downloadError);
return;
}
// Save to temporary file with custom name
NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
NSURL *fileURL = [NSURL fileURLWithPath:tmpPath];
BOOL success = [pdfData writeToURL:fileURL atomically:YES];
if (!success) {
NSLog(@"Failed to save PDF to temp file");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
UIDocumentInteractionController *docController =
[UIDocumentInteractionController interactionControllerWithURL:fileURL];
docController.delegate = self;
[docController presentPreviewAnimated:YES];
});
});
}
- (void)openPDFFromView:(UITapGestureRecognizer *)gesture {
NSString *url = objc_getAssociatedObject(gesture.view, "pdfURL");
NSString *fileName = objc_getAssociatedObject(gesture.view, "pdfName"); // optional custom name
[self openRemotePDF:url fileName:fileName ?: @"Document"];
}
- (void)openRemotePDFButtonTapped:(UIButton *)sender {
NSString *url = objc_getAssociatedObject(sender, "pdfURL");
NSString *fileName = objc_getAssociatedObject(sender, "pdfName"); // optional custom name
[self openRemotePDF:url fileName:fileName ?: @"Document"];
}
// UIDocumentInteractionController delegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller {
return self;
}
- (void)showFullImage:(UITapGestureRecognizer *)sender {
UIImageView *imageView = (UIImageView *)sender.view;
if (!imageView.image) return; // Nothing to show
// Full screen scroll view
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.backgroundColor = [UIColor blackColor];
scrollView.minimumZoomScale = 1.0;
scrollView.maximumZoomScale = 3.0;
scrollView.delegate = self;
scrollView.tag = 999; // optional for reference
// Full-screen image view
UIImageView *fullImageView = [[UIImageView alloc] initWithImage:imageView.image];
fullImageView.contentMode = UIViewContentModeScaleAspectFit;
fullImageView.frame = scrollView.bounds;
fullImageView.userInteractionEnabled = YES;
[scrollView addSubview:fullImageView];
// Add tap to dismiss
UITapGestureRecognizer *dismissTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissFullImage:)];
[scrollView addGestureRecognizer:dismissTap];
// Add to main view
[self.view addSubview:scrollView];
}
- (void)dismissFullImage:(UITapGestureRecognizer *)sender {
[sender.view removeFromSuperview];
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
// Return the image view inside the scroll view
return scrollView.subviews.firstObject;
}
@end
...@@ -8,32 +8,32 @@ ...@@ -8,32 +8,32 @@
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string> <string>ios-arm64</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
<dict> <dict>
<key>BinaryPath</key> <key>BinaryPath</key>
<string>QmsPluginFramework.framework/QmsPluginFramework</string> <string>QmsPluginFramework.framework/QmsPluginFramework</string>
<key>LibraryIdentifier</key> <key>LibraryIdentifier</key>
<string>ios-arm64</string> <string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key> <key>LibraryPath</key>
<string>QmsPluginFramework.framework</string> <string>QmsPluginFramework.framework</string>
<key>SupportedArchitectures</key> <key>SupportedArchitectures</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
<string>x86_64</string>
</array> </array>
<key>SupportedPlatform</key> <key>SupportedPlatform</key>
<string>ios</string> <string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict> </dict>
</array> </array>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
......
...@@ -4036,13 +4036,13 @@ __metadata: ...@@ -4036,13 +4036,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"QmsPlugin@git+http://convep.ddns.me:20000/WeiHan/weihan-plugin.git": "QmsPlugin@file:../::locator=qms-plugin-example%40workspace%3Aexample":
version: 0.1.0 version: 0.1.0
resolution: "QmsPlugin@git+http://convep.ddns.me:20000/WeiHan/weihan-plugin.git#commit=1eec3cd3c23eb7eff0cfdfd7fb189aed9b5d14fc" resolution: "QmsPlugin@file:../#../::hash=af21c5&locator=qms-plugin-example%40workspace%3Aexample"
peerDependencies: peerDependencies:
react: "*" react: "*"
react-native: "*" react-native: "*"
checksum: 2fb7968faaef90c557836de22e96ea10ec933db7d570e09bf5cac2cc73fb84aaf0fb4a0978328e1f3b86a32d06992861798bed9ffe3c53f8735e746dbaffde3f checksum: 62359d48e334f58417817ce08cbadbc903d955edfcec23fcd1f25e51a95155b0465958c08490b7333e72f4e1272fd5bd0678dd812a5805f8c5daeb7d6c891213
languageName: node languageName: node
linkType: hard linkType: hard
...@@ -10563,7 +10563,7 @@ __metadata: ...@@ -10563,7 +10563,7 @@ __metadata:
"@types/jest": ^29.5.13 "@types/jest": ^29.5.13
"@types/react": ^19.1.0 "@types/react": ^19.1.0
"@types/react-test-renderer": ^19.1.0 "@types/react-test-renderer": ^19.1.0
QmsPlugin: "git+http://convep.ddns.me:20000/WeiHan/weihan-plugin.git" QmsPlugin: "file:../"
eslint: ^8.19.0 eslint: ^8.19.0
jest: ^29.6.3 jest: ^29.6.3
prettier: 2.8.8 prettier: 2.8.8
......
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