Commit 7735252a authored by Wei Han's avatar Wei Han

help and announcement screen

parent 72a404a7
...@@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface AnnouncementDetailsViewController : UIViewController @interface AnnouncementDetailsViewController : UIViewController
@property (nonatomic, strong) NSDictionary *data;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
......
...@@ -2,12 +2,24 @@ ...@@ -2,12 +2,24 @@
#import "AnnounceDetailsViewController.h" #import "AnnounceDetailsViewController.h"
#import "APIClient.h" #import "APIClient.h"
#import "ImageCacheHelper.h"
@interface AnnouncementDetailsViewController () @interface AnnouncementDetailsViewController () <UITextViewDelegate>
// Header (existing) // Header (existing)
@property (nonatomic, strong) UIView *headerBar; @property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton; @property (nonatomic, strong) UIButton *backButton;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *announcementImage;
@property (nonatomic, strong) UILabel *announcementTitleLabel;
@property (nonatomic, strong) UILabel *dateLabel;
@property (nonatomic, strong) UITextView *descriptionTextView;
@property (nonatomic, assign) CGFloat imageHeight;
@end @end
@implementation AnnouncementDetailsViewController @implementation AnnouncementDetailsViewController
...@@ -17,6 +29,11 @@ ...@@ -17,6 +29,11 @@
self.view.backgroundColor = UIColor.whiteColor; self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader]; [self setupHeader];
[self setupContentView];
if (self.data) {
[self configureWithData:self.data];
}
} }
#pragma mark - Header #pragma mark - Header
...@@ -68,11 +85,165 @@ ...@@ -68,11 +85,165 @@
]]; ]];
} }
#pragma mark - Content View
- (void)setupContentView {
// Scroll view
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
[NSLayoutConstraint activateConstraints:@[
[self.scrollView.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor],
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
// Content view inside scroll
self.contentView = [[UIView alloc] init];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.contentView];
[NSLayoutConstraint activateConstraints:@[
[self.contentView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
[self.contentView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
[self.contentView.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
[self.contentView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
[self.contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor], // Important!
]];
// Image
self.announcementImage = [[UIImageView alloc] init];
self.announcementImage.contentMode = UIViewContentModeScaleAspectFit;
self.announcementImage.translatesAutoresizingMaskIntoConstraints = NO;
self.announcementImage.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(viewImageFullScreen)];
[self.announcementImage addGestureRecognizer:tapGesture];
[self.contentView addSubview:self.announcementImage];
self.imageHeight = 375; // default
[NSLayoutConstraint activateConstraints:@[
[self.announcementImage.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.announcementImage.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.announcementImage.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.announcementImage.heightAnchor constraintEqualToConstant:self.imageHeight]
]];
// Title
self.announcementTitleLabel = [[UILabel alloc] init];
self.announcementTitleLabel.font = [UIFont boldSystemFontOfSize:20];
self.announcementTitleLabel.numberOfLines = 0;
self.announcementTitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.announcementTitleLabel];
[NSLayoutConstraint activateConstraints:@[
[self.announcementTitleLabel.topAnchor constraintEqualToAnchor:self.announcementImage.bottomAnchor constant:16],
[self.announcementTitleLabel.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.announcementTitleLabel.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
]];
// Date
self.dateLabel = [[UILabel alloc] init];
self.dateLabel.font = [UIFont italicSystemFontOfSize:14];
self.dateLabel.textColor = [UIColor grayColor];
self.dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.dateLabel];
[NSLayoutConstraint activateConstraints:@[
[self.dateLabel.topAnchor constraintEqualToAnchor:self.announcementTitleLabel.bottomAnchor constant:8],
[self.dateLabel.leadingAnchor constraintEqualToAnchor:self.announcementTitleLabel.leadingAnchor],
[self.dateLabel.trailingAnchor constraintEqualToAnchor:self.announcementTitleLabel.trailingAnchor],
]];
// Description (UITextView for selectable text + HTML rendering)
self.descriptionTextView = [[UITextView alloc] init];
self.descriptionTextView.font = [UIFont systemFontOfSize:16];
self.descriptionTextView.editable = NO;
self.descriptionTextView.scrollEnabled = NO; // Let scrollView handle it
self.descriptionTextView.dataDetectorTypes = UIDataDetectorTypeLink;
self.descriptionTextView.translatesAutoresizingMaskIntoConstraints = NO;
self.descriptionTextView.linkTextAttributes = @{NSForegroundColorAttributeName: UIColor.systemBlueColor};
self.descriptionTextView.delegate = self;
[self.contentView addSubview:self.descriptionTextView];
[NSLayoutConstraint activateConstraints:@[
[self.descriptionTextView.topAnchor constraintEqualToAnchor:self.dateLabel.bottomAnchor constant:12],
[self.descriptionTextView.leadingAnchor constraintEqualToAnchor:self.announcementTitleLabel.leadingAnchor],
[self.descriptionTextView.trailingAnchor constraintEqualToAnchor:self.announcementTitleLabel.trailingAnchor],
[self.descriptionTextView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16]
]];
}
- (void)configureWithData:(NSDictionary *)data {
self.announcementTitleLabel.text = data[@"title"];
NSString *descHTML = data[@"description"];
NSAttributedString *attrDesc = [[NSAttributedString alloc] initWithData:[descHTML dataUsingEncoding:NSUnicodeStringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
documentAttributes:nil
error:nil];
self.descriptionTextView.attributedText = attrDesc;
NSString *dateStr = data[@"start_date"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSDate *date = [formatter dateFromString:dateStr];
NSDateFormatter *displayFormatter = [[NSDateFormatter alloc] init];
displayFormatter.dateFormat = @"dd MMM yyyy";
self.dateLabel.text = [displayFormatter stringFromDate:date];
NSString *imageURL = data[@"image"];
if (imageURL.length > 0) {
NSURL *url = [NSURL URLWithString:imageURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
if (!image) return;
UIImage *img = image;
dispatch_async(dispatch_get_main_queue(), ^{
self.announcementImage.image = img;
for (NSLayoutConstraint *c in self.announcementImage.constraints) {
if (c.firstAttribute == NSLayoutAttributeHeight) {
[self.announcementImage removeConstraint:c];
}
}
CGFloat ratio = img.size.height / img.size.width;
[self.announcementImage.heightAnchor constraintEqualToAnchor:self.announcementImage.widthAnchor multiplier:ratio].active = YES;
});
}];
}
);
}
}
#pragma mark - helpers #pragma mark - helpers
- (void)onBack { - (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil];
} }
- (void)viewImageFullScreen {
if (!self.announcementImage.image) return;
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = UIColor.blackColor;
UIImageView *imgView = [[UIImageView alloc] initWithImage:self.announcementImage.image];
imgView.contentMode = UIViewContentModeScaleAspectFit;
imgView.frame = vc.view.bounds;
imgView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[vc.view addSubview:imgView];
// Tap to dismiss
UITapGestureRecognizer *dismissTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissFullScreenImage:)];
[vc.view addGestureRecognizer:dismissTap];
[self presentViewController:vc animated:YES completion:nil];
}
- (void)dismissFullScreenImage:(UITapGestureRecognizer *)tap {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end @end
// AnnounceViewController.mm // AnnouncementViewController.mm
#import "AnnounceViewController.h" #import "AnnounceViewController.h"
#import "APIClient.h" #import "APIClient.h"
#import "ImageCacheHelper.h"
#import "AnnounceDetailsViewController.h"
@interface AnnouncementCell : UITableViewCell
@property (nonatomic, strong) UIView *cardView;
@property (nonatomic, strong) UIImageView *announcementImage;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *descriptionLabel;
@property (nonatomic, strong) UILabel *dateLabel;
@property (nonatomic, strong) NSLayoutConstraint *imageAspectConstraint;
- (void)configureWithData:(NSDictionary *)data;
@end
@implementation AnnouncementCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.backgroundColor = UIColor.clearColor;
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self setupViews];
}
return self;
}
- (void)setupViews {
// Card container
self.cardView = [[UIView alloc] init];
self.cardView.backgroundColor = UIColor.whiteColor;
self.cardView.layer.cornerRadius = 8;
self.cardView.layer.shadowColor = [UIColor blackColor].CGColor;
self.cardView.layer.shadowOpacity = 0.1;
self.cardView.layer.shadowOffset = CGSizeMake(0, 2);
self.cardView.layer.shadowRadius = 4;
self.cardView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.cardView];
[NSLayoutConstraint activateConstraints:@[
[self.cardView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:12],
[self.cardView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor],
[self.cardView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.cardView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
]];
// Image
self.announcementImage = [[UIImageView alloc] init];
self.announcementImage.contentMode = UIViewContentModeScaleAspectFit;
self.announcementImage.clipsToBounds = YES;
self.announcementImage.layer.cornerRadius = 6;
self.announcementImage.translatesAutoresizingMaskIntoConstraints = NO;
[self.cardView addSubview:self.announcementImage];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.cardView addSubview:self.titleLabel];
@interface AnnouncementViewController () // Description
// Header (existing) self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.font = [UIFont systemFontOfSize:14];
self.descriptionLabel.numberOfLines = 4; // infinite lines
self.descriptionLabel.textColor = [UIColor darkGrayColor];
self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.cardView addSubview:self.descriptionLabel];
// Date
self.dateLabel = [[UILabel alloc] init];
self.dateLabel.font = [UIFont italicSystemFontOfSize:12];
self.dateLabel.textColor = [UIColor grayColor];
self.dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.cardView addSubview:self.dateLabel];
// Constraints
[NSLayoutConstraint activateConstraints:@[
// Image
[self.announcementImage.topAnchor constraintEqualToAnchor:self.cardView.topAnchor constant:12],
[self.announcementImage.leadingAnchor constraintEqualToAnchor:self.cardView.leadingAnchor constant:12],
[self.announcementImage.widthAnchor constraintEqualToConstant:64],
// Title
[self.titleLabel.topAnchor constraintEqualToAnchor:self.cardView.topAnchor constant:12],
[self.titleLabel.leadingAnchor constraintEqualToAnchor:self.announcementImage.trailingAnchor constant:12],
[self.titleLabel.trailingAnchor constraintEqualToAnchor:self.cardView.trailingAnchor constant:-12],
// Description
[self.descriptionLabel.topAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:4],
[self.descriptionLabel.leadingAnchor constraintEqualToAnchor:self.titleLabel.leadingAnchor],
[self.descriptionLabel.trailingAnchor constraintEqualToAnchor:self.titleLabel.trailingAnchor],
// Date
[self.dateLabel.topAnchor constraintEqualToAnchor:self.descriptionLabel.bottomAnchor constant:4],
[self.dateLabel.leadingAnchor constraintEqualToAnchor:self.titleLabel.leadingAnchor],
[self.dateLabel.trailingAnchor constraintEqualToAnchor:self.titleLabel.trailingAnchor],
[self.dateLabel.bottomAnchor constraintEqualToAnchor:self.cardView.bottomAnchor constant:-12],
]];
// Image aspect constraint placeholder
self.imageAspectConstraint = [self.announcementImage.heightAnchor constraintEqualToConstant:64];
self.imageAspectConstraint.active = YES;
}
- (void)configureWithData:(NSDictionary *)data {
self.titleLabel.text = data[@"title"];
// HTML description
NSString *descHTML = data[@"description"];
if (descHTML.length > 0) {
NSAttributedString *attrDesc = [[NSAttributedString alloc] initWithData:[descHTML dataUsingEncoding:NSUnicodeStringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
documentAttributes:nil
error:nil];
self.descriptionLabel.attributedText = attrDesc;
} else {
self.descriptionLabel.text = @"";
}
// Date formatting
NSString *dateStr = data[@"start_date"];
if (dateStr.length > 0) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSDate *date = [formatter dateFromString:dateStr];
NSDateFormatter *displayFormatter = [[NSDateFormatter alloc] init];
displayFormatter.dateFormat = @"dd MMM yyyy";
self.dateLabel.text = [displayFormatter stringFromDate:date];
} else {
self.dateLabel.text = @"";
}
// Image
NSString *imageURL = data[@"image"];
if (imageURL.length > 0) {
NSURL *url = [NSURL URLWithString:imageURL];
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
if (!image) return;
dispatch_async(dispatch_get_main_queue(), ^{
self.announcementImage.image = image;
// Update aspect ratio
[NSLayoutConstraint deactivateConstraints:@[self.imageAspectConstraint]];
CGFloat ratio = image.size.height / image.size.width;
self.imageAspectConstraint = [self.announcementImage.heightAnchor constraintEqualToAnchor:self.announcementImage.widthAnchor multiplier:ratio];
self.imageAspectConstraint.active = YES;
});
}];
} else {
self.announcementImage.image = nil;
[NSLayoutConstraint deactivateConstraints:@[self.imageAspectConstraint]];
self.imageAspectConstraint = [self.announcementImage.heightAnchor constraintEqualToConstant:0];
self.imageAspectConstraint.active = YES;
}
}
@end
@interface AnnouncementViewController () <UITableViewDelegate, UITableViewDataSource>
// Header
@property (nonatomic, strong) UIView *headerBar; @property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton; @property (nonatomic, strong) UIButton *backButton;
// Table
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *announcement;
@end @end
@implementation AnnouncementViewController @implementation AnnouncementViewController
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor; self.view.backgroundColor = UIColor.whiteColor;
// Dummy data
self.announcement = @[
@{@"title": @"Announcement 1", @"description": @"This is a test announcement 1.", @"date": @"2026-01-26"},
@{@"title": @"Announcement 2", @"description": @"This is a test announcement 2.", @"date": @"2026-01-25"},
@{@"title": @"Announcement 3", @"description": @"This is a test announcement 3.", @"date": @"2026-01-24"}
];
[self setupHeader]; [self setupHeader];
[self setupTableView];
[self requestAnnouncement];
} }
#pragma mark - Header #pragma mark - Header
- (void)setupHeader { - (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide; UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
//Header
self.headerBar = [[UIView alloc] init]; self.headerBar = [[UIView alloc] init];
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
[self.view addSubview:self.headerBar]; [self.view addSubview:self.headerBar];
...@@ -38,14 +208,12 @@ ...@@ -38,14 +208,12 @@
[self.headerBar.heightAnchor constraintEqualToConstant:44], [self.headerBar.heightAnchor constraintEqualToConstant:44],
]]; ]];
// Title
self.titleLabel = [[UILabel alloc] init]; self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Announcement"; self.titleLabel.text = @"Announcement";
self.titleLabel.textColor = UIColor.whiteColor; self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium]; self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter; self.titleLabel.textAlignment = NSTextAlignmentCenter;
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"] forState:UIControlStateNormal]; [self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor; self.backButton.tintColor = UIColor.whiteColor;
...@@ -69,6 +237,97 @@ ...@@ -69,6 +237,97 @@
]]; ]];
} }
#pragma mark - Table
- (void)setupTableView {
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.tableFooterView = [UIView new]; // hide empty rows
[self.view addSubview:self.tableView];
self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView registerClass:[AnnouncementCell class] forCellReuseIdentifier:@"AnnouncementCell"];
[NSLayoutConstraint activateConstraints:@[
[self.tableView.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor],
[self.tableView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.tableView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.tableView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.announcement.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
AnnouncementCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AnnouncementCell" forIndexPath:indexPath];
NSDictionary *announcement = self.announcement[indexPath.row]; // or self.announcement when API data is ready
// Configure the cell
[cell configureWithData:announcement];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *announcement = self.announcement[indexPath.row];
NSLog(@"Selected announcement: %@", announcement[@"title"]);
AnnouncementDetailsViewController *vc = [[AnnouncementDetailsViewController alloc] init];
vc.data = announcement;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
#pragma mark - API
- (void)requestAnnouncement {
[APIClient requestAnnouncement:^(BOOL success, NSDictionary *data, NSError *error) {
// Save debug file
NSString *plainText = [NSString stringWithFormat:@"%@", data];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"announcement.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 announcement saved as plain text to: %@", filePath);
if (error || !data) {
NSLog(@"❌ announcement API failed: %@", error);
return;
}
if (data[@"status_code"]) {
NSLog(@"❌ announcement status error: %@", data[@"message"]);
return;
}
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ announcement failure: %@", appData[@"message"]);
return;
}
NSArray *announcement = data[@"Data"];
if (![announcement isKindOfClass:[NSArray class]]) {
NSLog(@"❌ Invalid announcement format");
return;
}
self.announcement = announcement;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
}
#pragma mark - helpers #pragma mark - helpers
- (void)onBack { - (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil];
......
...@@ -275,6 +275,7 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath { ...@@ -275,6 +275,7 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *projectId = [unit[@"project_id"] description]; NSString *projectId = [unit[@"project_id"] description];
NSString *drawingPlanId = [unit[@"id"] description]; NSString *drawingPlanId = [unit[@"id"] description];
[APIConfig setDrawingPlanId:drawingPlanId]; [APIConfig setDrawingPlanId:drawingPlanId];
NSLog(@"lotid: %@", unit[@"lot_id"]);
NSLog(@"self.projectLogo: %@", self.projectLogo); NSLog(@"self.projectLogo: %@", self.projectLogo);
DashboardViewController *vc = [[DashboardViewController alloc] init]; DashboardViewController *vc = [[DashboardViewController alloc] init];
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#import "ImageCacheHelper.h" #import "ImageCacheHelper.h"
#import "ProfileViewController.h" #import "ProfileViewController.h"
#import "NotificationViewController.h" #import "NotificationViewController.h"
#import "HelpViewController.h"
#import "AnnounceViewController.h"
static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification"; static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification";
...@@ -228,13 +230,16 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific ...@@ -228,13 +230,16 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
[self presentViewController:nextVC animated:YES completion:nil]; [self presentViewController:nextVC animated:YES completion:nil];
} }
else if ([action isEqualToString:@"announcement"]) { else if ([action isEqualToString:@"announcement"]) {
// [self openAnnouncement];
NSLog(@"navigating to Announcement"); NSLog(@"navigating to Announcement");
AnnouncementViewController *nextVC = [[AnnouncementViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
} }
else if ([action isEqualToString:@"help"]) { else if ([action isEqualToString:@"help"]) {
// [self openHelp];
NSLog(@"navigating to Help"); NSLog(@"navigating to Help");
HelpViewController *nextVC = [[HelpViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
} }
else if ([action isEqualToString:@"sync"]) { else if ([action isEqualToString:@"sync"]) {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#import "NotificationViewController.h" #import "NotificationViewController.h"
#import "APIClient.h" #import "APIClient.h"
#import "ImageCacheHelper.h"
@interface NotificationViewController () @interface NotificationViewController ()
// Header (existing) // Header (existing)
...@@ -12,6 +13,8 @@ ...@@ -12,6 +13,8 @@
@property (nonatomic, strong) UIView *contentView; @property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIView *footerView; @property (nonatomic, strong) UIView *footerView;
@property (nonatomic, strong) UIStackView *stackView; @property (nonatomic, strong) UIStackView *stackView;
@property (nonatomic, strong) NSArray *notifications;
@property (nonatomic, strong) NSArray *projectList;
@end @end
@implementation NotificationViewController @implementation NotificationViewController
...@@ -20,11 +23,12 @@ ...@@ -20,11 +23,12 @@
[super viewDidLoad]; [super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor; self.view.backgroundColor = UIColor.whiteColor;
[self requestProjectList];
[self setupHeader]; [self setupHeader];
[self setupScrollView]; [self setupScrollView];
[self setupFooterButton]; [self setupFooterButton];
[self setupNotificationList]; [self setupNotificationList];
[self addDummyNotifications]; [self requestNotification];
} }
#pragma mark - Header #pragma mark - Header
...@@ -135,47 +139,133 @@ ...@@ -135,47 +139,133 @@
]]; ]];
} }
- (UIView *)createNotificationCardWithText:(NSString *)text date:(NSString *)date { - (UIView *)createNotificationCard:(NSDictionary *)item {
UIView *card = [[UIView alloc] init]; UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.systemGray6Color;
card.layer.cornerRadius = 12; card.layer.cornerRadius = 12;
card.translatesAutoresizingMaskIntoConstraints = NO; card.translatesAutoresizingMaskIntoConstraints = NO;
card.backgroundColor = UIColor.whiteColor;
UIImageView *icon = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"bell.fill"]]; card.layer.shadowColor = UIColor.blackColor.CGColor;
icon.tintColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; card.layer.shadowOpacity = 0.05;
card.layer.shadowRadius = 6;
card.layer.shadowOffset = CGSizeMake(0, 2);
card.layer.borderColor = UIColor.systemGray5Color.CGColor;
card.layer.borderWidth = 0.5;
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(onNotificationTap:)];
[card addGestureRecognizer:tap];
card.userInteractionEnabled = YES;
UIImageView *icon = [[UIImageView alloc] init];
icon.translatesAutoresizingMaskIntoConstraints = NO; icon.translatesAutoresizingMaskIntoConstraints = NO;
icon.contentMode = UIViewContentModeScaleAspectFit;
icon.clipsToBounds = YES;
icon.layer.cornerRadius = 12;
NSString *logoURL = item[@"project_logo"];
if (logoURL.length > 0) {
NSURL *url = [NSURL URLWithString:logoURL];
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
icon.image = image;
UIImage *img = image;
icon.image = img;
// Remove old aspect ratio if exists
for (NSLayoutConstraint *c in icon.constraints) {
if (c.firstAttribute == NSLayoutAttributeHeight &&
c.secondAttribute == NSLayoutAttributeWidth) {
[icon removeConstraint:c];
}
}
CGFloat ratio = img.size.height / img.size.width;
NSLayoutConstraint *aspect =
[icon.heightAnchor constraintEqualToAnchor:icon.widthAnchor multiplier:ratio];
aspect.active = YES;
});
}
}];
}
UILabel *projectLabel = [[UILabel alloc] init];
projectLabel.text = item[@"project_name"];
projectLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightBold];
projectLabel.textColor = UIColor.labelColor;
projectLabel.numberOfLines = 1;
projectLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *messageLabel = [[UILabel alloc] init]; UILabel *messageLabel = [[UILabel alloc] init];
messageLabel.text = text; messageLabel.text = item[@"message"];
messageLabel.numberOfLines = 0; messageLabel.numberOfLines = 0;
messageLabel.font = [UIFont systemFontOfSize:15]; messageLabel.font = [UIFont systemFontOfSize:15];
messageLabel.translatesAutoresizingMaskIntoConstraints = NO; messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *dateLabel = [[UILabel alloc] init]; UILabel *dateLabel = [[UILabel alloc] init];
dateLabel.text = date; dateLabel.text = item[@"date_time"];
dateLabel.font = [UIFont systemFontOfSize:12]; dateLabel.font = [UIFont systemFontOfSize:12];
dateLabel.textColor = UIColor.secondaryLabelColor; dateLabel.textColor = UIColor.secondaryLabelColor;
dateLabel.translatesAutoresizingMaskIntoConstraints = NO; dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
UIView *unreadDot = [[UIView alloc] init];
unreadDot.backgroundColor = [UIColor systemOrangeColor];
unreadDot.layer.cornerRadius = 4;
unreadDot.translatesAutoresizingMaskIntoConstraints = NO;
CGFloat dotSize = 8;
UIView *textContainer = [[UIView alloc] init];
textContainer.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:icon]; [card addSubview:icon];
[card addSubview:messageLabel]; [card addSubview:textContainer];
[card addSubview:dateLabel];
[textContainer addSubview:projectLabel];
[textContainer addSubview:messageLabel];
[textContainer addSubview:dateLabel];
[textContainer addSubview:unreadDot];
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
// Logo
[icon.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12], [icon.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[icon.topAnchor constraintEqualToAnchor:card.topAnchor constant:12], [icon.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[icon.widthAnchor constraintEqualToConstant:24], [icon.widthAnchor constraintEqualToConstant:64],
[icon.heightAnchor constraintEqualToConstant:24], [icon.heightAnchor constraintLessThanOrEqualToConstant:64],
[messageLabel.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:12], // Text container
[messageLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12], [textContainer.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:12],
[messageLabel.topAnchor constraintEqualToAnchor:card.topAnchor constant:12], [textContainer.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[textContainer.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[dateLabel.leadingAnchor constraintEqualToAnchor:messageLabel.leadingAnchor], [textContainer.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-12],
[dateLabel.topAnchor constraintEqualToAnchor:messageLabel.bottomAnchor constant:8],
[dateLabel.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-12], // Project name
[projectLabel.topAnchor constraintEqualToAnchor:textContainer.topAnchor],
[projectLabel.leadingAnchor constraintEqualToAnchor:textContainer.leadingAnchor],
[projectLabel.trailingAnchor constraintEqualToAnchor:textContainer.trailingAnchor],
// Message
[messageLabel.topAnchor constraintEqualToAnchor:projectLabel.bottomAnchor constant:4],
[messageLabel.leadingAnchor constraintEqualToAnchor:textContainer.leadingAnchor],
[messageLabel.trailingAnchor constraintEqualToAnchor:textContainer.trailingAnchor],
// Date
[dateLabel.topAnchor constraintEqualToAnchor:messageLabel.bottomAnchor constant:6],
[dateLabel.leadingAnchor constraintEqualToAnchor:textContainer.leadingAnchor],
[dateLabel.bottomAnchor constraintEqualToAnchor:textContainer.bottomAnchor],
[dateLabel.trailingAnchor constraintEqualToAnchor:unreadDot.leadingAnchor constant:-8],
// Unread Dot
[unreadDot.widthAnchor constraintEqualToConstant:dotSize],
[unreadDot.heightAnchor constraintEqualToConstant:dotSize],
[unreadDot.centerYAnchor constraintEqualToAnchor:dateLabel.centerYAnchor],
[unreadDot.trailingAnchor constraintEqualToAnchor:textContainer.trailingAnchor],
]]; ]];
BOOL unread = [item[@"read_status"] isEqualToString:@"0"];
unreadDot.hidden = !unread;
return card; return card;
} }
...@@ -186,7 +276,7 @@ ...@@ -186,7 +276,7 @@
clearButton.titleLabel.font = [UIFont boldSystemFontOfSize:16]; clearButton.titleLabel.font = [UIFont boldSystemFontOfSize:16];
clearButton.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; clearButton.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
clearButton.tintColor = UIColor.whiteColor; clearButton.tintColor = UIColor.whiteColor;
clearButton.layer.cornerRadius = 24; clearButton.layer.cornerRadius = 12;
[self.footerView addSubview:clearButton]; [self.footerView addSubview:clearButton];
clearButton.translatesAutoresizingMaskIntoConstraints = NO; clearButton.translatesAutoresizingMaskIntoConstraints = NO;
...@@ -204,40 +294,87 @@ ...@@ -204,40 +294,87 @@
[self dismissViewControllerAnimated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil];
} }
- (void)addDummyNotifications { - (void)requestNotification {
NSArray *dummyData = @[ [APIClient requestNotification:^(BOOL success, NSDictionary *data, NSError *error) {
@{@"text": @"Your appointment is scheduled for tomorrow.", @"date": @"2h ago"}, // Save debug file
@{@"text": @"New issue has been assigned to your unit.", @"date": @"Yesterday"}, NSString *plainText = [NSString stringWithFormat:@"%@", data];
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"}, NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"}, NSString *filePath = [docsPath stringByAppendingPathComponent:@"notification.txt"];
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"}, [plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"}, NSLog(@"📁 notification saved as plain text to: %@", filePath);
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"}, if (error || !data) {
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, NSLog(@"❌ notification API failed: %@", error);
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, return;
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, }
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, if (data[@"status_code"]) {
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, NSLog(@"❌ notification status error: %@", data[@"message"]);
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, return;
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, }
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, NSDictionary *appData = data[@"AppData"];
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, if (![appData[@"status"] isEqualToString:@"success"]) {
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, NSLog(@"❌ notification failure: %@", appData[@"message"]);
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, return;
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"}, }
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
]; NSArray *notif = data[@"Data"];
if (![notif isKindOfClass:[NSArray class]]) {
for (NSDictionary *item in dummyData) { NSLog(@"❌ Invalid notification format");
UIView *card = [self createNotificationCardWithText:item[@"text"] return;
date:item[@"date"]]; }
self.notifications = notif;
dispatch_async(dispatch_get_main_queue(), ^{
// Clear existing cards
for (UIView *v in self.stackView.arrangedSubviews) {
[v removeFromSuperview];
}
for (NSDictionary *item in self.notifications) {
UIView *card = [self createNotificationCard:item];
[self.stackView addArrangedSubview:card]; [self.stackView addArrangedSubview:card];
} }
UIView *endView = [self createEndOfListView];
[self.stackView addArrangedSubview:endView]; [self.stackView addArrangedSubview:[self createEndOfListView]];
});
}];
}
- (void)requestProjectList {
[APIClient requestProjectList:^(BOOL success, NSDictionary *data, NSError *error) {
// Save debug file
NSString *plainText = [NSString stringWithFormat:@"%@", data];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"projectList.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 projectList saved as plain text to: %@", filePath);
if (error || !data) {
NSLog(@"❌ Project list API failed: %@", error);
return;
}
if (data[@"status_code"]) {
NSLog(@"❌ API status error: %@", data[@"message"]);
return;
}
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ API failure: %@", appData[@"message"]);
return;
}
NSArray *projects = data[@"Data"];
if (![projects isKindOfClass:[NSArray class]]) {
NSLog(@"❌ Invalid project list format");
return;
}
self.projectList = projects;
}];
} }
- (UIView *)createEndOfListView { - (UIView *)createEndOfListView {
...@@ -261,8 +398,23 @@ ...@@ -261,8 +398,23 @@
return container; return container;
} }
- (void)onNotificationTap:(UITapGestureRecognizer *)tap {
NSLog(@"notification tapped");
UIView *card = tap.view;
// NSDictionary *item =
// objc_getAssociatedObject(card, @"notification");
//
// if (!item) return;
//
// NSString *type = item[@"type"];
//
// if ([type isEqualToString:@"appointment_reminder"]) {
// [self openAppointment:item];
// } else if ([type isEqualToString:@"rectification"]) {
// [self openRectification:item];
// } else {
// [self openGenericNotification:item];
// }
}
@end @end
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
@property (nonatomic, strong) UIButton *createButton; @property (nonatomic, strong) UIButton *createButton;
@property (nonatomic, strong) UIView *dimmedOverlay; @property (nonatomic, strong) UIView *dimmedOverlay;
@property (nonatomic, strong) UIView *addLinkSheet; @property (nonatomic, strong) UIView *addLinkSheet;
@property (nonatomic, strong) UILabel *usernameLabel;
@end @end
...@@ -145,6 +146,7 @@ ...@@ -145,6 +146,7 @@
[self.profileSectionView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], [self.profileSectionView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.profileSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], [self.profileSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.profileSectionView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], [self.profileSectionView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.profileSectionView.heightAnchor constraintGreaterThanOrEqualToConstant:358],
[self.settingsSectionView.topAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:16], [self.settingsSectionView.topAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:16],
[self.settingsSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16], [self.settingsSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
...@@ -173,7 +175,6 @@ ...@@ -173,7 +175,6 @@
// Edit Image Button // Edit Image Button
self.editProfileImageButton = self.editProfileImageButton =
[UIButton buttonWithType:UIButtonTypeSystem]; [UIButton buttonWithType:UIButtonTypeSystem];
[self.editProfileImageButton setImage: [self.editProfileImageButton setImage:
[UIImage systemImageNamed:@"camera"] [UIImage systemImageNamed:@"camera"]
forState:UIControlStateNormal]; forState:UIControlStateNormal];
...@@ -183,15 +184,18 @@ ...@@ -183,15 +184,18 @@
[self.editProfileImageButton addTarget:self [self.editProfileImageButton addTarget:self
action:@selector(editProfileImageTapped) action:@selector(editProfileImageTapped)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
self.editProfileImageButton.layer.cornerRadius = 18;
self.editProfileImageButton.translatesAutoresizingMaskIntoConstraints = NO; self.editProfileImageButton.translatesAutoresizingMaskIntoConstraints = NO;
self.editProfileImageButton.layer.cornerRadius = 20;
self.editProfileImageButton.layer.shadowColor = UIColor.blackColor.CGColor;
self.editProfileImageButton.layer.shadowOpacity = 0.15;
self.editProfileImageButton.layer.shadowOffset = CGSizeMake(0, 2);
self.editProfileImageButton.layer.shadowRadius = 4;
[self.profileSectionView addSubview:self.editProfileImageButton]; [self.profileSectionView addSubview:self.editProfileImageButton];
// Name Label // Name Label
self.nameLabel = [[UILabel alloc] init]; self.nameLabel = [[UILabel alloc] init];
self.nameLabel.text = @"User Name"; self.nameLabel.text = @"User Name";
self.nameLabel.font = [UIFont boldSystemFontOfSize:24]; self.nameLabel.font = [UIFont systemFontOfSize:24 weight:UIFontWeightSemibold];
self.nameLabel.textColor = UIColor.whiteColor; self.nameLabel.textColor = UIColor.whiteColor;
self.nameLabel.textAlignment = NSTextAlignmentCenter; self.nameLabel.textAlignment = NSTextAlignmentCenter;
self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO; self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
...@@ -207,6 +211,19 @@ ...@@ -207,6 +211,19 @@
action:@selector(showAddLinkSheet) action:@selector(showAddLinkSheet)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
self.usernameLabel = [[UILabel alloc] init];
self.usernameLabel.text = @"JohnSnow132";
self.usernameLabel.textColor = UIColor.whiteColor;
self.usernameLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
self.usernameLabel.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.2];
self.usernameLabel.layer.cornerRadius = 14;
self.usernameLabel.clipsToBounds = YES;
self.usernameLabel.textAlignment = NSTextAlignmentCenter;
self.usernameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.usernameLabel];
// Email Label // Email Label
self.emailLabel = [[UILabel alloc] init]; self.emailLabel = [[UILabel alloc] init];
self.emailLabel.text = @"example@mail.com"; self.emailLabel.text = @"example@mail.com";
...@@ -223,6 +240,11 @@ ...@@ -223,6 +240,11 @@
self.contactLabel.translatesAutoresizingMaskIntoConstraints = NO; self.contactLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.contactLabel]; [self.profileSectionView addSubview:self.contactLabel];
[self.profileSectionView bringSubviewToFront:self.profileImageView];
[self.profileSectionView bringSubviewToFront:self.editProfileImageButton];
self.emailLabel.alpha = 0.9;
self.contactLabel.alpha = 0.9;
// Layout // Layout
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
[self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:16], [self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:16],
...@@ -232,18 +254,21 @@ ...@@ -232,18 +254,21 @@
[self.editProfileImageButton.widthAnchor constraintEqualToConstant:36], [self.editProfileImageButton.widthAnchor constraintEqualToConstant:36],
[self.editProfileImageButton.heightAnchor constraintEqualToConstant:36], [self.editProfileImageButton.heightAnchor constraintEqualToConstant:36],
[self.editProfileImageButton.trailingAnchor [self.editProfileImageButton.trailingAnchor constraintEqualToAnchor:self.profileImageView.trailingAnchor],
constraintEqualToAnchor:self.profileImageView.trailingAnchor], [self.editProfileImageButton.bottomAnchor constraintEqualToAnchor:self.profileImageView.bottomAnchor],
[self.editProfileImageButton.bottomAnchor
constraintEqualToAnchor:self.profileImageView.bottomAnchor],
[self.nameLabel.topAnchor constraintEqualToAnchor:self.editProfileImageButton.bottomAnchor constant:16], [self.nameLabel.topAnchor constraintEqualToAnchor:self.editProfileImageButton.bottomAnchor constant:16],
[self.nameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor], [self.nameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.editNameButton.centerYAnchor constraintEqualToAnchor:self.nameLabel.centerYAnchor], [self.editNameButton.centerYAnchor constraintEqualToAnchor:self.nameLabel.centerYAnchor],
[self.editNameButton.leadingAnchor constraintEqualToAnchor:self.nameLabel.trailingAnchor constant:16], [self.editNameButton.leadingAnchor constraintEqualToAnchor:self.nameLabel.trailingAnchor constant:6],
[self.usernameLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:12],
[self.usernameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.usernameLabel.heightAnchor constraintEqualToConstant:28],
[self.usernameLabel.widthAnchor constraintGreaterThanOrEqualToConstant:120],
[self.emailLabel.topAnchor constraintEqualToAnchor:self.editNameButton.bottomAnchor constant:12], [self.emailLabel.topAnchor constraintEqualToAnchor:self.usernameLabel.bottomAnchor constant:12],
[self.emailLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor], [self.emailLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:8], [self.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:8],
...@@ -253,53 +278,152 @@ ...@@ -253,53 +278,152 @@
} }
- (void)setupSettingsSectionContents { - (void)setupSettingsSectionContents {
// Title
// Section title
UILabel *titleLabel = [[UILabel alloc] init]; UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"Account Settings"; titleLabel.text = @"Account Settings";
titleLabel.font = [UIFont boldSystemFontOfSize:18]; titleLabel.font = [UIFont boldSystemFontOfSize:18];
titleLabel.textColor = UIColor.systemGrayColor;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO; titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:titleLabel]; [self.settingsSectionView addSubview:titleLabel];
// Buttons // Card container
UIButton *changeLanguageButton = [UIButton buttonWithType:UIButtonTypeSystem]; UIView *card = [[UIView alloc] init];
[changeLanguageButton setTitle:@"Change Language" forState:UIControlStateNormal]; card.backgroundColor = UIColor.whiteColor;
changeLanguageButton.translatesAutoresizingMaskIntoConstraints = NO; card.layer.cornerRadius = 16;
[self.settingsSectionView addSubview:changeLanguageButton]; card.layer.borderWidth = 1;
card.layer.borderColor = UIColor.systemGray4Color.CGColor;
self.logoutButton = [UIButton buttonWithType:UIButtonTypeSystem]; card.translatesAutoresizingMaskIntoConstraints = NO;
[self.logoutButton setTitle:@"Log Out" forState:UIControlStateNormal]; [self.settingsSectionView addSubview:card];
self.logoutButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:self.logoutButton]; // ---- Change Language row ----
[self.logoutButton addTarget:self UIButton *changeLanguageButton = [self settingsRowWithTitle:@"Change Language"
action:@selector(logoutTapped) icon:@"globe"
forControlEvents:UIControlEventTouchUpInside]; textColor:UIColor.labelColor
action:@selector(changeLanguageTapped)];
[card addSubview:changeLanguageButton];
UILabel *langLabel = [[UILabel alloc] init];
langLabel.text = @"EN";
langLabel.textColor = UIColor.systemGrayColor;
langLabel.font = [UIFont systemFontOfSize:16];
langLabel.translatesAutoresizingMaskIntoConstraints = NO;
[changeLanguageButton addSubview:langLabel];
// Separator
UIView *separator = [self separatorView];
[card addSubview:separator];
// ---- Logout row ----
UIButton *logoutButton =
[self settingsRowWithTitle:@"Log Out"
icon:@"arrow.right.square"
textColor:UIColor.systemRedColor
action:@selector(logoutTapped)];
[card addSubview:logoutButton];
// ---- Delete Account pill ----
self.deleteAccountButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.deleteAccountButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.deleteAccountButton setTitle:@"Delete My Account" forState:UIControlStateNormal]; [self.deleteAccountButton setTitle:@"Delete My Account" forState:UIControlStateNormal];
[self.deleteAccountButton setTitleColor:UIColor.systemRedColor forState:UIControlStateNormal]; [self.deleteAccountButton setTitleColor:UIColor.systemRedColor forState:UIControlStateNormal];
self.deleteAccountButton.backgroundColor =
[[UIColor systemRedColor] colorWithAlphaComponent:0.1];
self.deleteAccountButton.layer.cornerRadius = 14;
self.deleteAccountButton.contentEdgeInsets = UIEdgeInsetsMake(14, 16, 14, 16);
self.deleteAccountButton.translatesAutoresizingMaskIntoConstraints = NO; self.deleteAccountButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:self.deleteAccountButton];
[self.deleteAccountButton addTarget:self [self.deleteAccountButton addTarget:self
action:@selector(deleteAccountTapped) action:@selector(deleteAccountTapped)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
[self.settingsSectionView addSubview:self.deleteAccountButton];
// Layout // ---- Layout ----
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint activateConstraints:@[
// Title
[titleLabel.topAnchor constraintEqualToAnchor:self.settingsSectionView.topAnchor constant:16], [titleLabel.topAnchor constraintEqualToAnchor:self.settingsSectionView.topAnchor constant:16],
[titleLabel.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16], [titleLabel.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor],
// Card
[card.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:12],
[card.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor],
[card.trailingAnchor constraintEqualToAnchor:self.settingsSectionView.trailingAnchor],
// Change Language row
[changeLanguageButton.topAnchor constraintEqualToAnchor:card.topAnchor],
[changeLanguageButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor],
[changeLanguageButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor],
[changeLanguageButton.heightAnchor constraintEqualToConstant:52],
// Language text
[langLabel.centerYAnchor constraintEqualToAnchor:changeLanguageButton.centerYAnchor],
[langLabel.trailingAnchor constraintEqualToAnchor:changeLanguageButton.trailingAnchor constant:-36],
// Separator
[separator.topAnchor constraintEqualToAnchor:changeLanguageButton.bottomAnchor],
[separator.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:16],
[separator.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[separator.heightAnchor constraintEqualToConstant:1],
// Logout row
[logoutButton.topAnchor constraintEqualToAnchor:separator.bottomAnchor],
[logoutButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor],
[logoutButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor],
[logoutButton.heightAnchor constraintEqualToConstant:52],
// Card bottom
[card.bottomAnchor constraintEqualToAnchor:logoutButton.bottomAnchor],
// Delete button
[self.deleteAccountButton.topAnchor constraintEqualToAnchor:card.bottomAnchor constant:16],
[self.deleteAccountButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor],
[self.deleteAccountButton.trailingAnchor constraintEqualToAnchor:self.settingsSectionView.trailingAnchor],
[self.deleteAccountButton.bottomAnchor constraintEqualToAnchor:self.settingsSectionView.bottomAnchor constant:-16],
]];
}
[changeLanguageButton.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:16], - (UIButton *)settingsRowWithTitle:(NSString *)title
[changeLanguageButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16], icon:(NSString *)iconName
textColor:(UIColor *)color
action:(SEL)selector {
[self.logoutButton.topAnchor constraintEqualToAnchor:changeLanguageButton.bottomAnchor constant:16], UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[self.logoutButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16], button.translatesAutoresizingMaskIntoConstraints = NO;
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
[self.deleteAccountButton.topAnchor constraintEqualToAnchor:self.logoutButton.bottomAnchor constant:16], UIImageView *icon =
[self.deleteAccountButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16], [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:iconName]];
[self.deleteAccountButton.bottomAnchor constraintEqualToAnchor:self.settingsSectionView.bottomAnchor constant:-16], icon.tintColor = color;
icon.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *label = [[UILabel alloc] init];
label.text = title;
label.textColor = color;
label.font = [UIFont systemFontOfSize:16];
label.translatesAutoresizingMaskIntoConstraints = NO;
UIImageView *chevron =
[[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"chevron.right"]];
chevron.tintColor = UIColor.systemGray3Color;
chevron.translatesAutoresizingMaskIntoConstraints = NO;
[button addSubview:icon];
[button addSubview:label];
[button addSubview:chevron];
[NSLayoutConstraint activateConstraints:@[
[icon.leadingAnchor constraintEqualToAnchor:button.leadingAnchor constant:16],
[icon.centerYAnchor constraintEqualToAnchor:button.centerYAnchor],
[label.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:12],
[label.centerYAnchor constraintEqualToAnchor:button.centerYAnchor],
[chevron.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:-16],
[chevron.centerYAnchor constraintEqualToAnchor:button.centerYAnchor],
]]; ]];
return button;
} }
#pragma mark - API #pragma mark - API
-(void) requestProfile { -(void) requestProfile {
[APIClient requestProfile:^(BOOL success, NSDictionary *data, NSError *error) { [APIClient requestProfile:^(BOOL success, NSDictionary *data, NSError *error) {
...@@ -340,10 +464,18 @@ ...@@ -340,10 +464,18 @@
self.nameLabel.text = profile[@"name"] ?: @""; self.nameLabel.text = profile[@"name"] ?: @"";
self.emailLabel.text = profile[@"email"] ?: @""; self.emailLabel.text = profile[@"email"] ?: @"";
self.contactLabel.text = profile[@"contact"] ?: @""; self.contactLabel.text = profile[@"contact"] ?: @"";
self.usernameLabel.text = profile[@"ic_passport"] ?: @"";
}); });
}]; }];
} }
- (UIView *)separatorView {
UIView *view = [[UIView alloc] init];
view.backgroundColor = UIColor.systemGray4Color;
view.translatesAutoresizingMaskIntoConstraints = NO;
return view;
}
#pragma mark - Helpers #pragma mark - Helpers
- (void)onBack { - (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil]; [self dismissViewControllerAnimated:YES completion:nil];
......
...@@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)requestDashboardInfo:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion; + (void)requestDashboardInfo:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion;
+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion; + (void)requestClientAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion;
+ (void)requestHistory:(NSString *)issueID + (void)requestHistory:(NSString *)issueID
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion; completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion;
...@@ -96,6 +96,10 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -96,6 +96,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)editProfile:(NSString *)name + (void)editProfile:(NSString *)name
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion; completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)requestNotification:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -867,9 +867,9 @@ ...@@ -867,9 +867,9 @@
[task resume]; [task resume];
} }
+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion { + (void)requestClientAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Announcement" completion:completion]) { if ([APIConfig handleOfflineForAPI:@"ClientAnnouncement" completion:completion]) {
return; return;
} }
// ✅ Keep token in URL // ✅ Keep token in URL
...@@ -918,7 +918,7 @@ ...@@ -918,7 +918,7 @@
NSError *jsonErr; NSError *jsonErr;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr]; NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr];
NSLog(@"✅ Announcement API response:\n%@", json); NSLog(@"✅ ClientAnnouncement API response:\n%@", json);
NSDictionary *appData = json[@"AppData"]; NSDictionary *appData = json[@"AppData"];
NSArray *announcements = @[]; NSArray *announcements = @[];
...@@ -931,7 +931,7 @@ ...@@ -931,7 +931,7 @@
} }
if (completion) { if (completion) {
NSString *cacheKey = [NSString stringWithFormat:@"Announcement_%@", [APIConfig projectId]]; NSString *cacheKey = [NSString stringWithFormat:@"ClientAnnouncement_%@", [APIConfig projectId]];
NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json]; NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
[LocalStorage saveDictionary:safeJson forKey:cacheKey]; [LocalStorage saveDictionary:safeJson forKey:cacheKey];
NSLog(@"💾 Saved to cache: %@", cacheKey); NSLog(@"💾 Saved to cache: %@", cacheKey);
...@@ -2320,6 +2320,143 @@ ...@@ -2320,6 +2320,143 @@
[task resume]; [task resume];
} }
+ (void)requestNotification:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Notification" completion:completion]) {
return;
}
NSURL *url = [APIConfig urlWithPath:@"/framework/owner/notification"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSDictionary *jsonBody = @{
@"data": @{
@"os": [APIConfig os] ,
}
};
NSLog(@"🌍 request notification: %@", url.absoluteString);
NSLog(@"📦 request notification json: %@", jsonBody);
NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON Serialization Error: %@", jsonError);
if (completion) completion(NO, nil, jsonError);
return;
}
request.HTTPBody = bodyData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network Error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, error);
});
return;
}
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (parseError) {
NSLog(@"⚠️ JSON Parse Error: %@", parseError);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, parseError);
});
return;
}
NSString *cacheKey = [NSString stringWithFormat:@"Notification_%@", [APIConfig projectId]];
NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
[LocalStorage saveDictionary:safeJson forKey:cacheKey];
NSLog(@"💾 Saved to cache: %@", cacheKey);
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, json, nil);
});
}];
[task resume];
}
+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Announcement" completion:completion]) {
return;
}
// NSURL *url = [APIConfig urlWithPath:@"/announcement"];
NSURL *url = [NSURL URLWithString:@"https://kitadev.commudesk.com/api/announcement?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSDictionary *jsonBody = @{
@"data": @{
@"os": [APIConfig os] ,
}
};
NSLog(@"🌍 announcement notification: %@", url.absoluteString);
NSLog(@"📦 announcement notification json: %@", jsonBody);
NSError *jsonError;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:jsonBody options:0 error:&jsonError];
if (jsonError) {
NSLog(@"❌ JSON Serialization Error: %@", jsonError);
if (completion) completion(NO, nil, jsonError);
return;
}
request.HTTPBody = bodyData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"❌ Network Error: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, error);
});
return;
}
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (parseError) {
NSLog(@"⚠️ JSON Parse Error: %@", parseError);
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, parseError);
});
return;
}
NSString *cacheKey = [NSString stringWithFormat:@"Announcement_%@", [APIConfig projectId]];
NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
[LocalStorage saveDictionary:safeJson forKey:cacheKey];
NSLog(@"💾 Saved to cache: %@", cacheKey);
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, json, nil);
});
}];
[task resume];
}
#pragma mark - Helper Methods #pragma mark - Helper Methods
+ (NSString *)multipartFormField:(NSString *)name value:(NSString *)value boundary:(NSString *)boundary { + (NSString *)multipartFormField:(NSString *)name value:(NSString *)value boundary:(NSString *)boundary {
return [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"data[%@]\"\r\n\r\n%@\r\n", boundary, name, value]; return [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"data[%@]\"\r\n\r\n%@\r\n", boundary, name, value];
......
...@@ -28,6 +28,10 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -28,6 +28,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)authToken; + (NSString *)authToken;
+ (void)setAuthTokem:(NSString *)authToken; + (void)setAuthTokem:(NSString *)authToken;
// Lot Id
+ (NSString *)lotId;
+ (void)setLotId:(NSString *)lotId;
/// Convenience: baseURL + path + ?token= /// Convenience: baseURL + path + ?token=
+ (NSURL *)urlWithPath:(NSString *)path; + (NSURL *)urlWithPath:(NSString *)path;
......
...@@ -8,6 +8,7 @@ static NSString *kDrawingPlanId = @""; ...@@ -8,6 +8,7 @@ static NSString *kDrawingPlanId = @"";
static NSString *kProjectId = @""; static NSString *kProjectId = @"";
static NSString *kAuthToken = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InNlcnZlci10by1zZXJ2ZXIiLCJmcmFtZXdvcmstYXBpIjp0cnVlLCJjb21wYW55X2NvZGUiOiJTUFQiLCJzdWIiOjE1NTcsImlzcyI6Imh0dHBzOi8vc3B0ZXN0LmNvbW11ZGVzay5jb20vYXBpL2ZyYW1ld29yay9vd25lci9hdXRoL2NsaWVudF9hdXRoZW50aWNhdGlvbiIsImlhdCI6MTc2Nzc1MjU5OSwiZXhwIjoyMDgzMzIyMTE5LCJuYmYiOjE3Njc3NTI1OTksImp0aSI6IktEcmhmSXgzeWU0WmxaUEYifQ.Bv4ZSjGRm9YYOjGItLSV_3wS7grKuPTabL60BFnp88o"; static NSString *kAuthToken = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InNlcnZlci10by1zZXJ2ZXIiLCJmcmFtZXdvcmstYXBpIjp0cnVlLCJjb21wYW55X2NvZGUiOiJTUFQiLCJzdWIiOjE1NTcsImlzcyI6Imh0dHBzOi8vc3B0ZXN0LmNvbW11ZGVzay5jb20vYXBpL2ZyYW1ld29yay9vd25lci9hdXRoL2NsaWVudF9hdXRoZW50aWNhdGlvbiIsImlhdCI6MTc2Nzc1MjU5OSwiZXhwIjoyMDgzMzIyMTE5LCJuYmYiOjE3Njc3NTI1OTksImp0aSI6IktEcmhmSXgzeWU0WmxaUEYifQ.Bv4ZSjGRm9YYOjGItLSV_3wS7grKuPTabL60BFnp88o";
static NSString *kClientId = @"1"; static NSString *kClientId = @"1";
static NSString *kLotId = @"";
@implementation APIConfig @implementation APIConfig
#pragma mark - OS #pragma mark - OS
...@@ -75,6 +76,18 @@ static NSString *kClientId = @"1"; ...@@ -75,6 +76,18 @@ static NSString *kClientId = @"1";
} }
} }
#pragma mark - LotId
+ (NSString *)lotId {
return kLotId;
}
+ (void)setLotId:(NSString *)lotId {
if (lotId.length > 0) {
kLotId = [lotId copy];
NSLog(@"🌐 API lotId set to: %@", kLotId);
}
}
+ (NSURL *)urlWithPath:(NSString *)path { + (NSURL *)urlWithPath:(NSString *)path {
NSString *urlString = NSString *urlString =
[NSString stringWithFormat:@"%@%@?token=%@", [NSString stringWithFormat:@"%@%@?token=%@",
......
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