Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
weihan-plugin
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Wei Han
weihan-plugin
Commits
7735252a
Commit
7735252a
authored
Jan 27, 2026
by
Wei Han
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
help and announcement screen
parent
72a404a7
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
988 additions
and
108 deletions
+988
-108
AnnounceDetailsViewController.h
...ginFramework/Announcement/AnnounceDetailsViewController.h
+2
-0
AnnounceDetailsViewController.mm
...inFramework/Announcement/AnnounceDetailsViewController.mm
+172
-1
AnnounceViewController.mm
...QmsPluginFramework/Announcement/AnnounceViewController.mm
+267
-8
UnitViewController.mm
...mework/QmsPluginFramework/Dashboard/UnitViewController.mm
+1
-0
DrawerViewController.mm
...amework/QmsPluginFramework/Drawer/DrawerViewController.mm
+8
-3
NotificationViewController.mm
...k/QmsPluginFramework/Drawer/NotificationViewController.mm
+208
-56
ProfileViewController.mm
...mework/QmsPluginFramework/Drawer/ProfileViewController.mm
+167
-35
APIClient.h
...ginFramework/QmsPluginFramework/Utilities/API/APIClient.h
+5
-1
APIClient.mm
...inFramework/QmsPluginFramework/Utilities/API/APIClient.mm
+141
-4
APIConfig.h
...ginFramework/QmsPluginFramework/Utilities/API/APIConfig.h
+4
-0
APIConfig.mm
...inFramework/QmsPluginFramework/Utilities/API/APIConfig.mm
+13
-0
No files found.
framework/QmsPluginFramework/QmsPluginFramework/Announcement/AnnounceDetailsViewController.h
View file @
7735252a
...
@@ -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
...
...
framework/QmsPluginFramework/QmsPluginFramework/Announcement/AnnounceDetailsViewController.mm
View file @
7735252a
...
@@ -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
framework/QmsPluginFramework/QmsPluginFramework/Announcement/AnnounceViewController.mm
View file @
7735252a
// 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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/UnitViewController.mm
View file @
7735252a
...
@@ -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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Drawer/DrawerViewController.mm
View file @
7735252a
...
@@ -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"]) {
...
...
framework/QmsPluginFramework/QmsPluginFramework/Drawer/NotificationViewController.mm
View file @
7735252a
...
@@ -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 *)createNotificationCard
WithText:(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
framework/QmsPluginFramework/QmsPluginFramework/Drawer/ProfileViewController.mm
View file @
7735252a
...
@@ -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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIClient.h
View file @
7735252a
...
@@ -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
)
request
Client
Announcement
:(
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
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIClient.mm
View file @
7735252a
...
@@ -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)request
Client
Announcement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Announcement" completion:completion]) {
if ([APIConfig handleOfflineForAPI:@"
Client
Announcement" 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(@"✅
Client
Announcement 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:@"
Client
Announcement_%@", [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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIConfig.h
View file @
7735252a
...
@@ -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
;
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIConfig.mm
View file @
7735252a
...
@@ -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=%@",
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment