Commit 106bf6c9 authored by Wei Han's avatar Wei Han

readme update

parent b3b7d2a9
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
<dict> <dict>
<key>files</key> <key>files</key>
<dict> <dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<data> <data>
k1ElB+OnAYDlAZsTB5il2X25Zbo= k1ElB+OnAYDlAZsTB5il2X25Zbo=
...@@ -23,6 +27,13 @@ ...@@ -23,6 +27,13 @@
</dict> </dict>
<key>files2</key> <key>files2</key>
<dict> <dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<dict> <dict>
<key>hash2</key> <key>hash2</key>
......
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
<dict> <dict>
<key>files</key> <key>files</key>
<dict> <dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<data> <data>
k1ElB+OnAYDlAZsTB5il2X25Zbo= k1ElB+OnAYDlAZsTB5il2X25Zbo=
...@@ -23,6 +27,13 @@ ...@@ -23,6 +27,13 @@
</dict> </dict>
<key>files2</key> <key>files2</key>
<dict> <dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<dict> <dict>
<key>hash2</key> <key>hash2</key>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
@interface DashboardAPIClient : NSObject @interface DashboardAPIClient : NSObject
+ (void)fetchDashboardSummary:(void (^)(NSDictionary *data))completion; + (void)fetchDashboardSummary:(void (^)(NSDictionary *data))completion;
+ (void)fetchAnnouncements:(void (^)(NSArray *data))completion; + (void)fetchAnnouncement:(void (^)(NSArray *data))completion;
+ (void)fetchAppointments:(void (^)(NSArray *data))completion; + (void)fetchAppointments:(void (^)(NSArray *data))completion;
+ (void)fetchIssues:(void (^)(NSArray *data))completion; + (void)fetchIssues:(void (^)(NSArray *data))completion;
+ (void)fetchHeaderInfo:(void (^)(NSDictionary *data))completion; + (void)fetchHeaderInfo:(void (^)(NSDictionary *data))completion;
......
...@@ -15,18 +15,6 @@ ...@@ -15,18 +15,6 @@
}] resume]; }] resume];
} }
+ (void)fetchAnnouncements:(void (^)(NSArray *))completion {
NSURL *url = [NSURL URLWithString:@"https://jsonplaceholder.typicode.com/posts?_limit=5"];
[[[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) { completion(nil); return; }
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
completion(json);
});
}] resume];
}
+ (void)fetchAppointments:(void (^)(NSArray *))completion { + (void)fetchAppointments:(void (^)(NSArray *))completion {
NSURL *url = [NSURL URLWithString:@"https://jsonplaceholder.typicode.com/todos?_limit=5"]; NSURL *url = [NSURL URLWithString:@"https://jsonplaceholder.typicode.com/todos?_limit=5"];
[[[NSURLSession sharedSession] dataTaskWithURL:url [[[NSURLSession sharedSession] dataTaskWithURL:url
...@@ -68,57 +56,121 @@ ...@@ -68,57 +56,121 @@
} }
+ (void)fetchDashboardInfo:(void (^)(NSDictionary *data, NSError *error))completion { + (void)fetchDashboardInfo:(void (^)(NSDictionary *data, NSError *error))completion {
NSString *baseURL = @"https://kitadev.commudesk.com/api/owner/plan/getIssueUpdateAppointmentInfo"; NSString *urlString = @"https://kitadev.commudesk.com/api/owner/plan/getIssueUpdateAppointmentInfo";
NSString *token = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"; NSURL *url = [NSURL URLWithString:urlString];
// 👇 Append token as query parameter
NSString *fullURL = [NSString stringWithFormat:@"%@?token=%@", baseURL, token];
NSURL *url = [NSURL URLWithString:fullURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST"; request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; // 🪪 Authorization header
NSString *token = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA";
[request setValue:[NSString stringWithFormat:@"Bearer %@", token] forHTTPHeaderField:@"Authorization"];
// 🧱 Create multipart form-data body
NSString *boundary = [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
NSMutableData *body = [NSMutableData data];
// Helper block to append fields
void (^appendFormField)(NSString *, NSString *) = ^(NSString *key, NSString *value) {
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"%@\r\n", value] dataUsingEncoding:NSUTF8StringEncoding]];
};
// Add form fields
appendFormField(@"data[os]", @"AND");
appendFormField(@"data[drawing_plan_id]", @"135");
NSString *infoJson = @"{\"OS\":\"AND\",\"IMEI\":\"AND:2a2f13ff17b1cbb5\",\"OS_VERSION\":\"35\",\"MODEL\":\"2306EPN60G\",\"APP_VERSION\":\"1.22.26\"}";
appendFormField(@"data[information]", infoJson);
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
request.HTTPBody = body;
// 🌍 Debug logs
NSLog(@"🌍 URL: %@", urlString);
NSLog(@"🪪 Authorization: Bearer %@", token);
NSLog(@"📦 Body:\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network error: %@", error);
if (completion) completion(nil, error);
return;
}
NSError *jsonErr;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
NSLog(@"🧩 Dashboard Info response: %@", json);
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(json, jsonErr);
});
}
}];
[task resume];
}
+ (void)fetchAnnouncement:(void (^)(NSArray *data))completion {
NSURL *url = [NSURL URLWithString:
@"https://kitadev.commudesk.com/api/announcement?token=YOUR_TOKEN_HERE"];
NSDictionary *body = @{ NSDictionary *body = @{
@"os": @"AND", @"data": @{
@"drawing_plan_id": @135, @"os": @"iOS",
@"information": @{ @"information": @{
@"OS": @"AND", @"OS": @"iOS",
@"IMEI": @"AND:2a2f13ff17b1cbb5", @"IMEI": @"iOS:1234567890",
@"OS_VERSION": @"35", @"OS_VERSION": [[UIDevice currentDevice] systemVersion],
@"MODEL": @"2306EPN60G", @"MODEL": [[UIDevice currentDevice] model],
@"APP_VERSION": @"1.22.26" @"APP_VERSION": [[[NSBundle mainBundle] infoDictionary]
objectForKey:@"CFBundleShortVersionString"]
}
} }
}; };
NSError *jsonError = nil; NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError]; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError];
if (jsonError) { if (jsonError) {
if (completion) completion(nil, jsonError); NSLog(@"❌ JSON serialization failed: %@", jsonError);
if (completion) completion(@[]);
return; return;
} }
request.HTTPBody = bodyData;
NSLog(@"🌍 Final URL: %@", url.absoluteString); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSLog(@"📦 Body: %@", [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding]); request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
request.HTTPBody = jsonData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) { if (error) {
NSLog(@"❌ Network error: %@", error); NSLog(@"❌ Request failed: %@", error.localizedDescription);
if (completion) completion(nil, error); if (completion) completion(@[]);
return; return;
} }
NSError *jsonErr; NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr]; NSLog(@"✅ Announcement API response:\n%@", json);
NSLog(@"🧩 Dashboard Info response: %@", json);
NSDictionary *appData = json[@"AppData"];
if (completion) { if ([appData[@"status"] isEqualToString:@"success"]) {
dispatch_async(dispatch_get_main_queue(), ^{ NSArray *announcements = json[@"Data"];
completion(json, jsonErr); NSLog(@"📣 Got %lu announcements", (unsigned long)announcements.count);
}); if (completion) completion(announcements);
} else {
NSLog(@"⚠️ Announcement fetch failed: %@", appData[@"message"]);
if (completion) completion(@[]);
} }
}]; }];
......
...@@ -5,6 +5,27 @@ ...@@ -5,6 +5,27 @@
#import "PlanViewController.h" #import "PlanViewController.h"
#import "DashboardAPIClient.h" #import "DashboardAPIClient.h"
@interface DashboardViewController ()
@property (nonatomic, strong) NSArray *appointments;
@property (nonatomic, strong) NSArray *issues;
@property (nonatomic, assign) BOOL hasPendingAccess;
@property (nonatomic, assign) BOOL hasSignOff;
@property (nonatomic, strong) NSString *strContactlessHandoverStatus;
@property (nonatomic, strong) NSString *strContactlessFormUrl;
@property (nonatomic, strong) NSString *strPendingAccessReminder;
@property (nonatomic, assign) BOOL bln_Block_Handover;
@property (nonatomic, assign) BOOL bln_Block_Inspection;
@property (nonatomic, assign) NSInteger intPOACount;
@property (nonatomic, assign) NSInteger intPendingAccessCount;
@property (nonatomic, assign) BOOL bln_ReminderPA;
@property (nonatomic, assign) BOOL bln_POASignOff;
@property (nonatomic, strong) NSString *projectCode;
@property (nonatomic, strong) NSString *projectName;
@property (nonatomic, strong) UIImage *headerBackgroundImage;
@end
@implementation DashboardViewController { @implementation DashboardViewController {
DashboardHeaderView *_headerView; DashboardHeaderView *_headerView;
UIScrollView *_scrollView; UIScrollView *_scrollView;
...@@ -21,18 +42,42 @@ ...@@ -21,18 +42,42 @@
[self setupHeader]; [self setupHeader];
[self setupScrollContent]; [self setupScrollContent];
[self setupFooter]; [self setupFooter];
self.projectCode = @"PTD197656";
self.projectName = @"Ara E`Residence Dev";
// 👇 TEMPORARY: test real API
[DashboardAPIClient fetchDashboardInfo:^(NSDictionary *data, NSError *error) { [DashboardAPIClient fetchDashboardInfo:^(NSDictionary *data, NSError *error) {
if (error) { if (error) {
NSLog(@"❌ fetchDashboardInfo error: %@", error.localizedDescription); NSLog(@"❌ fetchDashboardInfo error: %@", error.localizedDescription);
return; return;
} }
NSLog(@"🧩 Dashboard Info response: %@", data);
NSLog(@"🧩 Dashboard Info response received");
[self processDashboardResponse:data];
// 💾 Optionally, save full JSON to a file for reference
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"DashboardInfoResponse.txt"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
[jsonData writeToFile:filePath atomically:YES];
NSLog(@"📁 Dashboard API response saved to %@", filePath);
}]; }];
[DashboardAPIClient fetchAnnouncement:^(NSDictionary *data, NSError *error) {
if (error) {
NSLog(@"❌ fetchAnnouncement error: %@", error.localizedDescription);
return;
}
NSLog(@"🗞️ Announcement response: %@", data);
// existing mock data calls // Optional: save to file for reference
[self fetchDashboardData]; NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"AnnouncementResponse.txt"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:NSJSONWritingPrettyPrinted error:nil];
[jsonData writeToFile:filePath atomically:YES];
NSLog(@"📁 Announcement response saved to %@", filePath);
}];
} }
#pragma mark - Layout #pragma mark - Layout
...@@ -56,10 +101,26 @@ ...@@ -56,10 +101,26 @@
- (void)setupHeader { - (void)setupHeader {
CGFloat headerHeight = 220.0; CGFloat headerHeight = 220.0;
_headerView = [[DashboardHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, headerHeight)]; _headerView = [[DashboardHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, headerHeight)];
[_headerView configureWithProjectName:@"Project A"
unitName:@"Unit 3A" // Remote background image URL
backgroundImage:[UIImage imageNamed:@"header_bg"] NSString *remoteHeaderURL = @"https://kitadev.commudesk.com/uploads/marketing_photo/164543837127.png";
overlayImage:[UIImage imageNamed:@"overlay"]];
// Load image asynchronously so it doesn’t block UI
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:remoteHeaderURL];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *remoteImage = nil;
if (data) {
remoteImage = [UIImage imageWithData:data];
}
self.headerBackgroundImage = remoteImage ?: [UIImage imageNamed:@"header_bg"];
dispatch_async(dispatch_get_main_queue(), ^{
[_headerView configureWithProjectName:@"Project A"
unitName:@"Unit 3A"
backgroundImage:self.headerBackgroundImage
overlayImage:[UIImage imageNamed:@"overlay"]];
});
});
[self.view addSubview:_headerView]; [self.view addSubview:_headerView];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeSystem]; UIButton *backButton = [UIButton buttonWithType:UIButtonTypeSystem];
...@@ -274,124 +335,570 @@ ...@@ -274,124 +335,570 @@
}]; }];
} }
#pragma mark - Networking #pragma mark - Dashboard Data Processor
- (void)fetchDashboardData { - (void)processDashboardResponse:(NSDictionary *)data {
__weak typeof(self) weakSelf = self; NSDictionary *appData = data[@"AppData"];
NSDictionary *mainData = data[@"Data"];
[DashboardAPIClient fetchDashboardSummary:^(NSDictionary *data) { if (![appData[@"status"] isEqualToString:@"success"] || !mainData) {
__strong typeof(weakSelf) strongSelf = weakSelf; NSLog(@"⚠️ Dashboard status not success: %@", appData[@"message"]);
NSLog(@"🏗 Dashboard Summary: %@", data); return;
// In the future: update header info (project/unit name, etc.) }
[strongSelf->_headerView configureWithProjectName:data[@"name"]
unitName:data[@"username"] NSArray *appointments = mainData[@"appointment"] ?: @[];
backgroundImage:[UIImage imageNamed:@"header_bg"] NSArray *issues = mainData[@"issue_update"] ?: @[];
overlayImage:[UIImage imageNamed:@"overlay"]]; NSNumber *paCount = mainData[@"pa_count"];
}]; NSNumber *poaCount = mainData[@"poa_count"];
NSString *accessMessage = mainData[@"access_item_message"];
[DashboardAPIClient fetchHeaderInfo:^(NSDictionary *data) { NSString *accessTNC = mainData[@"access_item_terms_and_condition"];
__strong typeof(weakSelf) strongSelf = weakSelf; NSString *handoverStatus = mainData[@"contactlessHandoverTempStatus"];
if (!strongSelf) return; NSString *handoverURL = mainData[@"contactlessHandoverTempURL"];
NSLog(@"🖼 Header info: %@", data); BOOL hasPendingAccess = (paCount && paCount.intValue > 0);
BOOL hasSignOff = (poaCount && poaCount.intValue > 0);
NSString *projectName = data[@"title"] ?: @"Project A";
NSString *unitName = @"Demo Unit 3A"; BOOL blockHandover = NO;
NSString *imageURL = data[@"url"]; BOOL blockInspection = NO;
NSInteger rectificationIssueCount = 0;
NSURL *url = [NSURL URLWithString:imageURL];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url for (NSDictionary *appt in appointments) {
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { NSString *type = [appt[@"appointment_type"] lowercaseString];
if (error) { NSString *status = [appt[@"status"] lowercaseString];
NSLog(@"❌ Failed to download image: %@", error.localizedDescription);
return; if ([type isEqualToString:@"handover"] && [status isEqualToString:@"active"]) {
blockHandover = YES;
} else if ([type isEqualToString:@"joint inspection"] && [status isEqualToString:@"active"]) {
blockInspection = YES;
} else if ([type isEqualToString:@"rectification"] && ![status isEqualToString:@"cancelled"]) {
NSArray *issuesList = appt[@"issues"];
if ([issuesList isKindOfClass:[NSArray class]]) {
rectificationIssueCount += issuesList.count;
} }
UIImage *image = [UIImage imageWithData:data]; }
if (!image) return; }
dispatch_async(dispatch_get_main_queue(), ^{ // If rectification count == pending access count, clear reminder
[strongSelf->_headerView configureWithProjectName:projectName if (paCount.intValue == rectificationIssueCount) {
unitName:unitName hasPendingAccess = NO;
backgroundImage:image }
overlayImage:[UIImage imageNamed:@"overlay"]];
}); // --- Store results ---
}]; self.appointments = appointments;
[task resume]; self.issues = issues;
NSLog(@"🖼 Header image URL: %@", imageURL); self.hasPendingAccess = hasPendingAccess;
}]; self.hasSignOff = hasSignOff;
self.intPOACount = poaCount.intValue;
self.intPendingAccessCount = paCount.intValue;
self.bln_Block_Handover = blockHandover;
self.bln_Block_Inspection = blockInspection;
self.strPendingAccessReminder = accessMessage ?: @"";
self.strContactlessHandoverStatus = handoverStatus ?: @"";
self.strContactlessFormUrl = handoverURL ?: @"";
NSLog(@"✅ Dashboard parsed:");
NSLog(@" • Appointments: %lu", (unsigned long)appointments.count);
NSLog(@" • Issues: %lu", (unsigned long)issues.count);
NSLog(@" • Pending Access: %d", hasPendingAccess);
NSLog(@" • POA Count: %ld", (long)self.intPOACount);
NSLog(@" • Block Handover: %d", blockHandover);
NSLog(@" • Block Inspection: %d", blockInspection);
dispatch_async(dispatch_get_main_queue(), ^{
[_headerView configureWithProjectName:self.projectName
unitName:self.projectCode
backgroundImage:self.headerBackgroundImage
overlayImage:[UIImage imageNamed:@"overlay"]];
[self refreshDashboardSectionsWithData:mainData];
});
}
- (void)refreshDashboardSectionsWithData:(NSDictionary *)mainData {
NSLog(@"🔄 Refreshing dashboard sections...");
// Remove any old views from scroll
for (UIView *view in _contentContainer.subviews) {
[view removeFromSuperview];
}
_currentY = 16.0;
// === PROJECT UPDATES ===
NSArray *announcements = mainData[@"announcement"];
if (announcements.count > 0) {
UIView *announcementSection = [self buildAnnouncementSection:announcements];
CGRect f = announcementSection.frame;
f.origin.y = _currentY;
announcementSection.frame = f;
[_contentContainer addSubview:announcementSection];
_currentY += CGRectGetHeight(announcementSection.frame) + 12;
}
// === APPOINTMENTS ===
NSArray *appointments = mainData[@"appointment"];
if (appointments.count > 0) {
UIView *appointmentSection = [self buildAppointmentSection:appointments];
CGRect f = appointmentSection.frame;
f.origin.y = _currentY;
appointmentSection.frame = f;
[_contentContainer addSubview:appointmentSection];
_currentY += CGRectGetHeight(appointmentSection.frame) + 12;
}
// === ISSUE UPDATES ===
NSArray *issues = mainData[@"issue_update"];
if (issues.count > 0) {
UIView *issueSection = [self buildIssueSection:issues];
CGRect f = issueSection.frame;
f.origin.y = _currentY;
issueSection.frame = f;
[_contentContainer addSubview:issueSection];
_currentY += CGRectGetHeight(issueSection.frame) + 16;
}
_contentContainer.frame = CGRectMake(0, 0, _scrollView.bounds.size.width, _currentY);
_scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, _currentY);
NSLog(@"✅ Dashboard UI refreshed with %lu announcements, %lu appointments, %lu issues",
(unsigned long)announcements.count, (unsigned long)appointments.count, (unsigned long)issues.count);
}
- (UIView *)buildAnnouncementSection:(NSArray *)announcements {
UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 160)];
// --- Title row ---
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = @"Project Update";
title.font = [UIFont boldSystemFontOfSize:18];
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
[section addSubview:title];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
// --- Horizontal scroll for cards ---
UIScrollView *hScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 40, self.view.bounds.size.width, 110)];
hScroll.showsHorizontalScrollIndicator = NO;
CGFloat x = 16;
for (NSDictionary *item in announcements) {
UIView *card = [self makeAnnouncementCard:item];
CGRect frame = card.frame;
frame.origin.x = x;
frame.origin.y = 0;
card.frame = frame;
[hScroll addSubview:card];
x += frame.size.width + 12;
}
hScroll.contentSize = CGSizeMake(x, 110);
[section addSubview:hScroll];
return section;
}
- (UIView *)buildAppointmentSection:(NSArray *)appointments {
UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 0)];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
title.text = @"Appointments";
title.font = [UIFont boldSystemFontOfSize:18];
[section addSubview:title];
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
CGFloat y = 34;
CGFloat cardWidth = self.view.bounds.size.width * 0.9; // 90% of screen width
CGFloat xCenter = (self.view.bounds.size.width - cardWidth) / 2.0;
[DashboardAPIClient fetchAnnouncements:^(NSArray *data) { NSArray *limitedAppointments = (appointments.count > 3)
__strong typeof(weakSelf) strongSelf = weakSelf; ? [appointments subarrayWithRange:NSMakeRange(0, 3)]
NSLog(@"📢 Announcements: %@", data); : appointments;
[strongSelf populateSection:@"Project Update" withData:data];
}]; for (NSDictionary *item in limitedAppointments) {
UIView *card = [self makeAppointmentCard:item width:cardWidth];
CGRect frame = card.frame;
frame.origin.x = xCenter;
frame.origin.y = y;
card.frame = frame;
[section addSubview:card];
y += frame.size.height + 12; // spacing between cards
}
[DashboardAPIClient fetchAppointments:^(NSArray *data) { section.frame = CGRectMake(0, 0, self.view.bounds.size.width, y + 10);
__strong typeof(weakSelf) strongSelf = weakSelf; return section;
NSLog(@"📅 Appointments: %@", data); }
[strongSelf populateSection:@"Appointments" withData:data];
}];
[DashboardAPIClient fetchIssues:^(NSArray *data) { - (UIView *)buildIssueSection:(NSArray *)issues {
__strong typeof(weakSelf) strongSelf = weakSelf; UIView *section = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 0)];
NSLog(@"🪲 Issues: %@", data);
[strongSelf populateSection:@"Issue Updates" withData:data]; UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
}]; title.text = @"Issue Updates";
title.font = [UIFont boldSystemFontOfSize:18];
[section addSubview:title];
title.userInteractionEnabled = YES; // 👈 enable taps
UITapGestureRecognizer *titleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:titleTap];
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_green_arrowright"]];
arrow.frame = CGRectMake(self.view.bounds.size.width - 40, 2, 24, 24);
[section addSubview:arrow];
CGFloat y = 34;
CGFloat cardWidth = self.view.bounds.size.width * 0.9; // 90% of screen width
CGFloat xCenter = (self.view.bounds.size.width - cardWidth) / 2.0;
for (NSDictionary *item in issues) {
UIView *card = [self makeIssueCard:item width:cardWidth];
CGRect frame = card.frame;
frame.origin.x = xCenter;
frame.origin.y = y;
card.frame = frame;
[section addSubview:card];
y += frame.size.height + 12;
}
section.frame = CGRectMake(0, 0, self.view.bounds.size.width, y + 10);
return section;
} }
- (UIImage *)downloadImageFrom:(NSString *)urlString { - (UILabel *)makeSectionTitle:(NSString *)titleText {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]; UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(16, 0, 200, 24)];
return data ? [UIImage imageWithData:data] : [UIImage imageNamed:@"header_bg"]; title.text = titleText;
title.font = [UIFont boldSystemFontOfSize:18];
title.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(sectionHeaderTapped:)];
[title addGestureRecognizer:tap];
return title;
}
- (UIView *)makeAnnouncementCard:(NSDictionary *)item {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 100)];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
card.layer.shadowOpacity = 0.3;
card.layer.shadowOffset = CGSizeMake(0, 1);
UILabel *date = [[UILabel alloc] initWithFrame:CGRectMake(12, 8, 200, 16)];
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *dateObj = [fmt dateFromString:item[@"created_at"]];
fmt.dateFormat = @"dd/MM/yyyy";
date.text = dateObj ? [fmt stringFromDate:dateObj] : @"--/--/----";
date.textColor = [UIColor grayColor];
date.font = [UIFont systemFontOfSize:13];
[card addSubview:date];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(12, 28, 256, 60)];
title.text = item[@"title"] ?: @"Untitled announcement";
title.numberOfLines = 3;
title.font = [UIFont boldSystemFontOfSize:16];
[card addSubview:title];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAnnouncementTap:)];
[card addGestureRecognizer:tap];
return card;
}
- (UIView *)makeAppointmentCard:(NSDictionary *)item width:(CGFloat)width {
UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 90)];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 8;
card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
card.layer.shadowOpacity = 0.3;
card.layer.shadowOffset = CGSizeMake(0, 1);
// Extract fields
NSString *appointmentType = item[@"appointment_type"] ?: @"-";
NSString *status = item[@"status"] ?: @"-";
NSString *startDate = item[@"start_date"] ?: @"-";
NSString *startTime = item[@"start_time"] ?: @"-";
NSString *buyerName = @"-";
NSArray *buyers = item[@"primary_buyer"];
if ([buyers isKindOfClass:[NSArray class]] && buyers.count > 0) {
NSDictionary *buyer = buyers.firstObject;
if ([buyer[@"name"] isKindOfClass:[NSString class]]) {
buyerName = buyer[@"name"];
}
}
// Notification Icon
UIView *iconCircle = [[UIView alloc] initWithFrame:CGRectMake(12, 14, 24, 24)];
iconCircle.backgroundColor = [UIColor colorWithRed:1.0 green:0.4 blue:0.4 alpha:1.0]; // red tone
iconCircle.layer.cornerRadius = 12;
UIImageView *bell = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"bell.fill"]];
bell.tintColor = UIColor.whiteColor;
bell.frame = CGRectMake(4, 4, 16, 16);
[iconCircle addSubview:bell];
[card addSubview:iconCircle];
// === Status ===
UILabel *statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(44, 12, 180, 26)];
statusLabel.text = status;
statusLabel.font = [UIFont boldSystemFontOfSize:17];
statusLabel.textColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // teal
[card addSubview:statusLabel];
// === Time (right aligned) ===
UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(width - 90, 12, 80, 26)];
timeLabel.textAlignment = NSTextAlignmentRight;
timeLabel.text = startTime;
timeLabel.font = [UIFont boldSystemFontOfSize:15];
[card addSubview:timeLabel];
// === Description text ===
NSString *desc = [NSString stringWithFormat:
@"You have a %@ appointment on %@, %@",
appointmentType, startDate, buyerName];
UILabel *descLabel = [[UILabel alloc] initWithFrame:CGRectMake(16, 46, width - 32, 36)];
descLabel.text = desc;
descLabel.font = [UIFont systemFontOfSize:15];
descLabel.numberOfLines = 2;
descLabel.textColor = [UIColor blackColor];
[card addSubview:descLabel];
// === Tap animation ===
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAppointmentTap:)];
[card addGestureRecognizer:tap];
return card;
} }
- (void)populateSection:(NSString *)title withData:(NSArray *)data { - (UIView *)makeIssueCard:(NSDictionary *)item width:(CGFloat)width {
CGFloat y = _currentY; UIView *card = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 110)];
card.backgroundColor = UIColor.whiteColor;
// --- Section Header --- card.layer.cornerRadius = 8;
UILabel *header = [[UILabel alloc] initWithFrame:CGRectMake(16, y, self.view.bounds.size.width - 32, 24)]; card.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor;
header.text = title; card.layer.shadowOpacity = 0.3;
header.font = [UIFont boldSystemFontOfSize:18]; card.layer.shadowOffset = CGSizeMake(0, 1);
[_contentContainer addSubview:header]; card.layer.masksToBounds = NO;
y += 34; // === Extract fields ===
NSString *status = item[@"status_external"] ?: @"";
// --- Special handling for Project Update --- NSString *issueRef = item[@"issue_reference"] ?: @"";
if ([title isEqualToString:@"Project Update"]) { NSString *createdAt = item[@"created_at"] ?: @"";
UIScrollView *hScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, y, self.view.bounds.size.width, 140)]; NSString *formattedDate = [self formatDate:createdAt];
hScroll.showsHorizontalScrollIndicator = NO; UIColor *statusColor = [self colorForStatus:status];
hScroll.autoresizingMask = UIViewAutoresizingFlexibleWidth;
// === Determine issue image (like renderOnlineImage) ===
CGFloat x = 16; NSString *issueImage = item[@"image"];
for (NSDictionary *item in data) { if (!issueImage || [issueImage isEqualToString:@""]) {
UIView *card = [self createCardWithText:item[@"title"] ?: @"No title" NSArray *firstArray = item[@"first"];
frame:CGRectMake(x, 0, 220, 120)]; NSArray *lastArray = item[@"last"];
[hScroll addSubview:card]; if (firstArray.count > 0 && firstArray[0][@"image"])
x += 236; // card width + spacing issueImage = firstArray[0][@"image"];
else if (lastArray.count > 0 && lastArray[0][@"image"])
issueImage = lastArray[0][@"image"];
}
// === Top row: Status + Date ===
CGFloat iconSize = 16;
UIView *statusDot = [[UIView alloc] initWithFrame:CGRectMake(12, 12, iconSize, iconSize)];
statusDot.layer.cornerRadius = iconSize / 2;
statusDot.backgroundColor = statusColor;
[card addSubview:statusDot];
UILabel *statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(statusDot.frame) + 6, 8, width - 160, 22)];
statusLabel.text = status.length ? status : @"Unknown";
statusLabel.textColor = statusColor;
statusLabel.font = [UIFont boldSystemFontOfSize:15];
[card addSubview:statusLabel];
UILabel *dateLabel = [[UILabel alloc] initWithFrame:CGRectMake(width - 130, 10, 118, 18)];
dateLabel.textAlignment = NSTextAlignmentRight;
dateLabel.text = formattedDate;
dateLabel.textColor = [UIColor grayColor];
dateLabel.font = [UIFont systemFontOfSize:13];
[card addSubview:dateLabel];
// === Divider line ===
// UIView *divider = [[UIView alloc] initWithFrame:CGRectMake(12, 36, width - 24, 0.5)];
// divider.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1];
// [card addSubview:divider];
// === Image + Info Row ===
CGFloat imageSize = 44;
UIImageView *thumbView = [[UIImageView alloc] initWithFrame:CGRectMake(12, 48, imageSize, imageSize)];
thumbView.layer.cornerRadius = 6;
thumbView.clipsToBounds = YES;
thumbView.contentMode = UIViewContentModeScaleAspectFill;
if (issueImage && issueImage.length > 0) {
NSURL *url = [NSURL URLWithString:issueImage];
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
thumbView.image = [UIImage imageWithData:data];
} else {
thumbView.image = [UIImage imageNamed:@"small_placeholder"];
} }
} else {
thumbView.image = [UIImage imageNamed:@"small_placeholder"];
}
[card addSubview:thumbView];
hScroll.contentSize = CGSizeMake(x, 140); // === Text beside image ===
[_contentContainer addSubview:hScroll]; CGFloat textX = CGRectGetMaxX(thumbView.frame) + 10;
UILabel *infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(textX, 46, width - textX - 12, 50)];
infoLabel.numberOfLines = 3;
infoLabel.textColor = [UIColor blackColor];
infoLabel.font = [UIFont systemFontOfSize:15];
y += 150; // spacing after horizontal list infoLabel.attributedText = [self notificationTextForIssue:item];
[card addSubview:infoLabel];
// === Tap gesture ===
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleIssueTap:)];
[card addGestureRecognizer:tap];
return card;
}
// Convert hex string (like "#F14915") to UIColor
- (UIColor *)colorFromHex:(NSString *)hexString {
unsigned rgbValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexString];
[scanner setScanLocation:1];
[scanner scanHexInt:&rgbValue];
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
green:((rgbValue & 0x00FF00) >> 8) / 255.0
blue:(rgbValue & 0x0000FF) / 255.0
alpha:1.0];
}
// Status → Color
- (UIColor *)colorForStatus:(NSString *)status {
if ([status isEqualToString:@"Lodged"]) return [self colorFromHex:@"#2952D6"];
if ([status isEqualToString:@"New"]) return [self colorFromHex:@"#F99100"];
if ([status isEqualToString:@"Pending Start"]) return [self colorFromHex:@"#BD5607"];
if ([status isEqualToString:@"Withdraw"] || [status isEqualToString:@"WITHDRAW"] || [status isEqualToString:@"VOID"])
return [self colorFromHex:@"#EFC089"];
if ([status isEqualToString:@"WIP"] || [status isEqualToString:@"W.I.P"])
return [self colorFromHex:@"#F6C607"];
if ([status isEqualToString:@"Completed"]) return [self colorFromHex:@"#DF00D0"];
if ([status isEqualToString:@"Pending Access"]) return [self colorFromHex:@"#F14915"];
if ([status isEqualToString:@"Pending Owner Acceptance"]) return [self colorFromHex:@"#EF7215"];
if ([status isEqualToString:@"Rejected"]) return [self colorFromHex:@"#FF3939"];
if ([status isEqualToString:@"Close Internal"]) return [self colorFromHex:@"#58BA63"];
if ([status isEqualToString:@"Close External"]) return [self colorFromHex:@"#1C7C00"];
return [UIColor darkGrayColor];
}
- (NSAttributedString *)notificationTextForIssue:(NSDictionary *)issue {
NSString *status = issue[@"status_external"] ?: @"";
NSString *issueRef = issue[@"issue_reference"] ?: @"";
NSString *projectName = self.projectName; // Replace with GLOBALS.PROJECT_NAME if available
// Base sentence builder
NSString *text = @"";
if ([status isEqualToString:@"New"]) {
text = [NSString stringWithFormat:@"New issue %@ from %@ has been created.", issueRef, projectName];
} else if ([status isEqualToString:@"Lodged"]) {
text = [NSString stringWithFormat:@"%@ from %@ has been lodged.", issueRef, projectName];
} else if ([status isEqualToString:@"Pending Start"]) {
text = [NSString stringWithFormat:@"%@ from %@ is pending start.", issueRef, projectName];
} else if ([status isEqualToString:@"Pending Access"]) {
text = [NSString stringWithFormat:@"%@ from %@ is pending access.", issueRef, projectName];
} else if ([status isEqualToString:@"W.I.P"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to W.I.P.", issueRef, projectName];
} else if ([status isEqualToString:@"Not Me"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Not Me.", issueRef, projectName];
} else if ([status isEqualToString:@"Reassign"]) {
text = [NSString stringWithFormat:@"%@ from %@ needs to be reassigned.", issueRef, projectName];
} else if ([status isEqualToString:@"Completed"]) {
text = [NSString stringWithFormat:@"%@ from %@ is completed.", issueRef, projectName];
} else if ([status isEqualToString:@"Redo"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Redo.", issueRef, projectName];
} else if ([status isEqualToString:@"Close Internal"]) {
text = [NSString stringWithFormat:@"%@ from %@ is closed internally.", issueRef, projectName];
} else if ([status isEqualToString:@"Close External"]) {
text = [NSString stringWithFormat:@"%@ from %@ is closed externally.", issueRef, projectName];
} else if ([status isEqualToString:@"Rejected"] ||
[status isEqualToString:@"Decline"] ||
[status isEqualToString:@"Withdraw"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to %@.", issueRef, projectName, status];
} else if ([status isEqualToString:@"Pending Owner Acceptance"] ||
[status isEqualToString:@"POA"] ||
[status isEqualToString:@"poa"]) {
text = [NSString stringWithFormat:@"%@ from %@ has changed status to Pending Owner Acceptance.", issueRef, projectName];
} else { } else {
// --- Vertical cards for all other sections --- text = [NSString stringWithFormat:@"Issue %@ (%@)", issueRef, status];
for (NSDictionary *item in data) { }
UIView *card = [self createCardWithText:item[@"title"] ?: @"No title"
frame:CGRectMake(16, y, self.view.bounds.size.width - 32, 100)]; // === Create the base attributed string ===
[_contentContainer addSubview:card]; NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] initWithString:text
y += 120; attributes:@{
} NSFontAttributeName: [UIFont systemFontOfSize:15],
NSForegroundColorAttributeName: UIColor.blackColor
}];
y += 20; // extra bottom padding between sections // === Highlight the status substring ===
UIColor *statusColor = [self colorForStatus:status];
NSRange statusRange = [text rangeOfString:status options:NSCaseInsensitiveSearch];
if (statusRange.location != NSNotFound) {
[attrText addAttributes:@{
NSFontAttributeName: [UIFont boldSystemFontOfSize:15],
NSForegroundColorAttributeName: statusColor
} range:statusRange];
} }
_currentY = y; return attrText;
_contentContainer.frame = CGRectMake(0, 0, _scrollView.bounds.size.width, _currentY); }
_scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, _currentY);
- (NSString *)formatDate:(NSString *)inputDateString {
if (!inputDateString || inputDateString.length == 0) return @"--/--/----";
NSDateFormatter *inputFmt = [[NSDateFormatter alloc] init];
inputFmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *date = [inputFmt dateFromString:inputDateString];
if (!date) return inputDateString;
NSDateFormatter *outputFmt = [[NSDateFormatter alloc] init];
outputFmt.dateFormat = @"dd/MM/yyyy";
return [outputFmt stringFromDate:date];
} }
#pragma mark - Individual Card Taps
- (void)handleAnnouncementTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"📰 Announcement card tapped");
// TODO: open AnnouncementDetailsScreen equivalent in future
}
- (void)handleAppointmentTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"📅 Appointment card tapped");
// TODO: open AppointmentDetails in future
}
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
UIView *card = gesture.view;
[self animateTapForView:card];
NSLog(@"🪲 Issue card tapped");
// TODO: open IssueDetails later
}
- (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
......
{
"images" : [
{
"filename" : "overlay.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
<dict> <dict>
<key>files</key> <key>files</key>
<dict> <dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<data> <data>
k1ElB+OnAYDlAZsTB5il2X25Zbo= k1ElB+OnAYDlAZsTB5il2X25Zbo=
...@@ -23,6 +27,13 @@ ...@@ -23,6 +27,13 @@
</dict> </dict>
<key>files2</key> <key>files2</key>
<dict> <dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<dict> <dict>
<key>hash2</key> <key>hash2</key>
......
...@@ -4,6 +4,10 @@ ...@@ -4,6 +4,10 @@
<dict> <dict>
<key>files</key> <key>files</key>
<dict> <dict>
<key>Assets.car</key>
<data>
KnMHH8GU4NZESWbs6/0uU5WX8uA=
</data>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<data> <data>
k1ElB+OnAYDlAZsTB5il2X25Zbo= k1ElB+OnAYDlAZsTB5il2X25Zbo=
...@@ -23,6 +27,13 @@ ...@@ -23,6 +27,13 @@
</dict> </dict>
<key>files2</key> <key>files2</key>
<dict> <dict>
<key>Assets.car</key>
<dict>
<key>hash2</key>
<data>
aLAhYopvL1Lb4CTMG5A8x8RAmnkpXwHQbjsJdpq8gYg=
</data>
</dict>
<key>Headers/QmsPlugin.h</key> <key>Headers/QmsPlugin.h</key>
<dict> <dict>
<key>hash2</key> <key>hash2</key>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment