Commit 9eb256b5 authored by Wei Han's avatar Wei Han

code update

parent 010b196a
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; 44006126336D83DB220E7214 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
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 */; };
AF643AF9AD2E6D7F8427E3F1 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF98AE45D9936432AFEC029 /* libPods-example.a */; }; F2581A09DD01A13570F68628 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D30A3A8F21F213E96F2C8F7 /* 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>"; };
688F14F134731D6E3695DCD4 /* 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>"; }; 2D30A3A8F21F213E96F2C8F7 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6FF98AE45D9936432AFEC029 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 508BA7CC4A1A0B5A23D84649 /* 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>"; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = example/AppDelegate.swift; sourceTree = "<group>"; };
7C33988A67E3A60B09B56690 /* 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>"; };
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>"; };
E7FE0B6CF3283929F0ED2217 /* 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>"; };
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 = (
AF643AF9AD2E6D7F8427E3F1 /* libPods-example.a in Frameworks */, F2581A09DD01A13570F68628 /* 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 */,
6FF98AE45D9936432AFEC029 /* libPods-example.a */, 2D30A3A8F21F213E96F2C8F7 /* 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 = (
E7FE0B6CF3283929F0ED2217 /* Pods-example.debug.xcconfig */, 7C33988A67E3A60B09B56690 /* Pods-example.debug.xcconfig */,
688F14F134731D6E3695DCD4 /* Pods-example.release.xcconfig */, 508BA7CC4A1A0B5A23D84649 /* 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 = (
73D6515E0B05E56E1C38DCEE /* [CP] Check Pods Manifest.lock */, D47CB7885794FE215FE8B238 /* [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 */,
13DCD710B66171E18819239B /* [CP] Embed Pods Frameworks */, ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */,
E355AFBEE78FE218C8B2F995 /* [CP] Copy Pods Resources */, 4B920070F73F4233D4D79AC3 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
...@@ -183,7 +183,24 @@ ...@@ -183,7 +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";
}; };
13DCD710B66171E18819239B /* [CP] Embed Pods Frameworks */ = { 4B920070F73F4233D4D79AC3 /* [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;
};
ABA4014BAFCE28774B9D5225 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
...@@ -200,7 +217,7 @@ ...@@ -200,7 +217,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
73D6515E0B05E56E1C38DCEE /* [CP] Check Pods Manifest.lock */ = { D47CB7885794FE215FE8B238 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
...@@ -222,23 +239,6 @@ ...@@ -222,23 +239,6 @@
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;
}; };
E355AFBEE78FE218C8B2F995 /* [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 */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
...@@ -255,7 +255,7 @@ ...@@ -255,7 +255,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = { 13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = E7FE0B6CF3283929F0ED2217 /* Pods-example.debug.xcconfig */; baseConfigurationReference = 7C33988A67E3A60B09B56690 /* 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 = 688F14F134731D6E3695DCD4 /* Pods-example.release.xcconfig */; baseConfigurationReference = 508BA7CC4A1A0B5A23D84649 /* Pods-example.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
......
...@@ -9,10 +9,11 @@ ...@@ -9,10 +9,11 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F8D09082E9605E200C764FF /* CoreGraphics.framework */; }; 2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F8D09082E9605E200C764FF /* CoreGraphics.framework */; };
2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F99420C2EA9C5F000622199 /* QuartzCore.framework */; }; 2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F99420C2EA9C5F000622199 /* QuartzCore.framework */; };
2FAEF09A2E8B7BC80086EDA3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FAEF0992E8B7BC80086EDA3 /* UIKit.framework */; };
2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */; }; 2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */; };
2FB2922A2EAB04F400D9F164 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB292292EAB04F400D9F164 /* AVFoundation.framework */; }; 2FB2922A2EAB04F400D9F164 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB292292EAB04F400D9F164 /* AVFoundation.framework */; };
2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */; }; 2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */; };
2FBC22B52EF93B8A00882B6F /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */; };
2FBC22B72EF93BB400882B6F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FBC22B62EF93BB400882B6F /* UIKit.framework */; };
2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD1557A2ECAC5CC00233981 /* WebKit.framework */; }; 2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD1557A2ECAC5CC00233981 /* WebKit.framework */; };
2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD9A0B72EF0E85B00A7B53E /* Network.framework */; }; 2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FD9A0B72EF0E85B00A7B53E /* Network.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
...@@ -25,6 +26,8 @@ ...@@ -25,6 +26,8 @@
2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 2FAEF09B2E8B7BD40086EDA3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
2FB292292EAB04F400D9F164 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; 2FB292292EAB04F400D9F164 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.0.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/EventKit.framework; sourceTree = DEVELOPER_DIR; }; 2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/EventKit.framework; sourceTree = DEVELOPER_DIR; };
2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
2FBC22B62EF93BB400882B6F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
2FD027B02EEFD25100CF5C33 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; }; 2FD027B02EEFD25100CF5C33 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };
2FD1557A2ECAC5CC00233981 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/iOSSupport/System/Library/PrivateFrameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 2FD1557A2ECAC5CC00233981 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/iOSSupport/System/Library/PrivateFrameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
2FD9A0B72EF0E85B00A7B53E /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; 2FD9A0B72EF0E85B00A7B53E /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; };
...@@ -34,7 +37,7 @@ ...@@ -34,7 +37,7 @@
2FD9A0B92EF0F7FD00A7B53E /* Exceptions for "QmsPluginFramework" folder in "QmsPluginFramework" target */ = { 2FD9A0B92EF0F7FD00A7B53E /* Exceptions for "QmsPluginFramework" folder in "QmsPluginFramework" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
FSCalendar/Info.plist, Utilities/FSCalendar/Info.plist,
); );
publicHeaders = ( publicHeaders = (
QmsPlugin.h, QmsPlugin.h,
...@@ -60,6 +63,8 @@ ...@@ -60,6 +63,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2FBC22B72EF93BB400882B6F /* UIKit.framework in Frameworks */,
2FBC22B52EF93B8A00882B6F /* UniformTypeIdentifiers.framework in Frameworks */,
2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */, 2FD9A1AE2EF0FA7100A7B53E /* Network.framework in Frameworks */,
2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */, 2FB975E02ED7F9AA00E64A87 /* EventKit.framework in Frameworks */,
2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */, 2FD1557B2ECAC5CC00233981 /* WebKit.framework in Frameworks */,
...@@ -67,7 +72,6 @@ ...@@ -67,7 +72,6 @@
2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */, 2F99420D2EA9C5F000622199 /* QuartzCore.framework in Frameworks */,
2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */, 2F8D09092E9605E200C764FF /* CoreGraphics.framework in Frameworks */,
2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */, 2FAEF09C2E8B7BD40086EDA3 /* Foundation.framework in Frameworks */,
2FAEF09A2E8B7BC80086EDA3 /* UIKit.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
...@@ -94,6 +98,8 @@ ...@@ -94,6 +98,8 @@
2FAEF0982E8B7BC80086EDA3 /* Frameworks */ = { 2FAEF0982E8B7BC80086EDA3 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2FBC22B62EF93BB400882B6F /* UIKit.framework */,
2FBC22B42EF93B8A00882B6F /* UniformTypeIdentifiers.framework */,
2FD027B02EEFD25100CF5C33 /* Network.framework */, 2FD027B02EEFD25100CF5C33 /* Network.framework */,
2FD9A0B72EF0E85B00A7B53E /* Network.framework */, 2FD9A0B72EF0E85B00A7B53E /* Network.framework */,
2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */, 2FB975DF2ED7F9AA00E64A87 /* EventKit.framework */,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "AccessAppointmentViewController.h" #import "AccessAppointmentViewController.h"
#import "AccessTransactionViewController.h" #import "AccessTransactionViewController.h"
#import "PullToRefresh.h"
// Custom Card // Custom Card
@interface SubmittedItemCell : UITableViewCell @interface SubmittedItemCell : UITableViewCell
...@@ -357,6 +358,11 @@ ...@@ -357,6 +358,11 @@
[self.tableView registerClass:[SubmittedItemCell class] forCellReuseIdentifier:@"SubmittedItemCell"]; [self.tableView registerClass:[SubmittedItemCell class] forCellReuseIdentifier:@"SubmittedItemCell"];
[self.tableView registerClass:[TransactionItemCell class] forCellReuseIdentifier:@"TransactionItemCell"]; [self.tableView registerClass:[TransactionItemCell class] forCellReuseIdentifier:@"TransactionItemCell"];
__weak typeof(self) weakSelf = self;
[self.tableView addPullToRefresh:^(UIRefreshControl *refresh) {
[weakSelf fetchAccessItem];
}];
[self.view addSubview:self.tableView]; [self.view addSubview:self.tableView];
...@@ -391,6 +397,7 @@ ...@@ -391,6 +397,7 @@
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData]; [self.tableView reloadData];
[self.tableView endPullToRefresh];
}); });
}]; }];
} }
......
// AddInfoViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AddInfoViewController : UIViewController
@property (nonatomic, strong) NSString *issueID;
@property (nonatomic, strong) NSString *planID;
@property (nonatomic, strong) NSString *status;
@property (nonatomic, strong) NSString *unitName;
@property (nonatomic, strong) NSString *reference;
@end
NS_ASSUME_NONNULL_END
// AddInfoViewController.mm
#import "AddInfoViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "APIClient.h"
#import "OfflineSyncManager.h"
#import "NetworkManager.h"
@interface AddInfoViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIDocumentPickerDelegate>
@property (nonatomic, strong) UIView *navBar;
@property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UILabel *referenceLabel;
@property (nonatomic, strong) UILabel *commentLabel;
@property (nonatomic, strong) UITextView *commentTextView;
@property (nonatomic, strong) UILabel *imageUploadLabel;
@property (nonatomic, strong) UIView *imageContainer;
@property (nonatomic, strong) UILabel *mediaUploadLabel;
@property (nonatomic, strong) UIView *mediaContainer;
@property (nonatomic, strong) UILabel *mediaRules;
@property (nonatomic, strong) UIButton *submitButton;
@property (nonatomic, strong) NSMutableArray<UIButton *> *uploadButtons;
@property (nonatomic, strong) NSMutableArray<UIImage *> *uploadedImages;
@property (nonatomic, assign) NSInteger activeUploadIndex;
@property (nonatomic, strong) UIButton *mediaUploadButton;
@property (nonatomic, strong) NSURL *selectedMediaURL;
@property (nonatomic, assign) BOOL isPickingMedia;
@end
@implementation AddInfoViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
self.uploadButtons = [NSMutableArray array];
self.uploadedImages = [NSMutableArray array];
self.activeUploadIndex = -1;
[self setupNavBar];
[self setupScrollView];
[self setupContent];
[self setupConstraints];
NSLog(@"📦 Data received by Add Info:\n issueID: %@\n planID: %@\n status: %@\n unitName: %@\n reference: %@",
self.issueID,
self.planID,
self.status,
self.unitName,
self.reference);
}
#pragma mark - UI Setup
- (void)setupNavBar {
self.navBar = [UIView new];
self.navBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
self.navBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.navBar];
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.left"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.backButton addTarget:self action:@selector(backTapped) forControlEvents:UIControlEventTouchUpInside];
[self.navBar addSubview:self.backButton];
self.titleLabel = [UILabel new];
self.titleLabel.text = @"Add Info";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.navBar addSubview:self.titleLabel];
}
- (void)setupScrollView {
self.scrollView = [UIScrollView new];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
self.contentView = [UIView new];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
}
- (void)setupContent {
self.referenceLabel = [UILabel new];
self.referenceLabel.text = self.reference ?: @"";
self.referenceLabel.font = [UIFont boldSystemFontOfSize:20];
self.referenceLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.commentLabel = [UILabel new];
self.commentLabel.text = @"* Comment";
self.commentLabel.font = [UIFont boldSystemFontOfSize:16];
self.commentLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.commentTextView = [UITextView new];
self.commentTextView.layer.borderWidth = 1.0;
self.commentTextView.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.commentTextView.layer.cornerRadius = 12;
self.commentTextView.font = [UIFont systemFontOfSize:15];
self.commentTextView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageUploadLabel = [UILabel new];
self.imageUploadLabel.text = @"Image Upload";
self.imageUploadLabel.font = [UIFont boldSystemFontOfSize:16];
self.imageUploadLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.imageContainer = [UIView new];
self.imageContainer.layer.borderWidth = 1.0;
self.imageContainer.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.imageContainer.layer.cornerRadius = 12;
self.imageContainer.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView *uploadImageStack = [[UIStackView alloc] init];
uploadImageStack.axis = UILayoutConstraintAxisHorizontal;
uploadImageStack.spacing = 12;
uploadImageStack.distribution = UIStackViewDistributionFillEqually;
uploadImageStack.translatesAutoresizingMaskIntoConstraints = NO;
for (NSInteger i = 0; i < 2; i++) {
UIButton *box = [self makeUploadBox];
box.tag = 1000 + i;
[self.uploadButtons addObject:box];
[uploadImageStack addArrangedSubview:box];
}
[self.imageContainer addSubview:uploadImageStack];
[NSLayoutConstraint activateConstraints:@[
[uploadImageStack.topAnchor constraintEqualToAnchor:self.imageContainer.topAnchor constant:12],
[uploadImageStack.leadingAnchor constraintEqualToAnchor:self.imageContainer.leadingAnchor constant:12],
[uploadImageStack.trailingAnchor constraintEqualToAnchor:self.imageContainer.trailingAnchor constant:-12],
[uploadImageStack.bottomAnchor constraintEqualToAnchor:self.imageContainer.bottomAnchor constant:-12],
]];
self.mediaUploadLabel = [UILabel new];
self.mediaUploadLabel.text = @"Media Upload";
self.mediaUploadLabel.font = [UIFont boldSystemFontOfSize:16];
self.mediaUploadLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.mediaContainer = [UIView new];
self.mediaContainer.layer.borderWidth = 1.0;
self.mediaContainer.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.mediaContainer.layer.cornerRadius = 12;
self.mediaContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.mediaUploadButton = [self makeUploadBox];
self.mediaUploadButton.tag = 2000; // distinct namespace
[self.mediaUploadButton setTitle:@"Choose Media" forState:UIControlStateNormal];
[self.mediaContainer addSubview:self.mediaUploadButton];
[NSLayoutConstraint activateConstraints:@[
[self.mediaUploadButton.topAnchor constraintEqualToAnchor:self.mediaContainer.topAnchor constant:12],
[self.mediaUploadButton.leadingAnchor constraintEqualToAnchor:self.mediaContainer.leadingAnchor constant:12],
[self.mediaUploadButton.trailingAnchor constraintEqualToAnchor:self.mediaContainer.trailingAnchor constant:-12],
[self.mediaUploadButton.bottomAnchor constraintEqualToAnchor:self.mediaContainer.bottomAnchor constant:-12],
]];
self.mediaRules = [UILabel new];
self.mediaRules.text = @"- Video duration should be 5 seconds or less.\n- Video file size should be nor more than 15MB.\n- Supported document format are .pdf, .docx, .xlsx, .csv.\n-Document file size should not be more than 15MB.";
self.mediaRules.font = [UIFont boldSystemFontOfSize:12];
self.mediaRules.translatesAutoresizingMaskIntoConstraints = NO;
self.submitButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.submitButton setTitle:@"SUBMIT" forState:UIControlStateNormal];
self.submitButton.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
[self.submitButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
self.submitButton.layer.cornerRadius = 8;
self.submitButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.submitButton addTarget:self action:@selector(submitTapped) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.referenceLabel];
[self.contentView addSubview:self.commentLabel];
[self.contentView addSubview:self.commentTextView];
[self.contentView addSubview:self.imageUploadLabel];
[self.contentView addSubview:self.imageContainer];
[self.contentView addSubview:self.mediaUploadLabel];
[self.contentView addSubview:self.mediaContainer];
[self.contentView addSubview:self.mediaRules];
[self.contentView addSubview:self.submitButton];
}
#pragma mark - Constraints
- (void)setupConstraints {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
// Nav bar
[NSLayoutConstraint activateConstraints:@[
[self.navBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.navBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.navBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.navBar.heightAnchor constraintEqualToConstant:48],
[self.backButton.leadingAnchor constraintEqualToAnchor:self.navBar.leadingAnchor constant:12],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.navBar.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.navBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.navBar.centerYAnchor],
]];
// Scroll view
[NSLayoutConstraint activateConstraints:@[
[self.scrollView.topAnchor constraintEqualToAnchor:self.navBar.bottomAnchor],
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[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],
]];
// Content
[NSLayoutConstraint activateConstraints:@[
[self.referenceLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:16],
[self.referenceLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.referenceLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.commentLabel.topAnchor constraintEqualToAnchor:self.referenceLabel.bottomAnchor constant:16],
[self.commentLabel.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.commentTextView.topAnchor constraintEqualToAnchor:self.commentLabel.bottomAnchor constant:12],
[self.commentTextView.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.commentTextView.trailingAnchor constraintEqualToAnchor:self.referenceLabel.trailingAnchor],
[self.commentTextView.heightAnchor constraintEqualToConstant:220],
[self.imageUploadLabel.topAnchor constraintEqualToAnchor:self.commentTextView.bottomAnchor constant:24],
[self.imageUploadLabel.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.imageContainer.topAnchor constraintEqualToAnchor:self.imageUploadLabel.bottomAnchor constant:12],
[self.imageContainer.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.imageContainer.trailingAnchor constraintEqualToAnchor:self.referenceLabel.trailingAnchor],
[self.imageContainer.heightAnchor constraintEqualToConstant:96],
[self.mediaUploadLabel.topAnchor constraintEqualToAnchor:self.imageContainer.bottomAnchor constant:24],
[self.mediaUploadLabel.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.mediaContainer.topAnchor constraintEqualToAnchor:self.mediaUploadLabel.bottomAnchor constant:12],
[self.mediaContainer.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.mediaContainer.trailingAnchor constraintEqualToAnchor:self.referenceLabel.trailingAnchor],
[self.mediaContainer.heightAnchor constraintEqualToConstant:96],
[self.mediaRules.topAnchor constraintEqualToAnchor:self.mediaContainer.bottomAnchor constant:12],
[self.mediaRules.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.submitButton.topAnchor constraintEqualToAnchor:self.mediaRules.bottomAnchor constant:40],
[self.submitButton.leadingAnchor constraintEqualToAnchor:self.referenceLabel.leadingAnchor],
[self.submitButton.trailingAnchor constraintEqualToAnchor:self.referenceLabel.trailingAnchor],
[self.submitButton.heightAnchor constraintEqualToConstant:48],
[self.submitButton.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-24],
]];
}
#pragma mark - Upload Buttons
- (UIButton *)makeUploadBox {
UIButton *upload = [UIButton buttonWithType:UIButtonTypeCustom]; // ✅ not System
upload.translatesAutoresizingMaskIntoConstraints = NO;
upload.layer.borderWidth = 1.0;
upload.layer.cornerRadius = 8;
upload.layer.borderColor = [UIColor lightGrayColor].CGColor;
[upload setTitle:@"Upload" forState:UIControlStateNormal];
[upload setTitleColor:[UIColor colorWithRed:0/255.0 green:109/255.0 blue:141/255.0 alpha:1] forState:UIControlStateNormal];
[upload setImage:[UIImage systemImageNamed:@"camera"] forState:UIControlStateNormal];
upload.tintColor = [UIColor colorWithRed:0/255.0 green:109/255.0 blue:141/255.0 alpha:1];
upload.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 8);
upload.titleLabel.font = [UIFont boldSystemFontOfSize:14];
[upload addTarget:self action:@selector(handleUploadTap:) forControlEvents:UIControlEventTouchUpInside];
return upload;
}
- (void)handleUploadTap:(UIButton *)sender {
// MEDIA UPLOAD
if (sender.tag == 2000) {
[self presentMediaPickerSheet];
return;
}
NSInteger index = sender.tag - 1000;
self.activeUploadIndex = index;
UIAlertController *sheet = [UIAlertController alertControllerWithTitle:@"Upload Image"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[sheet addAction:[UIAlertAction actionWithTitle:@"Camera"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openImagePickerForIndex:index source:UIImagePickerControllerSourceTypeCamera];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Gallery"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openImagePickerForIndex:index source:UIImagePickerControllerSourceTypePhotoLibrary];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:sheet animated:YES completion:nil];
}
- (void)openImagePickerForIndex:(NSInteger)index source:(UIImagePickerControllerSourceType)source {
if (![UIImagePickerController isSourceTypeAvailable:source]) return;
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = source;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)presentMediaPickerSheet {
UIAlertController *sheet =
[UIAlertController alertControllerWithTitle:@"Upload Media"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[sheet addAction:[UIAlertAction actionWithTitle:@"Video"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openVideoPicker];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Document"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openDocumentPicker];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:sheet animated:YES completion:nil];
}
- (void)openVideoPicker {
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) return;
UIImagePickerController *picker = [UIImagePickerController new];
picker.delegate = self;
picker.videoMaximumDuration = 5.0;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// Support both iOS 14+ and older
if (@available(iOS 14.0, *)) {
picker.mediaTypes = @[(NSString *)UTTypeMovie.identifier];
} else {
picker.mediaTypes = @[(NSString *)kUTTypeMovie];
}
[self presentViewController:picker animated:YES completion:nil];
}
- (void)openDocumentPicker {
NSArray *types = @[
@"com.adobe.pdf",
@"org.openxmlformats.wordprocessingml.document",
@"org.openxmlformats.spreadsheetml.sheet",
@"public.comma-separated-values-text"
];
UIDocumentPickerViewController *picker =
[[UIDocumentPickerViewController alloc] initWithDocumentTypes:types
inMode:UIDocumentPickerModeImport];
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)handleSelectedMediaURL:(NSURL *)url {
if (![self isFileSizeValid:url]) {
// [self showAlert:@"File too large (max 15MB)"];
NSLog(@"File size too big");
return;
}
self.selectedMediaURL = url;
[self.mediaUploadButton setTitle:url.lastPathComponent
forState:UIControlStateNormal];
[self.mediaUploadButton setImage:[UIImage systemImageNamed:@"doc.fill"]
forState:UIControlStateNormal];
}
#pragma mark - UIDocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
NSURL *url = urls.firstObject;
if (!url) return;
[self handleSelectedMediaURL:url];
}
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
NSString *mediaType = info[UIImagePickerControllerMediaType];
// Check video
BOOL isVideo = NO;
if (@available(iOS 14.0, *)) {
isVideo = [mediaType isEqualToString:UTTypeMovie.identifier];
} else {
isVideo = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
}
if (isVideo) {
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
[self handleSelectedMediaURL:videoURL];
[picker dismissViewControllerAnimated:YES completion:nil];
return;
}
// Otherwise treat as image
UIImage *image = info[UIImagePickerControllerOriginalImage];
image = [self resizedImage:image maxDimension:1280];
NSInteger index = self.activeUploadIndex;
if (index < self.uploadedImages.count) {
self.uploadedImages[index] = image;
} else {
[self.uploadedImages addObject:image];
}
UIButton *button = self.uploadButtons[index];
if ([button isKindOfClass:[UIButton class]]) {
[button setTitle:nil forState:UIControlStateNormal];
[button setImage:image forState:UIControlStateNormal];
button.imageView.contentMode = UIViewContentModeScaleAspectFill;
button.imageView.clipsToBounds = YES;
button.layer.masksToBounds = YES;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
button.imageEdgeInsets = UIEdgeInsetsZero;
[self addRemoveButtonToUpload:button atIndex:index];
}
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)addRemoveButtonToUpload:(UIButton *)uploadBtn atIndex:(NSInteger)index {
// Remove existing remove button if any (avoid duplicates)
for (UIView *sub in uploadBtn.subviews) {
if (sub.tag == 9000) {
[sub removeFromSuperview];
}
}
// Create small X button
UIButton *removeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
removeBtn.tag = 9000;
removeBtn.translatesAutoresizingMaskIntoConstraints = NO;
[removeBtn setImage:[UIImage systemImageNamed:@"xmark.circle.fill"] forState:UIControlStateNormal];
removeBtn.tintColor = [UIColor whiteColor];
removeBtn.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
removeBtn.layer.cornerRadius = 10;
removeBtn.clipsToBounds = YES;
removeBtn.contentEdgeInsets = UIEdgeInsetsMake(4, 4, 4, 4);
removeBtn.accessibilityIdentifier = [NSString stringWithFormat:@"remove_%ld", (long)index];
[removeBtn addTarget:self action:@selector(handleRemoveImage:) forControlEvents:UIControlEventTouchUpInside];
[uploadBtn addSubview:removeBtn];
// Position in top-right corner
[NSLayoutConstraint activateConstraints:@[
[removeBtn.topAnchor constraintEqualToAnchor:uploadBtn.topAnchor constant:4],
[removeBtn.trailingAnchor constraintEqualToAnchor:uploadBtn.trailingAnchor constant:-4],
[removeBtn.widthAnchor constraintEqualToConstant:20],
[removeBtn.heightAnchor constraintEqualToConstant:20],
]];
}
- (void)handleRemoveImage:(UIButton *)sender {
UIView *uploadBtn = sender.superview;
if (![uploadBtn isKindOfClass:[UIButton class]]) return;
NSInteger index = ((UIButton *)uploadBtn).tag - 1000;
if (index < self.uploadedImages.count) {
self.uploadedImages[index] = (id)[NSNull null];
}
// Reset upload button
UIButton *button = (UIButton *)uploadBtn;
[button setImage:[UIImage systemImageNamed:@"camera"] forState:UIControlStateNormal];
[button setTitle:@"Upload" forState:UIControlStateNormal];
[button setTitleColor:[UIColor colorWithRed:0/255.0 green:109/255.0 blue:141/255.0 alpha:1] forState:UIControlStateNormal];
button.tintColor = [UIColor colorWithRed:0/255.0 green:109/255.0 blue:141/255.0 alpha:1];
button.imageView.contentMode = UIViewContentModeCenter;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 8);
// Remove the small "x" button
[sender removeFromSuperview];
}
#pragma mark - Actions
- (void)backTapped {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)submitTapped {
if (self.commentTextView.text.length == 0) {
[self showAlertWithTitle:@"Missing Comment"
message:@"Please enter a comment."];
return;
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Confirm Submission"
message:@"Are you sure you want to submit this issue?"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"Submit"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self performSubmit];
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
[alert addAction:cancelAction];
[alert addAction:confirmAction];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)performSubmit {
NSArray<UIImage *> *images = [self validImages];
BOOL hasImages = images.count > 0;
BOOL hasMedia = (self.selectedMediaURL != nil);
NSLog(@"📤 Submitting AddInfo | images=%lu | media=%@",
(unsigned long)images.count,
self.selectedMediaURL.lastPathComponent);
NSString *mediaType = nil;
if (hasMedia) {
NSString *ext = self.selectedMediaURL.pathExtension.lowercaseString;
if ([@[@"mp4", @"mov"] containsObject:ext]) {
mediaType = @"Video";
} else {
mediaType = @"Document";
}
}
if (![NetworkManager.sharedManager isConnected]) {
[[OfflineSyncManager shared] queueAddInfo:self.issueID
remarks:self.commentTextView.text
images:images
mediaURL:self.selectedMediaURL
mediaType:mediaType];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
return;
}
if (!hasImages && !hasMedia) {
// ✅ JSON ONLY
[APIClient submitAddInfo:self.issueID
remarks:self.commentTextView.text
completion:^(NSDictionary *response, NSError *error) {
[self handleSubmitResponse:response error:error];
}];
return;
}
[APIClient uploadAddInfo:self.issueID
remarks:self.commentTextView.text
images:images
mediaURL:self.selectedMediaURL
mediaType:mediaType
completion:^(NSDictionary *response, NSError *error) {
[self handleSubmitResponse:response error:error];
}];
}
- (void)handleSubmitResponse:(NSDictionary *)response error:(NSError *)error {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self showAlertWithTitle:@"Submission Failed"
message:error.localizedDescription ?: @"Unknown error"];
return;
}
NSDictionary *appData = response[@"AppData"];
if ([appData[@"status"] isEqualToString:@"success"]) {
UIAlertController *success =
[UIAlertController alertControllerWithTitle:@"Success"
message:@"Info submitted successfully."
preferredStyle:UIAlertControllerStyleAlert];
[success addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}]];
[self presentViewController:success animated:YES completion:nil];
} else {
NSString *msg = appData[@"message"] ?: @"Server error";
[self showAlertWithTitle:@"Submission Error" message:msg];
}
});
}
#pragma mark - Helpers
- (UIImage *)resizedImage:(UIImage *)image maxDimension:(CGFloat)maxDim {
CGSize size = image.size;
CGFloat scale = MIN(maxDim / size.width, maxDim / size.height);
if (scale >= 1) return image;
CGSize newSize = CGSizeMake(size.width * scale, size.height * scale);
UIGraphicsBeginImageContextWithOptions(newSize, YES, 1.0);
[image drawInRect:(CGRect){CGPointZero, newSize}];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (BOOL)isFileSizeValid:(NSURL *)url {
NSNumber *size;
[url getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
return size.longLongValue <= (15 * 1024 * 1024);
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
- (NSArray<UIImage *> *)validImages {
NSMutableArray *valid = [NSMutableArray array];
for (id obj in self.uploadedImages) {
if ([obj isKindOfClass:[UIImage class]]) {
[valid addObject:obj];
}
}
return valid;
}
@end
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
#import "APIClient.h" #import "APIClient.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "OfflineSyncManager.h"
#import "NetworkManager.h"
@interface AddIssueDetailsViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate> @interface AddIssueDetailsViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIView *headerView; @property (nonatomic, strong) UIView *headerView;
...@@ -20,6 +22,7 @@ ...@@ -20,6 +22,7 @@
@property (nonatomic, assign) BOOL isEditMode; @property (nonatomic, assign) BOOL isEditMode;
@property (nonatomic, strong) NSMutableArray *existingImages; @property (nonatomic, strong) NSMutableArray *existingImages;
@property (nonatomic, strong) NSMutableArray *addedImages; @property (nonatomic, strong) NSMutableArray *addedImages;
@end @end
@implementation AddIssueDetailsViewController @implementation AddIssueDetailsViewController
...@@ -912,6 +915,20 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> * ...@@ -912,6 +915,20 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
NSLog(@"📦 Sending UpdateIssue with data: %@", updateData); NSLog(@"📦 Sending UpdateIssue with data: %@", updateData);
if (![NetworkManager.sharedManager isConnected]) {
[[OfflineSyncManager shared] queueUpdateIssue:updateData
images:self.addedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
[APIClient submitUpdateIssue:updateData [APIClient submitUpdateIssue:updateData
images:self.addedImages images:self.addedImages
completion:^(NSDictionary * _Nullable response, NSError * _Nullable error) { completion:^(NSDictionary * _Nullable response, NSError * _Nullable error) {
...@@ -971,6 +988,22 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> * ...@@ -971,6 +988,22 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
@"os": @"AND" @"os": @"AND"
}; };
NSLog(@"NetworkManager.sharedManager isConnected = %@",
[NetworkManager.sharedManager isConnected] ? @"YES" : @"NO");
if (![NetworkManager.sharedManager isConnected]) {
[[OfflineSyncManager shared] queueAddIssue:issueData
images:self.uploadedImages];
[self showAlertWithTitle:@"Saved Offline"
message:@"This will be synced when you're back online."];
UIViewController *presenter = self.presentingViewController;
if (presenter.presentingViewController) {
[presenter.presentingViewController dismissViewControllerAnimated:YES completion:nil];
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
[APIClient submitAddIssue:issueData [APIClient submitAddIssue:issueData
images:self.uploadedImages images:self.uploadedImages
completion:^(NSDictionary *response, NSError *error) { completion:^(NSDictionary *response, NSError *error) {
...@@ -1014,7 +1047,6 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> * ...@@ -1014,7 +1047,6 @@ didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *
} }
} }
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message { - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:message message:message
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#import "APIClient.h" #import "APIClient.h"
#import "CalendarView.h" #import "CalendarView.h"
#import "NSDate+CalendarHelpers.h" #import "NSDate+CalendarHelpers.h"
#import "AddInfoViewController.h"
@interface HistoryViewController () <UITableViewDelegate, UITableViewDataSource> @interface HistoryViewController () <UITableViewDelegate, UITableViewDataSource>
...@@ -78,7 +79,12 @@ ...@@ -78,7 +79,12 @@
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerView addSubview:self.titleLabel]; [self.headerView addSubview:self.titleLabel];
// need to make addInfo eventually self.addInfoButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.addInfoButton setImage:[UIImage systemImageNamed:@"document.badge.plus"] forState:UIControlStateNormal];
self.addInfoButton.tintColor = UIColor.whiteColor;
self.addInfoButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.addInfoButton addTarget:self action:@selector(addInfoTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerView addSubview:self.addInfoButton];
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
[self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor], [self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
...@@ -89,6 +95,9 @@ ...@@ -89,6 +95,9 @@
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:12], [self.backButton.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor constant:12],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor], [self.backButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.addInfoButton.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor constant:-12],
[self.addInfoButton.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor], [self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerView.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor], [self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerView.centerYAnchor],
]]; ]];
...@@ -434,6 +443,14 @@ ...@@ -434,6 +443,14 @@
- (void)addInfoTapped { - (void)addInfoTapped {
NSLog(@"➕ Add Info tapped"); NSLog(@"➕ Add Info tapped");
AddInfoViewController *vc = [[AddInfoViewController alloc] init];
vc.issueID = self.issueID;
vc.planID = self.planID;
vc.status = self.status;
vc.unitName = self.unitName;
vc.reference = self.reference;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
} }
#pragma mark - helper #pragma mark - helper
......
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
// Title // Title
self.titleLabel = [[UILabel alloc] init]; self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Issues"; self.titleLabel.text = @"Issue(s)";
self.titleLabel.textColor = UIColor.whiteColor; self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont boldSystemFontOfSize:20]; self.titleLabel.font = [UIFont boldSystemFontOfSize:20];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO; self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
......
...@@ -44,10 +44,17 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -44,10 +44,17 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)fetchHistory:(NSString *)issueID + (void)fetchHistory:(NSString *)issueID
completion:(void (^)(NSDictionary *data, NSError *error))completion; completion:(void (^)(NSDictionary *data, NSError *error))completion;
+(void)fetchAddInfo:(NSString *)issueID +(void)submitAddInfo:(NSString *)issueID
remarks:(NSString *)remarks remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion; completion:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)uploadAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL *)mediaURL
mediaType:(NSString *)mediaType
completion:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion; + (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion;
+ (void)fetchVoidIssue:(id)issueID + (void)fetchVoidIssue:(id)issueID
......
...@@ -390,10 +390,6 @@ ...@@ -390,10 +390,6 @@
appendFormField(@"data[comment]", updateData[@"comment"]); appendFormField(@"data[comment]", updateData[@"comment"]);
appendFormField(@"data[os]", updateData[@"os"]); appendFormField(@"data[os]", updateData[@"os"]);
// 🔹 Add `information` field (as JSON string)
// NSString *deviceInfo = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
// appendFormField(@"information", deviceInfo);
// 🔹 Append image files // 🔹 Append image files
for (int i = 0; i < images.count; i++) { for (int i = 0; i < images.count; i++) {
id maybeImage = images[i]; id maybeImage = images[i];
...@@ -984,7 +980,7 @@ ...@@ -984,7 +980,7 @@
[task resume]; [task resume];
} }
+(void)fetchAddInfo:(NSString *)issueID +(void)submitAddInfo:(NSString *)issueID
remarks:(NSString *)remarks remarks:(NSString *)remarks
completion:(void (^)(NSDictionary *data, NSError *error))completion { completion:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL // ✅ URL
...@@ -1061,6 +1057,124 @@ ...@@ -1061,6 +1057,124 @@
[task resume]; [task resume];
} }
+ (void)uploadAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL *)mediaURL
mediaType:(NSString *)mediaType
completion:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/addInfo?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 🧱 Multipart body
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSMutableData *body = [NSMutableData data];
// Helper for adding form fields
void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", value]
dataUsingEncoding:NSUTF8StringEncoding]];
};
// 🔹 Append OS info
appendFormField(@"data[issue_id]", issueID ?: @"");
appendFormField(@"data[remarks]", remarks ?: @"");
appendFormField(@"data[os]", @"AND");
// 🔹 Append images
if (images.count > 0) {
for (int i = 0; i < images.count; i++) {
id maybeImage = images[i];
if (![maybeImage isKindOfClass:[UIImage class]]) {
NSLog(@"⚠️ Skipping non-UIImage at index %d: %@", i, maybeImage);
continue; // skip invalid entries
}
UIImage *image = (UIImage *)maybeImage;
NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
if (!imageData) continue;
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Disposition: form-data; name=\"data[image][%d]\"; filename=\"image%d.jpg\"\r\n", i, i]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:imageData];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
}
// 🔹 Append media file if provided
if (mediaURL) {
NSData *fileData = [NSData dataWithContentsOfURL:mediaURL];
if (fileData) {
NSString *ext = mediaURL.pathExtension.lowercaseString;
NSString *mimeType = @"application/octet-stream";
if ([mediaType isEqualToString:@"Video"]) {
mimeType = [NSString stringWithFormat:@"video/%@", ext];
}
NSInteger index = images.count;
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Disposition: form-data; name=\"data[image][%ld]\"; filename=\"addinfo.%@\"\r\n",
(long)index, ext]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:
@"Content-Type: %@\r\n\r\n", mimeType]
dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:fileData];
[body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
}
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = body;
// Debug
NSLog(@"📦 [AddIssue] Uploading %lu image(s)", (unsigned long)images.count);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ [AddIssue] Network error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(nil, error); });
return;
}
NSError *jsonErr;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
if (jsonErr) {
NSString *raw = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"⚠️ [AddIssue] JSON parse error: %@\nRaw response: %@", jsonErr, raw);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(nil, jsonErr); });
return;
}
NSLog(@"✅ [AddIssue] Response: %@", json);
dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(json, nil); });
}];
[task resume];
}
+ (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion { + (void)fetchIssues:(void (^)(NSDictionary *data, NSError *error))completion {
// ✅ URL // ✅ URL
NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSString *urlString = @"https://kitadev.commudesk.com/api/owner/issue/getIssue?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
......
...@@ -45,17 +45,25 @@ ...@@ -45,17 +45,25 @@
nw_path_monitor_set_queue(self.monitor, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); nw_path_monitor_set_queue(self.monitor, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t path) { nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t path) {
self.connected = nw_path_get_status(path) == nw_path_status_satisfied;
NSLog(@"[NetworkManager] Internet connectivity: %@", self.connected ? @"YES" : @"NO");
// Post notification for all listeners BOOL hasInternet = (nw_path_get_status(path) == nw_path_status_satisfied);
// BOOL isWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi);
// BOOL isCellular = nw_path_uses_interface_type(path, nw_interface_type_cellular);
// BOOL isEthernet = nw_path_uses_interface_type(path, nw_interface_type_wired);
self.connected = hasInternet;
// && (isWiFi || isCellular || isEthernet);
// note that simulator might not report correct status
NSLog(@"[NetworkManager] Connected = %@", self.connected ? @"YES" : @"NO");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkStatusChanged" [[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkStatusChanged"
object:nil object:nil
userInfo:@{@"connected": @(self.connected)}]; userInfo:@{@"connected": @(self.connected)}];
}); });
}); });
nw_path_monitor_start(self.monitor); nw_path_monitor_start(self.monitor);
} }
......
// OfflineSyncManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OfflineSyncManager : NSObject
+ (instancetype)shared;
// Queue actions
- (void)queueAddIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images;
- (void)queueUpdateIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images;
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType;
// Sync
- (void)processQueueIfOnline;
// Helpers
- (BOOL)hasPendingItems;
@end
NS_ASSUME_NONNULL_END
// OfflineSyncManager.mm
#import "OfflineSyncManager.h"
#import "LocalStorage.h"
#import "NetworkManager.h"
#import "APIClient.h"
static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
@implementation OfflineSyncManager
#pragma mark - Singleton
+ (instancetype)shared {
static OfflineSyncManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[OfflineSyncManager alloc] init];
[instance setupNetworkListener];
});
return instance;
}
#pragma mark - Setup
- (void)setupNetworkListener {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(networkStatusChanged:)
name:@"NetworkStatusChanged"
object:nil];
}
- (void)networkStatusChanged:(NSNotification *)note {
BOOL connected = [note.userInfo[@"connected"] boolValue];
if (connected) {
[self processQueueIfOnline];
}
}
#pragma mark - Queue Helpers (CRITICAL)
- (NSMutableArray *)safeLoadQueue {
id stored = [LocalStorage loadArrayForKey:kOfflineQueueKey];
if (![stored isKindOfClass:[NSArray class]]) {
NSLog(@"🧹 [OfflineSync] Queue corrupted — resetting");
[LocalStorage removeObjectForKey:kOfflineQueueKey];
return [NSMutableArray array];
}
NSMutableArray *validItems = [NSMutableArray array];
for (id obj in stored) {
if ([obj isKindOfClass:[NSDictionary class]]) {
[validItems addObject:obj];
}
}
return validItems;
}
- (void)saveQueue:(NSArray *)queue {
[LocalStorage saveArray:queue forKey:kOfflineQueueKey];
}
#pragma mark - Public Queue APIs
- (void)queueAddIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"ADD_ISSUE"
payload:payload
images:images];
}
- (void)queueUpdateIssue:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
[self enqueueAction:@"UPDATE_ISSUE"
payload:payload
images:images];
}
- (void)queueAddInfo:(NSString *)issueID
remarks:(NSString *)remarks
images:(NSArray<UIImage *> *)images
mediaURL:(NSURL * _Nullable)mediaURL
mediaType:(NSString * _Nullable)mediaType {
NSMutableArray *queue = [self safeLoadQueue];
NSArray *imagePaths = [self saveImagesToDisk:images];
NSString *mediaPath = [self saveMediaToDisk:mediaURL];
NSMutableDictionary *item = [NSMutableDictionary dictionary];
item[@"id"] = NSUUID.UUID.UUIDString;
item[@"type"] = @"ADD_INFO";
item[@"issue_id"] = issueID ?: @"";
item[@"remarks"] = remarks ?: @"";
item[@"imagePaths"] = imagePaths ?: @[];
item[@"timestamp"] = @((NSInteger)[NSDate date].timeIntervalSince1970);
if (mediaPath) item[@"mediaPath"] = mediaPath;
if (mediaType) item[@"mediaType"] = mediaType;
[queue addObject:item];
[self saveQueue:queue];
NSLog(@"📥 [OfflineSync] Queued ADD_INFO (%lu)", (unsigned long)queue.count);
}
#pragma mark - Core Queue Add
- (void)enqueueAction:(NSString *)type
payload:(NSDictionary *)payload
images:(NSArray<UIImage *> *)images {
NSMutableArray *queue = [self safeLoadQueue];
NSArray *imagePaths = [self saveImagesToDisk:images];
NSDictionary *item = @{
@"id": NSUUID.UUID.UUIDString,
@"type": type,
@"payload": payload ?: @{},
@"imagePaths": imagePaths ?: @[],
@"timestamp": @((NSInteger)[NSDate date].timeIntervalSince1970)
};
[queue addObject:item];
[self saveQueue:queue];
NSLog(@"📥 [OfflineSync] Queued %@ (%lu)", type, (unsigned long)queue.count);
}
#pragma mark - Processing Queue
- (void)processQueueIfOnline {
if (![NetworkManager.sharedManager isConnected]) {
return;
}
NSMutableArray *queue = [self safeLoadQueue];
if (queue.count == 0) return;
NSDictionary *item = queue.firstObject;
if (![item isKindOfClass:[NSDictionary class]]) {
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
return;
}
[self processItem:item completion:^(BOOL success) {
if (success) {
[queue removeObjectAtIndex:0];
[self saveQueue:queue];
[self processQueueIfOnline];
}
}];
}
- (void)processItem:(NSDictionary *)item
completion:(void (^)(BOOL success))completion {
NSString *type = item[@"type"] ?: @"";
if ([type isEqualToString:@"ADD_INFO"]) {
NSArray *images = [self loadImagesFromPaths:item[@"imagePaths"]];
NSURL *mediaURL = item[@"mediaPath"] ? [NSURL fileURLWithPath:item[@"mediaPath"]] : nil;
BOOL hasImages = images.count > 0;
BOOL hasMedia = mediaURL != nil;
if (!hasImages && !hasMedia) {
[APIClient submitAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
completion:^(NSDictionary *r, NSError *e) {
completion(e == nil);
}];
} else {
[APIClient uploadAddInfo:item[@"issue_id"]
remarks:item[@"remarks"]
images:images
mediaURL:mediaURL
mediaType:item[@"mediaType"]
completion:^(NSDictionary *r, NSError *e) {
completion(e == nil);
}];
}
return;
}
NSLog(@"⚠️ Unknown offline type: %@", type);
completion(NO);
}
#pragma mark - Disk Handling
- (NSArray *)saveImagesToDisk:(NSArray<UIImage *> *)images {
NSMutableArray *paths = [NSMutableArray array];
if (![images isKindOfClass:[NSArray class]]) return paths;
NSString *dir = [self offlineDirectory];
for (id obj in images) {
if (![obj isKindOfClass:[UIImage class]]) continue;
NSString *path = [dir stringByAppendingPathComponent:
[NSString stringWithFormat:@"%@.jpg", NSUUID.UUID.UUIDString]];
NSData *data = UIImageJPEGRepresentation(obj, 0.8);
if (data && [data writeToFile:path atomically:YES]) {
[paths addObject:path];
}
}
return paths;
}
- (NSString *)saveMediaToDisk:(NSURL *)url {
if (!url) return nil;
NSString *dir = [self offlineDirectory];
NSString *path = [dir stringByAppendingPathComponent:
[NSString stringWithFormat:@"%@.%@",
NSUUID.UUID.UUIDString, url.pathExtension]];
NSData *data = [NSData dataWithContentsOfURL:url];
if ([data writeToFile:path atomically:YES]) {
return path;
}
return nil;
}
- (NSArray *)loadImagesFromPaths:(NSArray *)paths {
NSMutableArray *images = [NSMutableArray array];
for (NSString *path in paths) {
UIImage *img = [UIImage imageWithContentsOfFile:path];
if (img) [images addObject:img];
}
return images;
}
- (NSString *)offlineDirectory {
NSString *dir = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
dir = [dir stringByAppendingPathComponent:@"offline_files"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dir]) {
[[NSFileManager defaultManager] createDirectoryAtPath:dir
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
return dir;
}
#pragma mark - Status
- (BOOL)hasPendingItems {
return ([self safeLoadQueue].count > 0);
}
@end
// PullToRefresh.h
#import <UIKit/UIKit.h>
typedef void (^PullToRefreshAction)(UIRefreshControl *refreshControl);
@interface UIScrollView (PullToRefresh)
- (UIRefreshControl *)addPullToRefresh:(PullToRefreshAction)action;
- (void)endPullToRefresh;
@end
// PullToRefresh.mm
#import "PullToRefresh.h"
#import <objc/runtime.h>
static char kRefreshControlKey;
static char kRefreshActionKey;
@implementation UIScrollView (PullToRefresh)
- (UIRefreshControl *)addPullToRefresh:(PullToRefreshAction)action {
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
objc_setAssociatedObject(self,
&kRefreshActionKey,
action,
OBJC_ASSOCIATION_COPY_NONATOMIC);
[refreshControl addTarget:self
action:@selector(handlePullToRefresh:)
forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
objc_setAssociatedObject(self,
&kRefreshControlKey,
refreshControl,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return refreshControl;
}
- (void)handlePullToRefresh:(UIRefreshControl *)sender {
PullToRefreshAction action =
objc_getAssociatedObject(self, &kRefreshActionKey);
if (action) {
action(sender);
}
}
- (void)endPullToRefresh {
UIRefreshControl *refreshControl =
objc_getAssociatedObject(self, &kRefreshControlKey);
if (refreshControl.isRefreshing) {
[refreshControl endRefreshing];
}
}
@end
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