Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
weihan-plugin
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Wei Han
weihan-plugin
Commits
1511a7e0
Commit
1511a7e0
authored
Jan 13, 2026
by
Wei Han
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
code update
image caching
parent
cf0ca12f
Show whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
297 additions
and
221 deletions
+297
-221
Contents.json
framework/QmsPluginFramework/Images.xcassets/Contents.json
+0
-0
Contents.json
...mework/Images.xcassets/img_overlay.imageset/Contents.json
+1
-1
img_overlay_projdashboard3x.png
...sets/img_overlay.imageset/img_overlay_projdashboard3x.png
+0
-0
Contents.json
...rk/Images.xcassets/img_placeholder.imageset/Contents.json
+21
-0
image_placeholder.png
...s.xcassets/img_placeholder.imageset/image_placeholder.png
+0
-0
project.pbxproj
...ginFramework/QmsPluginFramework.xcodeproj/project.pbxproj
+4
-0
AccessTransactionViewController.mm
...inFramework/AccessItem/AccessTransactionViewController.mm
+13
-28
AppointmentConfirmationViewController.mm
...work/Appointment/AppointmentConfirmationViewController.mm
+2
-10
AppointmentDetailsViewController.mm
...Framework/Appointment/AppointmentDetailsViewController.mm
+3
-13
DashboardViewController.mm
...k/QmsPluginFramework/Dashboard/DashboardViewController.mm
+27
-31
GeneralInfoViewController.mm
...QmsPluginFramework/Dashboard/GeneralInfoViewController.mm
+5
-8
ProjectViewController.mm
...ork/QmsPluginFramework/Dashboard/ProjectViewController.mm
+14
-24
DetailsViewController.mm
...msPluginFramework/Issue/AddIssue/DetailsViewController.mm
+2
-4
PlanViewController.mm
...k/QmsPluginFramework/Issue/AddIssue/PlanViewController.mm
+17
-32
HistoryViewController.mm
...amework/QmsPluginFramework/Issue/HistoryViewController.mm
+8
-11
IssueDetailViewController.mm
...ork/QmsPluginFramework/Issue/IssueDetailViewController.mm
+9
-26
IssuesViewController.mm
...ramework/QmsPluginFramework/Issue/IssuesViewController.mm
+5
-11
WithdrawIssuesViewController.mm
.../QmsPluginFramework/Issue/WithdrawIssuesViewController.mm
+3
-10
SyncScreenViewController.mm
...ework/QmsPluginFramework/Sync/SyncScreenViewController.mm
+1
-1
APIClient.mm
...inFramework/QmsPluginFramework/Utilities/API/APIClient.mm
+0
-8
ImageCacheHelper.h
...PluginFramework/Utilities/ImageCaching/ImageCacheHelper.h
+18
-0
ImageCacheHelper.mm
...luginFramework/Utilities/ImageCaching/ImageCacheHelper.mm
+117
-0
OfflineSyncManager.mm
...uginFramework/Utilities/OfflineSync/OfflineSyncManager.mm
+27
-3
No files found.
framework/QmsPluginFramework/
QmsPluginFramework/QmsPluginAsset
s.xcassets/Contents.json
→
framework/QmsPluginFramework/
Image
s.xcassets/Contents.json
View file @
1511a7e0
File moved
framework/QmsPluginFramework/
QmsPluginFramework/QmsPluginAssets.xcassets/O
verlay.imageset/Contents.json
→
framework/QmsPluginFramework/
Images.xcassets/img_o
verlay.imageset/Contents.json
View file @
1511a7e0
{
{
"images"
:
[
"images"
:
[
{
{
"filename"
:
"
overlay
.png"
,
"filename"
:
"
img_overlay_projdashboard3x
.png"
,
"idiom"
:
"universal"
,
"idiom"
:
"universal"
,
"scale"
:
"1x"
"scale"
:
"1x"
},
},
...
...
framework/QmsPluginFramework/
QmsPluginFramework/QmsPluginAssets.xcassets/Overlay.imageset/overlay
.png
→
framework/QmsPluginFramework/
Images.xcassets/img_overlay.imageset/img_overlay_projdashboard3x
.png
View file @
1511a7e0
File moved
framework/QmsPluginFramework/Images.xcassets/img_placeholder.imageset/Contents.json
0 → 100644
View file @
1511a7e0
{
"images"
:
[
{
"filename"
:
"image_placeholder.png"
,
"idiom"
:
"universal"
,
"scale"
:
"1x"
},
{
"idiom"
:
"universal"
,
"scale"
:
"2x"
},
{
"idiom"
:
"universal"
,
"scale"
:
"3x"
}
],
"info"
:
{
"author"
:
"xcode"
,
"version"
:
1
}
}
framework/QmsPluginFramework/Images.xcassets/img_placeholder.imageset/image_placeholder.png
0 → 100644
View file @
1511a7e0
1.36 KB
framework/QmsPluginFramework/QmsPluginFramework.xcodeproj/project.pbxproj
View file @
1511a7e0
...
@@ -19,6 +19,7 @@
...
@@ -19,6 +19,7 @@
2FC779BD2F0E33410002A1D4
/* MobileCoreServices.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
2FC779BC2F0E33410002A1D4
/* MobileCoreServices.framework */
;
};
2FC779BD2F0E33410002A1D4
/* MobileCoreServices.framework in Frameworks */
=
{
isa
=
PBXBuildFile
;
fileRef
=
2FC779BC2F0E33410002A1D4
/* MobileCoreServices.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 */
;
};
2FDE729C2F15C48100C471A0
/* Images.xcassets in Resources */
=
{
isa
=
PBXBuildFile
;
fileRef
=
2FDE729B2F15C48100C471A0
/* Images.xcassets */
;
};
/* End PBXBuildFile section */
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
/* Begin PBXContainerItemProxy section */
...
@@ -60,6 +61,7 @@
...
@@ -60,6 +61,7 @@
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
;
};
2FDE729B2F15C48100C471A0
/* Images.xcassets */
=
{
isa
=
PBXFileReference
;
lastKnownFileType
=
folder.assetcatalog
;
path
=
Images.xcassets
;
sourceTree
=
"<group>"
;
};
/* End PBXFileReference section */
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
...
@@ -134,6 +136,7 @@
...
@@ -134,6 +136,7 @@
2F96475B2E86307D002CC7CB
=
{
2F96475B2E86307D002CC7CB
=
{
isa
=
PBXGroup
;
isa
=
PBXGroup
;
children
=
(
children
=
(
2FDE729B2F15C48100C471A0
/* Images.xcassets */
,
2F9647672E86307D002CC7CB
/* QmsPluginFramework */
,
2F9647672E86307D002CC7CB
/* QmsPluginFramework */
,
2FC779AA2F0E1C310002A1D4
/* testApp */
,
2FC779AA2F0E1C310002A1D4
/* testApp */
,
2FAEF0982E8B7BC80086EDA3
/* Frameworks */
,
2FAEF0982E8B7BC80086EDA3
/* Frameworks */
,
...
@@ -271,6 +274,7 @@
...
@@ -271,6 +274,7 @@
isa
=
PBXResourcesBuildPhase
;
isa
=
PBXResourcesBuildPhase
;
buildActionMask
=
2147483647
;
buildActionMask
=
2147483647
;
files
=
(
files
=
(
2FDE729C2F15C48100C471A0
/* Images.xcassets in Resources */
,
);
);
runOnlyForDeploymentPostprocessing
=
0
;
runOnlyForDeploymentPostprocessing
=
0
;
};
};
...
...
framework/QmsPluginFramework/QmsPluginFramework/AccessItem/AccessTransactionViewController.mm
View file @
1511a7e0
// AccessTransactionViewController.mm
// AccessTransactionViewController.mm
#import "ImageCacheHelper.h"
#import "AccessTransactionViewController.h"
#import "AccessTransactionViewController.h"
#import
<WebKit
/
WebKit
.
h
>
#import
<WebKit
/
WebKit
.
h
>
...
@@ -358,24 +358,17 @@
...
@@ -358,24 +358,17 @@
submittedSignature.clipsToBounds = YES;
submittedSignature.clipsToBounds = YES;
submittedSignature.layer.borderColor = UIColor.blackColor.CGColor;
submittedSignature.layer.borderColor = UIColor.blackColor.CGColor;
submittedSignature.layer.borderWidth = 1.0;
submittedSignature.layer.borderWidth = 1.0;
submittedSignature.layer.cornerRadius = 4.0; // optional rounded corners
submittedSignature.layer.cornerRadius = 4.0;
submittedSignature.clipsToBounds = YES;
submittedSignature.userInteractionEnabled = YES;
submittedSignature.userInteractionEnabled = YES;
UITapGestureRecognizer *tapSubmitted = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapSignature:)];
UITapGestureRecognizer *tapSubmitted = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapSignature:)];
[submittedSignature addGestureRecognizer:tapSubmitted];
[submittedSignature addGestureRecognizer:tapSubmitted];
[self.contentView addSubview:submittedSignature];
NSURL *submittedURL = [NSURL URLWithString:self.transactionData[@"signature_submit"]];
NSURL *submittedURL = [NSURL URLWithString:self.transactionData[@"signature_submit"]];
if (submittedURL) {
[ImageCacheHelper loadImageWithURL:submittedURL completion:^(UIImage *image) {
// Simple async load
submittedSignature.image = image;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
}];
NSData *data = [NSData dataWithContentsOfURL:submittedURL];
if (data) {
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
submittedSignature.image = img;
});
}
});
}
[self.contentView addSubview:submittedSignature];
[self.contentView addSubview:submittedSignature];
UILabel *submittedName = [self regular16:[NSString stringWithFormat:@"Name: %@", self.transactionData[@"name_submit"] ?: @"--"]];
UILabel *submittedName = [self regular16:[NSString stringWithFormat:@"Name: %@", self.transactionData[@"name_submit"] ?: @"--"]];
...
@@ -395,24 +388,16 @@
...
@@ -395,24 +388,16 @@
receivedSignature.layer.borderColor = UIColor.blackColor.CGColor;
receivedSignature.layer.borderColor = UIColor.blackColor.CGColor;
receivedSignature.layer.borderWidth = 1.0;
receivedSignature.layer.borderWidth = 1.0;
receivedSignature.layer.cornerRadius = 4.0;
receivedSignature.layer.cornerRadius = 4.0;
receivedSignature.clipsToBounds = YES;
receivedSignature.userInteractionEnabled = YES;
receivedSignature.userInteractionEnabled = YES;
UITapGestureRecognizer *tapReceived = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapSignature:)];
UITapGestureRecognizer *tapReceived = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapSignature:)];
[receivedSignature addGestureRecognizer:tapReceived];
[receivedSignature addGestureRecognizer:tapReceived];
NSURL *receivedURL = [NSURL URLWithString:self.transactionData[@"signature_receive"]];
if (receivedURL) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:receivedURL];
if (data) {
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
receivedSignature.image = img;
});
}
});
}
[self.contentView addSubview:receivedSignature];
[self.contentView addSubview:receivedSignature];
NSURL *receivedURL = [NSURL URLWithString:self.transactionData[@"signature_receive"]];
[ImageCacheHelper loadImageWithURL:receivedURL completion:^(UIImage *image) {
receivedSignature.image = image;
}];
UILabel *receivedName = [self regular16:[NSString stringWithFormat:@"Name: %@", self.transactionData[@"name_receive"] ?: @"--"]];
UILabel *receivedName = [self regular16:[NSString stringWithFormat:@"Name: %@", self.transactionData[@"name_receive"] ?: @"--"]];
[self.contentView addSubview:receivedName];
[self.contentView addSubview:receivedName];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Appointment/AppointmentConfirmationViewController.mm
View file @
1511a7e0
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
#import <WebKit/WebKit.h>
#import <WebKit/WebKit.h>
#import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
#import "APIClient.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
@interface AppointmentConfirmationViewController () <WKNavigationDelegate>
@interface AppointmentConfirmationViewController () <WKNavigationDelegate>
...
@@ -636,18 +637,9 @@
...
@@ -636,18 +637,9 @@
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) return;
if (!url) return;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data && !error) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
imageView.image = image;
});
}
}
}];
}];
[task resume];
}
}
-(void)confirmPressed {
-(void)confirmPressed {
...
...
framework/QmsPluginFramework/QmsPluginFramework/Appointment/AppointmentDetailsViewController.mm
View file @
1511a7e0
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
#import "APIClient.h"
#import "APIClient.h"
#import
<WebKit
/
WebKit
.
h
>
#import
<WebKit
/
WebKit
.
h
>
#import
<EventKit
/
EventKit
.
h
>
#import
<EventKit
/
EventKit
.
h
>
#import "ImageCacheHelper.h"
@interface AppointmentDetailsViewController ()
<UIScrollViewDelegate>
@interface AppointmentDetailsViewController ()
<UIScrollViewDelegate>
...
@@ -177,20 +178,9 @@
...
@@ -177,20 +178,9 @@
NSURL *url = [NSURL URLWithString:urlString];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) return;
if (!url) return;
NSURLSessionDataTask *task =
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
[[NSURLSession sharedSession] dataTaskWithURL:url
imageView.image = image;
completionHandler:^(NSData *data, NSURLResponse *res, NSError *err) {
if (err || !data) return;
UIImage *img = [UIImage imageWithData:data];
if (!img) return;
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = img;
});
}];
}];
[task resume];
}
}
- (void)handleFloorPlanTap:(UITapGestureRecognizer *)sender {
- (void)handleFloorPlanTap:(UITapGestureRecognizer *)sender {
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/DashboardViewController.mm
View file @
1511a7e0
...
@@ -14,6 +14,7 @@
...
@@ -14,6 +14,7 @@
#import "OfflineSyncManager.h"
#import "OfflineSyncManager.h"
#import "LocalStorage.h"
#import "LocalStorage.h"
#import "APIConfig.h"
#import "APIConfig.h"
#import "ImageCacheHelper.h"
@interface DashboardViewController ()
@interface DashboardViewController ()
...
@@ -69,6 +70,7 @@
...
@@ -69,6 +70,7 @@
self.view.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0];
self.view.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0];
_sectionViews = [NSMutableDictionary dictionary];
_sectionViews = [NSMutableDictionary dictionary];
[self setupHeader];
[self setupHeader];
[self setupScrollContent];
[self setupScrollContent];
[self setupFooter];
[self setupFooter];
...
@@ -212,10 +214,12 @@
...
@@ -212,10 +214,12 @@
// ---------- Overlay ----------
// ---------- Overlay ----------
self.overlayImageView = [[UIImageView alloc] init];
self.overlayImageView = [[UIImageView alloc] init];
self.overlayImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.overlayImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.overlayImageView.contentMode = UIViewContentModeScaleAspectFill;
self.overlayImageView.contentMode = UIViewContentModeScaleToFill;
self.overlayImageView.alpha = 0.4;
self.overlayImageView.userInteractionEnabled = NO;
self.overlayImageView.alpha = 0.5;
[imageContainer addSubview:self.overlayImageView];
[imageContainer addSubview:self.overlayImageView];
[NSLayoutConstraint activateConstraints:@[
[NSLayoutConstraint activateConstraints:@[
[self.overlayImageView.topAnchor constraintEqualToAnchor:imageContainer.topAnchor],
[self.overlayImageView.topAnchor constraintEqualToAnchor:imageContainer.topAnchor],
[self.overlayImageView.leadingAnchor constraintEqualToAnchor:imageContainer.leadingAnchor],
[self.overlayImageView.leadingAnchor constraintEqualToAnchor:imageContainer.leadingAnchor],
...
@@ -305,21 +309,27 @@
...
@@ -305,21 +309,27 @@
[self.infoButton.heightAnchor constraintEqualToConstant:24]
[self.infoButton.heightAnchor constraintEqualToConstant:24]
]];
]];
// ---------- Load Remote Image ----------
// ---------- Load Remote Image & Overlay ----------
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIImage *overlayImage =
[UIImage imageNamed:@"img_overlay"
inBundle:bundle
compatibleWithTraitCollection:nil];
self.overlayImageView.image = overlayImage;
[imageContainer bringSubviewToFront:self.overlayImageView];
NSLog(@"[Overlay] bundle = %@", bundle);
NSLog(@"[Overlay] image = %@", overlayImage);
NSString *remoteHeaderURL = self.projectLogo;
NSString *remoteHeaderURL = self.projectLogo;
if (remoteHeaderURL.length > 0) {
if (remoteHeaderURL.length > 0) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:remoteHeaderURL];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:remoteHeaderURL]];
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
UIImage *img = data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"];
self.backgroundImageView.image = image ?: [UIImage imageNamed:@"header_bg"];
[imageContainer bringSubviewToFront:self.overlayImageView];
dispatch_async(dispatch_get_main_queue(), ^{
}];
self.backgroundImageView.image = img;
self.overlayImageView.image = [UIImage imageNamed:@"overlay"];
});
});
} else {
} else {
self.backgroundImageView.image = [UIImage imageNamed:@"header_bg"];
self.backgroundImageView.image = [UIImage imageNamed:@"header_bg"];
self.overlayImageView.image = [UIImage imageNamed:@"overlay"];
self.overlayImageView.image = [UIImage imageNamed:@"
img_
overlay"];
}
}
}
}
...
@@ -670,7 +680,6 @@
...
@@ -670,7 +680,6 @@
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.projectNameLabel.text = self.projectName;
self.projectNameLabel.text = self.projectName;
self.unitNameLabel.text = self.projectCode;
self.unitNameLabel.text = self.projectCode;
self.overlayImageView.image = [UIImage imageNamed:@"overlay"];
[self refreshDashboardSectionsWithData:mainData];
[self refreshDashboardSectionsWithData:mainData];
});
});
...
@@ -1195,20 +1204,12 @@
...
@@ -1195,20 +1204,12 @@
issueImage = lastArray[0][@"image"];
issueImage = lastArray[0][@"image"];
}
}
if (issueImage
&& issueImage
.length > 0) {
if (issueImage.length > 0) {
NSURL *url = [NSURL URLWithString:issueImage];
NSURL *url = [NSURL URLWithString:issueImage];
if (url) {
if (url) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (image) thumb.image = image;
if (data) {
}];
UIImage *img = [UIImage imageWithData:data];
if (img) {
dispatch_async(dispatch_get_main_queue(), ^{
thumb.image = img;
});
}
}
});
}
}
}
}
...
@@ -1401,11 +1402,6 @@
...
@@ -1401,11 +1402,6 @@
}
}
}
}
- (UIImage *)downloadImageFrom:(NSString *)urlString {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]];
return data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"];
}
#pragma mark - Card Factory with Tap Animation
#pragma mark - Card Factory with Tap Animation
- (UIView *)createCardWithText:(NSString *)text frame:(CGRect)frame {
- (UIView *)createCardWithText:(NSString *)text frame:(CGRect)frame {
UIView *card = [[UIView alloc] initWithFrame:frame];
UIView *card = [[UIView alloc] initWithFrame:frame];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/GeneralInfoViewController.mm
View file @
1511a7e0
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
#import <objc/runtime.h>
#import <objc/runtime.h>
#import "GeneralInfoViewController.h"
#import "GeneralInfoViewController.h"
#import "APIClient.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
@interface GeneralInfoViewController () <UIDocumentInteractionControllerDelegate, UIScrollViewDelegate>
@interface GeneralInfoViewController () <UIDocumentInteractionControllerDelegate, UIScrollViewDelegate>
...
@@ -764,15 +765,11 @@ typedef struct {
...
@@ -764,15 +765,11 @@ typedef struct {
NSURL *url = [NSURL URLWithString:layoutImageURL];
NSURL *url = [NSURL URLWithString:layoutImageURL];
if (url) {
if (url) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (image) {
if (data) {
imgView.image = image;
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
imgView.image = img;
});
}
}
}
)
;
}
]
;
}
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showFullImage:)];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showFullImage:)];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/ProjectViewController.mm
View file @
1511a7e0
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
#import "APIClient.h"
#import "APIClient.h"
#import "APIConfig.h"
#import "APIConfig.h"
#import "UnitViewController.h"
#import "UnitViewController.h"
#import "ImageCacheHelper.h"
@interface ProjectCardCell : UICollectionViewCell
@interface ProjectCardCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIImageView *imageView;
...
@@ -160,7 +161,11 @@
...
@@ -160,7 +161,11 @@
forIndexPath:indexPath];
forIndexPath:indexPath];
// Default placeholder
// Default placeholder
cell.imageView.image = [UIImage systemImageNamed:@"photo"];
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UIImage *placeholderImage = [UIImage imageNamed:@"img_placeholder"
inBundle:bundle
compatibleWithTraitCollection:nil];
cell.imageView.image = placeholderImage;
NSDictionary *project = self.projects[indexPath.item];
NSDictionary *project = self.projects[indexPath.item];
...
@@ -168,35 +173,20 @@
...
@@ -168,35 +173,20 @@
id projectName = project[@"project_name"];
id projectName = project[@"project_name"];
cell.nameLabel.text = [projectName isKindOfClass:[NSString class]] ? projectName : @"";
cell.nameLabel.text = [projectName isKindOfClass:[NSString class]] ? projectName : @"";
//
Get logo URL safely
//
Determine logo URL
NSString *logo = project[@"logo"];
NSString *logo
URLString
= project[@"logo"];
NSString *thumb
= project[@"logo_thumbnail"];
if (!logoURLString.length) logoURLString
= project[@"logo_thumbnail"];
NSString *logoURLString = (logo.length > 0) ? logo :
// Use ImageCacheHelper for async loading & caching
(thumb.length > 0) ? thumb : nil;
[ImageCacheHelper loadImageWithURL:[NSURL URLWithString:logoURLString ?: @""] completion:^(UIImage *image) {
if (image) {
// Ensure the cell is still visible
NSURL *url = [NSURL URLWithString:logoURLString ?: @""];
// Async load image safely
if (url) {
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
UIImage *image = data ? [UIImage imageWithData:data] : nil;
if (!image) return; // leave placeholder
dispatch_async(dispatch_get_main_queue(), ^{
// Only update if the cell is still visible at this indexPath
ProjectCardCell *updateCell = (ProjectCardCell *)[collectionView cellForItemAtIndexPath:indexPath];
ProjectCardCell *updateCell = (ProjectCardCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) {
if (updateCell) {
updateCell.imageView.image = image;
updateCell.imageView.image = image;
}
}
});
}];
[task resume];
}
}
}];
return cell;
return cell;
}
}
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/AddIssue/DetailsViewController.mm
View file @
1511a7e0
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import "OfflineSyncManager.h"
#import "OfflineSyncManager.h"
#import "NetworkManager.h"
#import "NetworkManager.h"
#import "ImageCacheHelper.h"
@interface AddIssueDetailsViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@interface AddIssueDetailsViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) UIView *headerView;
...
@@ -159,9 +160,7 @@
...
@@ -159,9 +160,7 @@
NSURL *url = [NSURL URLWithString:urlStr];
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) continue;
if (!url) continue;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *img) {
if (error || !data) return;
UIImage *img = [UIImage imageWithData:data];
if (!img) return;
if (!img) return;
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
...
@@ -179,7 +178,6 @@
...
@@ -179,7 +178,6 @@
}
}
});
});
}];
}];
[task resume];
}
}
}
}
NSLog(@"✅ Prefilled Edit Mode UI successfully");
NSLog(@"✅ Prefilled Edit Mode UI successfully");
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/AddIssue/PlanViewController.mm
View file @
1511a7e0
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
#import "APIClient.h"
#import "APIClient.h"
#import <AVFoundation/AVFoundation.h>
#import <AVFoundation/AVFoundation.h>
#import "IssueTutorialViewController.h"
#import "IssueTutorialViewController.h"
#import "ImageCacheHelper.h"
@interface PlanViewController ()
@interface PlanViewController ()
...
@@ -218,35 +219,41 @@
...
@@ -218,35 +219,41 @@
return;
return;
}
}
NSLog(@"⬇️ Downloading plan image from %@", self.planImageURL);
NSLog(@"⬇️ Loading plan image from %@", self.planImageURL);
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error || !data) {
NSLog(@"❌ Failed to download plan image: %@", error.localizedDescription);
return;
}
UIImage *image = [UIImage imageWithData:data];
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
if (!image) {
if (!image) {
NSLog(@"
⚠️ Could not decode image data
");
NSLog(@"
❌ Failed to load plan image from cache/network
");
return;
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.planImageView.image = image;
self.planImageView.image = image;
self.planImageView.userInteractionEnabled = YES;
self.planImageView.userInteractionEnabled = YES;
// Add tap recognizer if not already added
BOOL hasTap = NO;
for (UIGestureRecognizer *gr in self.planImageView.gestureRecognizers) {
if ([gr isKindOfClass:[UITapGestureRecognizer class]]) {
hasTap = YES;
break;
}
}
if (!hasTap) {
UITapGestureRecognizer *tapRecognizer =
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleTapOnPlan:)];
action:@selector(handleTapOnPlan:)];
[self.planImageView addGestureRecognizer:tapRecognizer];
[self.planImageView addGestureRecognizer:tapRecognizer];
}
[self.scrollView setZoomScale:1.0 animated:NO];
[self.scrollView setZoomScale:1.0 animated:NO];
[self.view setNeedsLayout];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
[self.view layoutIfNeeded];
[self renderAllLocations:self.locations];
[self renderAllLocations:self.locations];
NSLog(@"✅ Plan image displayed successfully");
NSLog(@"✅ Plan image displayed successfully");
});
});
}];
}];
[task resume];
}
}
- (void)handleTapOnPlan:(UITapGestureRecognizer *)recognizer {
- (void)handleTapOnPlan:(UITapGestureRecognizer *)recognizer {
...
@@ -426,28 +433,6 @@
...
@@ -426,28 +433,6 @@
]];
]];
}
}
- (void)loadPlanImageFromURL:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
if (!url) return;
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Failed to load image: %@", error);
return;
}
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
self.planImageView.image = image;
NSLog(@"🖼 Plan image loaded successfully");
});
}
}];
[task resume];
}
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/HistoryViewController.mm
View file @
1511a7e0
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
#import "CalendarView.h"
#import "CalendarView.h"
#import "NSDate+CalendarHelpers.h"
#import "NSDate+CalendarHelpers.h"
#import "AddInfoViewController.h"
#import "AddInfoViewController.h"
#import "ImageCacheHelper.h"
@interface HistoryViewController () <UITableViewDelegate, UITableViewDataSource>
@interface HistoryViewController () <UITableViewDelegate, UITableViewDataSource>
...
@@ -379,6 +380,7 @@
...
@@ -379,6 +380,7 @@
CGFloat imageSize = 56;
CGFloat imageSize = 56;
CGFloat spacing = 8;
CGFloat spacing = 8;
// Inside your thumbnail loop
for (NSDictionary *imgDict in images) {
for (NSDictionary *imgDict in images) {
NSString *thumbURL = imgDict[@"thumb_image"];
NSString *thumbURL = imgDict[@"thumb_image"];
if (thumbURL.length == 0) continue;
if (thumbURL.length == 0) continue;
...
@@ -389,22 +391,17 @@
...
@@ -389,22 +391,17 @@
thumbView.contentMode = UIViewContentModeScaleAspectFill;
thumbView.contentMode = UIViewContentModeScaleAspectFill;
thumbView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
thumbView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
// Load async
[thumbScroll addSubview:thumbView];
NSURL *url = [NSURL URLWithString:thumbURL];
NSURL *url = [NSURL URLWithString:thumbURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (image) {
if (data) {
thumbView.image = image;
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
thumbView.image = img;
});
}
}
}
)
;
}
]
;
[thumbScroll addSubview:thumbView];
x += imageSize + spacing;
x += imageSize + spacing;
}
}
thumbScroll.contentSize = CGSizeMake(x, imageSize);
thumbScroll.contentSize = CGSizeMake(x, imageSize);
return cell;
return cell;
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssueDetailViewController.mm
View file @
1511a7e0
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
#import "HistoryViewController.h"
#import "HistoryViewController.h"
#import "PlanViewController.h"
#import "PlanViewController.h"
#import "WithdrawSingleIssueViewController.h"
#import "WithdrawSingleIssueViewController.h"
#import "ImageCacheHelper.h"
@interface IssueDetailViewController () <UIScrollViewDelegate>
@interface IssueDetailViewController () <UIScrollViewDelegate>
...
@@ -342,29 +343,17 @@
...
@@ -342,29 +343,17 @@
[imageScroll addSubview:imgView];
[imageScroll addSubview:imgView];
NSURL *url = [NSURL URLWithString:urlStr];
NSURL *url = [NSURL URLWithString:urlStr];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (image) imgView.image = image;
if (data) {
}];
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
imgView.image = img;
});
}
});
}
}
NSString *planThumbURL = self.issueData[@"plan_image_thumb"];
NSString *planThumbURL = self.issueData[@"plan_image_thumb"];
if (planThumbURL.length > 0) {
if (planThumbURL.length > 0) {
NSURL *url = [NSURL URLWithString:planThumbURL];
NSURL *url = [NSURL URLWithString:planThumbURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (image) self.floorPlanThumbnail.image = image;
if (data) {
}];
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.floorPlanThumbnail.image = img;
});
}
});
}
}
//thumbnail marker
//thumbnail marker
...
@@ -471,14 +460,9 @@
...
@@ -471,14 +460,9 @@
// Load full image asynchronously
// Load full image asynchronously
NSURL *url = [NSURL URLWithString:planFullURL];
NSURL *url = [NSURL URLWithString:planFullURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *img) {
NSData *data = [NSData dataWithContentsOfURL:url];
if (!data) return;
UIImage *img = [UIImage imageWithData:data];
if (!img) return;
if (!img) return;
dispatch_async(dispatch_get_main_queue(), ^{
fullImageView.image = img;
fullImageView.image = img;
NSString *xStr = self.issueData[@"pos_x"];
NSString *xStr = self.issueData[@"pos_x"];
...
@@ -504,8 +488,7 @@
...
@@ -504,8 +488,7 @@
marker.layer.borderWidth = 1.5;
marker.layer.borderWidth = 1.5;
[fullImageView addSubview:marker];
[fullImageView addSubview:marker];
}
}
});
}];
});
}
}
- (void)closeFullFloorPlan {
- (void)closeFullFloorPlan {
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/IssuesViewController.mm
View file @
1511a7e0
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
#import "IssueDetailViewController.h"
#import "IssueDetailViewController.h"
#import "WithdrawSingleIssueViewController.h"
#import "WithdrawSingleIssueViewController.h"
#import "WithdrawIssuesViewController.h"
#import "WithdrawIssuesViewController.h"
#import "ImageCacheHelper.h"
#pragma mark - Internal IssueCell
#pragma mark - Internal IssueCell
@interface IssueCell : UITableViewCell
@interface IssueCell : UITableViewCell
...
@@ -605,30 +606,23 @@
...
@@ -605,30 +606,23 @@
// --- Thumbnail Handling ---
// --- Thumbnail Handling ---
if (thumbURL.length > 0) {
if (thumbURL.length > 0) {
NSURL *url = [NSURL URLWithString:thumbURL];
NSURL *url = [NSURL URLWithString:thumbURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *img) {
NSData *data = [NSData dataWithContentsOfURL:url];
// Make sure cell is still visible
dispatch_async(dispatch_get_main_queue(), ^{
IssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
IssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (!updateCell) return;
if (!updateCell) return;
// Clear any "No image" label
s
// Clear previous subviews / placeholder
s
for (UIView *subview in updateCell.thumbView.subviews) {
for (UIView *subview in updateCell.thumbView.subviews) {
[subview removeFromSuperview];
[subview removeFromSuperview];
}
}
if (data) {
UIImage *img = [UIImage imageWithData:data];
if (img) {
if (img) {
updateCell.thumbView.image = img;
updateCell.thumbView.image = img;
updateCell.thumbView.backgroundColor = UIColor.clearColor;
updateCell.thumbView.backgroundColor = UIColor.clearColor;
} else {
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
[self setNoImagePlaceholder:updateCell.thumbView];
}
}
} else {
}];
[self setNoImagePlaceholder:updateCell.thumbView];
}
});
});
} else {
} else {
[self setNoImagePlaceholder:cell.thumbView];
[self setNoImagePlaceholder:cell.thumbView];
}
}
...
...
framework/QmsPluginFramework/QmsPluginFramework/Issue/WithdrawIssuesViewController.mm
View file @
1511a7e0
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
#import "APIClient.h"
#import "APIClient.h"
#import "WithdrawSingleIssueViewController.h"
#import "WithdrawSingleIssueViewController.h"
#import "FilterViewController.h"
#import "FilterViewController.h"
#import "ImageCacheHelper.h"
#pragma mark - custom cell structure
#pragma mark - custom cell structure
@interface WithdrawIssueCell : UITableViewCell
@interface WithdrawIssueCell : UITableViewCell
...
@@ -631,9 +632,7 @@
...
@@ -631,9 +632,7 @@
// Thumbnail rendering (copied from IssueViewController)
// Thumbnail rendering (copied from IssueViewController)
if (thumbURL.length > 0) {
if (thumbURL.length > 0) {
NSURL *url = [NSURL URLWithString:thumbURL];
NSURL *url = [NSURL URLWithString:thumbURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *img) {
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
WithdrawIssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
WithdrawIssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (!updateCell) return;
if (!updateCell) return;
...
@@ -642,19 +641,13 @@
...
@@ -642,19 +641,13 @@
[subview removeFromSuperview];
[subview removeFromSuperview];
}
}
if (data) {
UIImage *img = [UIImage imageWithData:data];
if (img) {
if (img) {
updateCell.thumbView.image = img;
updateCell.thumbView.image = img;
updateCell.thumbView.backgroundColor = UIColor.clearColor;
updateCell.thumbView.backgroundColor = UIColor.clearColor;
} else {
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
[self setNoImagePlaceholder:updateCell.thumbView];
}
}
} else {
}];
[self setNoImagePlaceholder:updateCell.thumbView];
}
});
});
} else {
} else {
[self setNoImagePlaceholder:cell.thumbView];
[self setNoImagePlaceholder:cell.thumbView];
}
}
...
...
framework/QmsPluginFramework/QmsPluginFramework/Sync/SyncScreenViewController.mm
View file @
1511a7e0
...
@@ -620,7 +620,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
...
@@ -620,7 +620,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
[APIConfig projectId], [APIConfig drawingPlanId]);
[APIConfig projectId], [APIConfig drawingPlanId]);
NSString *message = [NSString stringWithFormat:
NSString *message = [NSString stringWithFormat:
@"Currently downloading unit \"%@\".\nPlease do not close the app.", sortName];
@"Currently downloading
for
unit \"%@\".\nPlease do not close the app.", sortName];
[[OfflineSyncManager shared] startDownloadUnitImagesWithProgress:^(NSInteger completed, NSInteger total) {
[[OfflineSyncManager shared] startDownloadUnitImagesWithProgress:^(NSInteger completed, NSInteger total) {
__strong typeof(weakSelf) strongSelf2 = weakSelf;
__strong typeof(weakSelf) strongSelf2 = weakSelf;
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIClient.mm
View file @
1511a7e0
...
@@ -1738,10 +1738,6 @@
...
@@ -1738,10 +1738,6 @@
+ (void)requestArrayImage:(NSArray *)imageArray
+ (void)requestArrayImage:(NSArray *)imageArray
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"ArrayImage" completion:completion]) {
return;
}
// ✅ URL
// ✅ URL
NSURL *url = [APIConfig urlWithPath:@"/owner/offline/syncImageArray"];
NSURL *url = [APIConfig urlWithPath:@"/owner/offline/syncImageArray"];
...
@@ -1795,10 +1791,6 @@
...
@@ -1795,10 +1791,6 @@
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
if (completion) {
NSString *cacheKey = [NSString stringWithFormat:@"ArrayImage_%@", [APIConfig projectId]];
NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:jsonResponse];
[LocalStorage saveDictionary:safeJson forKey:cacheKey];
NSLog(@"💾 Saved to cache: %@", cacheKey);
completion(YES, jsonResponse, jsonParseError);
completion(YES, jsonResponse, jsonParseError);
}
}
});
});
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/ImageCaching/ImageCacheHelper.h
0 → 100644
View file @
1511a7e0
// ImageCacheHelper.h
#import <UIKit/UIKit.h>
@interface
ImageCacheHelper
:
NSObject
/// Load image from cache, download if missing, or fallback to placeholder
+
(
void
)
loadImageWithURL
:(
NSURL
*
)
url
completion
:(
void
(
^
)(
UIImage
*
image
))
completion
;
/// Shared placeholder image
+
(
UIImage
*
)
placeholderImage
;
/// Get the expected cache file path for a URL
+
(
NSString
*
)
cachedFilePathForURL
:(
NSURL
*
)
url
;
@end
framework/QmsPluginFramework/QmsPluginFramework/Utilities/ImageCaching/ImageCacheHelper.mm
0 → 100644
View file @
1511a7e0
// ImageCacheHelper.mm
#import <CommonCrypto/CommonDigest.h>
#import "ImageCacheHelper.h"
#import "NetworkManager.h"
#import "APIClient.h"
@implementation ImageCacheHelper
#pragma mark - Public Methods
+ (void)loadImageWithURL:(NSURL *)url
completion:(void (^)(UIImage *image))completion {
if (!url) {
if (completion) completion([self placeholderImage]);
return;
}
NSString *filePath = [self cachedFilePathForURL:url];
NSFileManager *fm = [NSFileManager defaultManager];
// 1️⃣ Check cache
if ([fm fileExistsAtPath:filePath]) {
UIImage *cachedImage = [UIImage imageWithContentsOfFile:filePath];
if (cachedImage) {
if (completion) completion(cachedImage);
return;
}
}
// 2️⃣ Not cached → check network
if (![self canDownloadImages]) {
// No internet → return placeholder
if (completion) completion([self placeholderImage]);
return;
}
// 3️⃣ Download
[APIClient downloadAndCacheImage:url completion:^(BOOL success, NSString *filePath) {
UIImage *img = nil;
if (success && filePath) {
img = [UIImage imageWithContentsOfFile:filePath];
}
if (!img) img = [self placeholderImage];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) completion(img);
});
}];
}
+ (UIImage *)placeholderImage {
static UIImage *image = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:self];
image = [UIImage imageNamed:@"img_placeholder"
inBundle:bundle
compatibleWithTraitCollection:nil];
});
return image;
}
#pragma mark - Cache File Helper
+ (NSString *)cachedFilePathForURL:(NSURL *)url {
if (!url) return nil;
NSString *cacheDir = [self offlineImageCacheDirectory]; // You already have this
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:cacheDir]) {
NSError *dirError = nil;
[fm createDirectoryAtPath:cacheDir
withIntermediateDirectories:YES
attributes:nil
error:&dirError];
if (dirError) {
NSLog(@"❌ Failed to create cache directory: %@", dirError);
}
}
NSString *cleanName = url.lastPathComponent.stringByDeletingPathExtension;
NSString *ext = url.pathExtension;
NSString *fileName = [NSString stringWithFormat:@"%@_%@.%@",
cleanName,
[self md5Hash:url.absoluteString],
ext];
return [cacheDir stringByAppendingPathComponent:fileName];
}
#pragma mark - MD5 Hash Helper
+ (NSString *)md5Hash:(NSString *)input {
if (!input) return @"";
const char *cStr = [input UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
#pragma mark - Offline Cache Directory
+ (NSString *)offlineImageCacheDirectory {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDir = [[paths firstObject] stringByAppendingPathComponent:@"OfflineImages"];
return cacheDir;
}
+ (BOOL)canDownloadImages {
return [NetworkManager.sharedManager isConnected];
}
@end
framework/QmsPluginFramework/QmsPluginFramework/Utilities/OfflineSync/OfflineSyncManager.mm
View file @
1511a7e0
...
@@ -3,15 +3,16 @@
...
@@ -3,15 +3,16 @@
#import "LocalStorage.h"
#import "LocalStorage.h"
#import "NetworkManager.h"
#import "NetworkManager.h"
#import "APIClient.h"
#import "APIClient.h"
#import "APIConfig.h"
static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
static NSString * const kOfflineQueueKey = @"OFFLINE_SYNC_QUEUE";
static NSString * const kSelectedOfflineUnitsKey = @"SELECTED_OFFLINE_UNITS";
static NSString * const kSyncHistoryKey = @"SYNC_HISTORY_QUEUE";
static NSString * const kSyncHistoryKey = @"SYNC_HISTORY_QUEUE";
static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
static NSString * const kSyncSelectedUnitsKey = @"SYNC_SELECTEDUNITS";
@implementation OfflineSyncManager
@implementation OfflineSyncManager
#pragma mark - Singleton
#pragma mark - Singleton
...
@@ -542,7 +543,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
...
@@ -542,7 +543,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
return;
return;
}
}
NSArray *selectedUnits = [
LocalStorage loadArrayForKey:kSelectedOfflineUnitsKey
] ?: @[];
NSArray *selectedUnits = [
self buildOfflineUnitPayloadForCurrentConfig
] ?: @[];
// ------------------------------
// ------------------------------
// Build sequential tasks
// Build sequential tasks
...
@@ -986,10 +987,33 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
...
@@ -986,10 +987,33 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
if (img[@"link"]) [urls addObject:[NSURL URLWithString:img[@"link"]]];
if (img[@"link"]) [urls addObject:[NSURL URLWithString:img[@"link"]]];
}
}
for (NSURL *url in urls) {
NSLog(@"URL for image downloads: %@", url.absoluteString);
}
completion(urls.array, nil);
completion(urls.array, nil);
}];
}];
}
}
- (NSArray *)buildOfflineUnitPayloadForCurrentConfig {
NSString *projectId = [APIConfig projectId];
NSString *planId = [APIConfig drawingPlanId];
if (!projectId || !planId) {
NSLog(@"⚠️ Missing projectId or planId for image sync");
return @[];
}
NSArray *result = @[
@{
@"project_id": projectId,
@"plan_id": @[planId] // API expects array
}
];
NSLog(@"🔄 Image sync payload (single unit): %@", result);
return result;
}
#pragma mark - Safe Sequential API Helper
#pragma mark - Safe Sequential API Helper
- (void)safeSequentialTask:(NSString *)taskKey
- (void)safeSequentialTask:(NSString *)taskKey
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment