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
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
392 additions
and
316 deletions
+392
-316
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
+5
-13
AppointmentDetailsViewController.mm
...Framework/Appointment/AppointmentDetailsViewController.mm
+3
-13
DashboardViewController.mm
...k/QmsPluginFramework/Dashboard/DashboardViewController.mm
+28
-32
GeneralInfoViewController.mm
...QmsPluginFramework/Dashboard/GeneralInfoViewController.mm
+5
-8
ProjectViewController.mm
...ork/QmsPluginFramework/Dashboard/ProjectViewController.mm
+20
-30
DetailsViewController.mm
...msPluginFramework/Issue/AddIssue/DetailsViewController.mm
+28
-30
PlanViewController.mm
...k/QmsPluginFramework/Issue/AddIssue/PlanViewController.mm
+21
-36
HistoryViewController.mm
...amework/QmsPluginFramework/Issue/HistoryViewController.mm
+8
-11
IssueDetailViewController.mm
...ork/QmsPluginFramework/Issue/IssueDetailViewController.mm
+36
-53
IssuesViewController.mm
...ramework/QmsPluginFramework/Issue/IssuesViewController.mm
+18
-24
WithdrawIssuesViewController.mm
.../QmsPluginFramework/Issue/WithdrawIssuesViewController.mm
+18
-25
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>
...
@@ -632,22 +633,13 @@
...
@@ -632,22 +633,13 @@
- (void)loadImage:(NSString *)urlString intoImageView:(UIImageView *)imageView {
- (void)loadImage:(NSString *)urlString intoImageView:(UIImageView *)imageView {
if (!urlString) return;
if (!urlString) return;
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) {
imageView.image = image;
if (data && !error) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
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 ()
...
@@ -68,7 +69,8 @@
...
@@ -68,7 +69,8 @@
[super viewDidLoad];
[super viewDidLoad];
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 *logoURLString = 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 ?: @""];
ProjectCardCell *updateCell = (ProjectCardCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell) {
// Async load image safely
updateCell.imageView.image = image;
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];
if (updateCell) {
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;
...
@@ -150,37 +151,34 @@
...
@@ -150,37 +151,34 @@
// 🖼 Prefill images under "first"
// 🖼 Prefill images under "first"
NSArray *firstImages = self.issueFullData[@"first"];
NSArray *firstImages = self.issueFullData[@"first"];
if ([firstImages isKindOfClass:[NSArray class]] && firstImages.count > 0) {
if ([firstImages isKindOfClass:[NSArray class]] && firstImages.count > 0) {
for (int i = 0; i < MIN(firstImages.count, 3); i++) {
for (int i = 0; i < MIN(firstImages.count, 3); i++) {
NSDictionary *imgDict = firstImages[i];
NSDictionary *imgDict = firstImages[i];
NSString *urlStr = imgDict[@"thumb_image"] ?: imgDict[@"image"];
NSString *urlStr = imgDict[@"thumb_image"] ?: imgDict[@"image"];
if (![urlStr isKindOfClass:[NSString class]]) continue;
if (![urlStr isKindOfClass:[NSString class]]) continue;
[self.existingImages addObject:urlStr];
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) continue;
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *img) {
if (!img) return;
[self.existingImages addObject:urlStr];
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [NSURL URLWithString:urlStr];
if (i < self.uploadedImages.count)
if (!url) continue;
self.uploadedImages[i] = img;
else
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[self.uploadedImages addObject:img];
if (error || !data) return;
UIImage *img = [UIImage imageWithData:data];
UIButton *btn = [self.view viewWithTag:1000 + i];
if (!img) return;
if ([btn isKindOfClass:[UIButton class]]) {
[btn setImage:img forState:UIControlStateNormal];
dispatch_async(dispatch_get_main_queue(), ^{
btn.imageView.contentMode = UIViewContentModeScaleAspectFill;
if (i < self.uploadedImages.count)
btn.clipsToBounds = YES;
self.uploadedImages[i] = img;
[self addRemoveButtonToUpload:btn atIndex:i];
else
}
[self.uploadedImages addObject:img];
});
}];
UIButton *btn = [self.view viewWithTag:1000 + i];
}
if ([btn isKindOfClass:[UIButton class]]) {
[btn setImage:img forState:UIControlStateNormal];
btn.imageView.contentMode = UIViewContentModeScaleAspectFill;
btn.clipsToBounds = YES;
[self addRemoveButtonToUpload:btn atIndex:i];
}
});
}];
[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;
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
// Add tap recognizer if not already added
action:@selector(handleTapOnPlan:)];
BOOL hasTap = NO;
[self.planImageView addGestureRecognizer:tapRecognizer];
for (UIGestureRecognizer *gr in self.planImageView.gestureRecognizers) {
if ([gr isKindOfClass:[UITapGestureRecognizer class]]) {
hasTap = YES;
break;
}
}
if (!hasTap) {
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleTapOnPlan:)];
[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,31 +343,19 @@
...
@@ -342,31 +343,19 @@
[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
NSString *x = self.issueData[@"pos_x"];
NSString *x = self.issueData[@"pos_x"];
NSString *y = self.issueData[@"pos_y"];
NSString *y = self.issueData[@"pos_y"];
...
@@ -471,41 +460,35 @@
...
@@ -471,41 +460,35 @@
// 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"];
NSString *yStr = self.issueData[@"pos_y"];
NSString *yStr = self.issueData[@"pos_y"];
if (xStr && yStr) {
if (xStr && yStr) {
CGFloat posX = [xStr floatValue];
CGFloat posX = [xStr floatValue];
CGFloat posY = [yStr floatValue];
CGFloat posY = [yStr floatValue];
// Calculate scale and offsets based on aspect-fit
// Calculate scale and offsets based on aspect-fit
CGSize imageSize = img.size;
CGSize imageSize = img.size;
CGSize viewSize = fullImageView.bounds.size;
CGSize viewSize = fullImageView.bounds.size;
CGFloat scale = MIN(viewSize.width / imageSize.width, viewSize.height / imageSize.height);
CGFloat scale = MIN(viewSize.width / imageSize.width, viewSize.height / imageSize.height);
CGFloat offsetX = (viewSize.width - imageSize.width * scale) / 2.0;
CGFloat offsetX = (viewSize.width - imageSize.width * scale) / 2.0;
CGFloat offsetY = (viewSize.height - imageSize.height * scale) / 2.0;
CGFloat offsetY = (viewSize.height - imageSize.height * scale) / 2.0;
CGFloat dotX = posX * scale + offsetX;
CGFloat dotX = posX * scale + offsetX;
CGFloat dotY = posY * scale + offsetY;
CGFloat dotY = posY * scale + offsetY;
UIView *marker = [[UIView alloc] initWithFrame:CGRectMake(dotX - 5, dotY - 5, 10, 10)];
UIView *marker = [[UIView alloc] initWithFrame:CGRectMake(dotX - 5, dotY - 5, 10, 10)];
marker.backgroundColor = [UIColor systemRedColor];
marker.backgroundColor = [UIColor systemRedColor];
marker.layer.cornerRadius = 5;
marker.layer.cornerRadius = 5;
marker.layer.borderColor = [UIColor whiteColor].CGColor;
marker.layer.borderColor = [UIColor whiteColor].CGColor;
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 previous subviews / placeholders
// Clear any "No image" labels
for (UIView *subview in updateCell.thumbView.subviews) {
for (UIView *subview in updateCell.thumbView.subviews) {
[subview removeFromSuperview];
[subview removeFromSuperview];
}
}
if (img) {
if (data) {
updateCell.thumbView.image = img;
UIImage *img = [UIImage imageWithData:data];
updateCell.thumbView.backgroundColor = UIColor.clearColor;
if (img) {
} else {
updateCell.thumbView.image = img;
[self setNoImagePlaceholder:updateCell.thumbView];
updateCell.thumbView.backgroundColor = UIColor.clearColor;
}
} else {
}];
[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,34 +632,26 @@
...
@@ -631,34 +632,26 @@
// 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];
WithdrawIssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
dispatch_async(dispatch_get_main_queue(), ^{
if (!updateCell) return;
WithdrawIssueCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (!updateCell) return;
// Clear any "No image" placeholder labels
for (UIView *subview in updateCell.thumbView.subviews) {
// Clear any "No image" placeholder labels
[subview removeFromSuperview];
for (UIView *subview in updateCell.thumbView.subviews) {
}
[subview removeFromSuperview];
}
if (img) {
updateCell.thumbView.image = img;
if (data) {
updateCell.thumbView.backgroundColor = UIColor.clearColor;
UIImage *img = [UIImage imageWithData:data];
} else {
if (img) {
[self setNoImagePlaceholder:updateCell.thumbView];
updateCell.thumbView.image = img;
}
updateCell.thumbView.backgroundColor = UIColor.clearColor;
}];
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
}
} else {
[self setNoImagePlaceholder:updateCell.thumbView];
}
});
});
} else {
} else {
[self setNoImagePlaceholder:cell.thumbView];
[self setNoImagePlaceholder:cell.thumbView];
}
}
// Text fields
// Text fields
cell.referenceLabel.text = issue[@"reference"] ?: @"";
cell.referenceLabel.text = issue[@"reference"] ?: @"";
cell.statusLabel.text = issue[@"status_external"] ?: @"";
cell.statusLabel.text = issue[@"status_external"] ?: @"";
...
...
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