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

help and announcement screen

parent 72a404a7
......@@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface AnnouncementDetailsViewController : UIViewController
@property (nonatomic, strong) NSDictionary *data;
@end
NS_ASSUME_NONNULL_END
......
......@@ -2,12 +2,24 @@
#import "AnnounceDetailsViewController.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
@interface AnnouncementDetailsViewController ()
@interface AnnouncementDetailsViewController () <UITextViewDelegate>
// Header (existing)
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@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
@implementation AnnouncementDetailsViewController
......@@ -17,6 +29,11 @@
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
[self setupContentView];
if (self.data) {
[self configureWithData:self.data];
}
}
#pragma mark - Header
......@@ -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
- (void)onBack {
[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
......@@ -275,6 +275,7 @@ didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *projectId = [unit[@"project_id"] description];
NSString *drawingPlanId = [unit[@"id"] description];
[APIConfig setDrawingPlanId:drawingPlanId];
NSLog(@"lotid: %@", unit[@"lot_id"]);
NSLog(@"self.projectLogo: %@", self.projectLogo);
DashboardViewController *vc = [[DashboardViewController alloc] init];
......
......@@ -5,6 +5,8 @@
#import "ImageCacheHelper.h"
#import "ProfileViewController.h"
#import "NotificationViewController.h"
#import "HelpViewController.h"
#import "AnnounceViewController.h"
static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification";
......@@ -228,13 +230,16 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"announcement"]) {
// [self openAnnouncement];
NSLog(@"navigating to Announcement");
AnnouncementViewController *nextVC = [[AnnouncementViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"help"]) {
// [self openHelp];
NSLog(@"navigating to Help");
HelpViewController *nextVC = [[HelpViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"sync"]) {
......
......@@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (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
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * error))completion;
......@@ -96,6 +96,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)editProfile:(NSString *)name
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
NS_ASSUME_NONNULL_END
......@@ -867,9 +867,9 @@
[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;
}
// ✅ Keep token in URL
......@@ -918,7 +918,7 @@
NSError *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"];
NSArray *announcements = @[];
......@@ -931,7 +931,7 @@
}
if (completion) {
NSString *cacheKey = [NSString stringWithFormat:@"Announcement_%@", [APIConfig projectId]];
NSString *cacheKey = [NSString stringWithFormat:@"ClientAnnouncement_%@", [APIConfig projectId]];
NSDictionary *safeJson = [APIConfig dictionaryByReplacingNullsWithBlanks:json];
[LocalStorage saveDictionary:safeJson forKey:cacheKey];
NSLog(@"💾 Saved to cache: %@", cacheKey);
......@@ -2320,6 +2320,143 @@
[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
+ (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];
......
......@@ -28,6 +28,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)authToken;
+ (void)setAuthTokem:(NSString *)authToken;
// Lot Id
+ (NSString *)lotId;
+ (void)setLotId:(NSString *)lotId;
/// Convenience: baseURL + path + ?token=
+ (NSURL *)urlWithPath:(NSString *)path;
......
......@@ -8,6 +8,7 @@ static NSString *kDrawingPlanId = @"";
static NSString *kProjectId = @"";
static NSString *kAuthToken = @"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InNlcnZlci10by1zZXJ2ZXIiLCJmcmFtZXdvcmstYXBpIjp0cnVlLCJjb21wYW55X2NvZGUiOiJTUFQiLCJzdWIiOjE1NTcsImlzcyI6Imh0dHBzOi8vc3B0ZXN0LmNvbW11ZGVzay5jb20vYXBpL2ZyYW1ld29yay9vd25lci9hdXRoL2NsaWVudF9hdXRoZW50aWNhdGlvbiIsImlhdCI6MTc2Nzc1MjU5OSwiZXhwIjoyMDgzMzIyMTE5LCJuYmYiOjE3Njc3NTI1OTksImp0aSI6IktEcmhmSXgzeWU0WmxaUEYifQ.Bv4ZSjGRm9YYOjGItLSV_3wS7grKuPTabL60BFnp88o";
static NSString *kClientId = @"1";
static NSString *kLotId = @"";
@implementation APIConfig
#pragma mark - OS
......@@ -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 {
NSString *urlString =
[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