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
@interface
AnnouncementDetailsViewController
:
UIViewController
@property
(
nonatomic
,
strong
)
NSDictionary
*
data
;
@end
NS_ASSUME_NONNULL_END
...
...
framework/QmsPluginFramework/QmsPluginFramework/Announcement/AnnounceDetailsViewController.mm
View file @
7735252a
...
...
@@ -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
framework/QmsPluginFramework/QmsPluginFramework/Announcement/AnnounceViewController.mm
View file @
7735252a
// AnnounceViewController.mm
// AnnouncementViewController.mm
#import "AnnounceViewController.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 ()
// Header (existing)
// Description
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) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
// Table
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *announcement;
@end
@implementation AnnouncementViewController
- (void)viewDidLoad {
[super viewDidLoad];
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 setupTableView];
[self requestAnnouncement];
}
#pragma mark - Header
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
//Header
self.headerBar = [[UIView alloc] init];
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
[self.view addSubview:self.headerBar];
...
...
@@ -38,14 +208,12 @@
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Announcement";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
...
...
@@ -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
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Dashboard/UnitViewController.mm
View file @
7735252a
...
...
@@ -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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Drawer/DrawerViewController.mm
View file @
7735252a
...
...
@@ -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"]) {
...
...
framework/QmsPluginFramework/QmsPluginFramework/Drawer/NotificationViewController.mm
View file @
7735252a
...
...
@@ -2,6 +2,7 @@
#import "NotificationViewController.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
@interface NotificationViewController ()
// Header (existing)
...
...
@@ -12,6 +13,8 @@
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIView *footerView;
@property (nonatomic, strong) UIStackView *stackView;
@property (nonatomic, strong) NSArray *notifications;
@property (nonatomic, strong) NSArray *projectList;
@end
@implementation NotificationViewController
...
...
@@ -20,11 +23,12 @@
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self requestProjectList];
[self setupHeader];
[self setupScrollView];
[self setupFooterButton];
[self setupNotificationList];
[self
addDummyNotifications
];
[self
requestNotification
];
}
#pragma mark - Header
...
...
@@ -135,47 +139,133 @@
]];
}
- (UIView *)createNotificationCard
WithText:(NSString *)text date:(NSString *)date
{
- (UIView *)createNotificationCard
:(NSDictionary *)item
{
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.systemGray6Color;
card.layer.cornerRadius = 12;
card.translatesAutoresizingMaskIntoConstraints = NO;
UIImageView *icon = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"bell.fill"]];
icon.tintColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
card.backgroundColor = UIColor.whiteColor;
card.layer.shadowColor = UIColor.blackColor.CGColor;
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.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];
messageLabel.text =
text
;
messageLabel.text =
item[@"message"]
;
messageLabel.numberOfLines = 0;
messageLabel.font = [UIFont systemFontOfSize:15];
messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *dateLabel = [[UILabel alloc] init];
dateLabel.text =
date
;
dateLabel.text =
item[@"date_time"]
;
dateLabel.font = [UIFont systemFontOfSize:12];
dateLabel.textColor = UIColor.secondaryLabelColor;
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:messageLabel];
[card addSubview:dateLabel];
[card addSubview:textContainer];
[textContainer addSubview:projectLabel];
[textContainer addSubview:messageLabel];
[textContainer addSubview:dateLabel];
[textContainer addSubview:unreadDot];
[NSLayoutConstraint activateConstraints:@[
// Logo
[icon.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:12],
[icon.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[icon.widthAnchor constraintEqualToConstant:24],
[icon.heightAnchor constraintEqualToConstant:24],
[messageLabel.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:12],
[messageLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-12],
[messageLabel.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[dateLabel.leadingAnchor constraintEqualToAnchor:messageLabel.leadingAnchor],
[dateLabel.topAnchor constraintEqualToAnchor:messageLabel.bottomAnchor constant:8],
[dateLabel.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-12],
[icon.widthAnchor constraintEqualToConstant:64],
[icon.heightAnchor constraintLessThanOrEqualToConstant:64],
// Text container
[textContainer.leadingAnchor constraintEqualToAnchor:icon.trailingAnchor constant:12],
[textContainer.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[textContainer.topAnchor constraintEqualToAnchor:card.topAnchor constant:12],
[textContainer.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;
}
...
...
@@ -186,7 +276,7 @@
clearButton.titleLabel.font = [UIFont boldSystemFontOfSize:16];
clearButton.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
clearButton.tintColor = UIColor.whiteColor;
clearButton.layer.cornerRadius =
24
;
clearButton.layer.cornerRadius =
12
;
[self.footerView addSubview:clearButton];
clearButton.translatesAutoresizingMaskIntoConstraints = NO;
...
...
@@ -204,40 +294,87 @@
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)addDummyNotifications {
NSArray *dummyData = @[
@{@"text": @"Your appointment is scheduled for tomorrow.", @"date": @"2h ago"},
@{@"text": @"New issue has been assigned to your unit.", @"date": @"Yesterday"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"Your clearance letter is now available.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
@{@"text": @"potatopotatopotatotoainjadnub iuebfiubsdibjsebofnondfobweobcoubdjsjbeiubijdsbjebfjbjdsbief.", @"date": @"3 days ago"},
];
for (NSDictionary *item in dummyData) {
UIView *card = [self createNotificationCardWithText:item[@"text"]
date:item[@"date"]];
- (void)requestNotification {
[APIClient requestNotification:^(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:@"notification.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 notification saved as plain text to: %@", filePath);
if (error || !data) {
NSLog(@"❌ notification API failed: %@", error);
return;
}
if (data[@"status_code"]) {
NSLog(@"❌ notification status error: %@", data[@"message"]);
return;
}
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ notification failure: %@", appData[@"message"]);
return;
}
NSArray *notif = data[@"Data"];
if (![notif isKindOfClass:[NSArray class]]) {
NSLog(@"❌ Invalid notification format");
return;
}
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];
}
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 {
...
...
@@ -261,8 +398,23 @@
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
framework/QmsPluginFramework/QmsPluginFramework/Drawer/ProfileViewController.mm
View file @
7735252a
...
...
@@ -40,6 +40,7 @@
@property (nonatomic, strong) UIButton *createButton;
@property (nonatomic, strong) UIView *dimmedOverlay;
@property (nonatomic, strong) UIView *addLinkSheet;
@property (nonatomic, strong) UILabel *usernameLabel;
@end
...
...
@@ -145,6 +146,7 @@
[self.profileSectionView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.profileSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.profileSectionView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.profileSectionView.heightAnchor constraintGreaterThanOrEqualToConstant:358],
[self.settingsSectionView.topAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:16],
[self.settingsSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
...
...
@@ -173,7 +175,6 @@
// Edit Image Button
self.editProfileImageButton =
[UIButton buttonWithType:UIButtonTypeSystem];
[self.editProfileImageButton setImage:
[UIImage systemImageNamed:@"camera"]
forState:UIControlStateNormal];
...
...
@@ -183,15 +184,18 @@
[self.editProfileImageButton addTarget:self
action:@selector(editProfileImageTapped)
forControlEvents:UIControlEventTouchUpInside];
self.editProfileImageButton.layer.cornerRadius = 18;
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];
// Name Label
self.nameLabel = [[UILabel alloc] init];
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.textAlignment = NSTextAlignmentCenter;
self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
...
...
@@ -207,6 +211,19 @@
action:@selector(showAddLinkSheet)
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
self.emailLabel = [[UILabel alloc] init];
self.emailLabel.text = @"example@mail.com";
...
...
@@ -223,6 +240,11 @@
self.contactLabel.translatesAutoresizingMaskIntoConstraints = NO;
[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
[NSLayoutConstraint activateConstraints:@[
[self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:16],
...
...
@@ -232,18 +254,21 @@
[self.editProfileImageButton.widthAnchor constraintEqualToConstant:36],
[self.editProfileImageButton.heightAnchor constraintEqualToConstant:36],
[self.editProfileImageButton.trailingAnchor
constraintEqualToAnchor:self.profileImageView.trailingAnchor],
[self.editProfileImageButton.bottomAnchor
constraintEqualToAnchor:self.profileImageView.bottomAnchor],
[self.editProfileImageButton.trailingAnchor constraintEqualToAnchor:self.profileImageView.trailingAnchor],
[self.editProfileImageButton.bottomAnchor constraintEqualToAnchor:self.profileImageView.bottomAnchor],
[self.nameLabel.topAnchor constraintEqualToAnchor:self.editProfileImageButton.bottomAnchor constant:16],
[self.nameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[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.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:8],
...
...
@@ -253,53 +278,152 @@
}
- (void)setupSettingsSectionContents {
// Title
// Section title
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"Account Settings";
titleLabel.font = [UIFont boldSystemFontOfSize:18];
titleLabel.textColor = UIColor.systemGrayColor;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:titleLabel];
// Buttons
UIButton *changeLanguageButton = [UIButton buttonWithType:UIButtonTypeSystem];
[changeLanguageButton setTitle:@"Change Language" forState:UIControlStateNormal];
changeLanguageButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:changeLanguageButton];
self.logoutButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.logoutButton setTitle:@"Log Out" forState:UIControlStateNormal];
self.logoutButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:self.logoutButton];
[self.logoutButton addTarget:self
action:@selector(logoutTapped)
forControlEvents:UIControlEventTouchUpInside];
// Card container
UIView *card = [[UIView alloc] init];
card.backgroundColor = UIColor.whiteColor;
card.layer.cornerRadius = 16;
card.layer.borderWidth = 1;
card.layer.borderColor = UIColor.systemGray4Color.CGColor;
card.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:card];
// ---- Change Language row ----
UIButton *changeLanguageButton = [self settingsRowWithTitle:@"Change Language"
icon:@"globe"
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 setTitle:@"Delete My Account" 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.settingsSectionView addSubview:self.deleteAccountButton];
[self.deleteAccountButton addTarget:self
action:@selector(deleteAccountTapped)
forControlEvents:UIControlEventTouchUpInside];
[self.settingsSectionView addSubview:self.deleteAccountButton];
//
Layout
//
---- Layout ----
[NSLayoutConstraint activateConstraints:@[
// Title
[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],
[changeLanguageButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
- (UIButton *)settingsRowWithTitle:(NSString *)title
icon:(NSString *)iconName
textColor:(UIColor *)color
action:(SEL)selector {
[self.logoutButton.topAnchor constraintEqualToAnchor:changeLanguageButton.bottomAnchor constant:16],
[self.logoutButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.translatesAutoresizingMaskIntoConstraints = NO;
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
[self.deleteAccountButton.topAnchor constraintEqualToAnchor:self.logoutButton.bottomAnchor constant:16],
[self.deleteAccountButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
[self.deleteAccountButton.bottomAnchor constraintEqualToAnchor:self.settingsSectionView.bottomAnchor constant:-16],
UIImageView *icon =
[[UIImageView alloc] initWithImage:[UIImage systemImageNamed:iconName]];
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
-(void) requestProfile {
[APIClient requestProfile:^(BOOL success, NSDictionary *data, NSError *error) {
...
...
@@ -340,10 +464,18 @@
self.nameLabel.text = profile[@"name"] ?: @"";
self.emailLabel.text = profile[@"email"] ?: @"";
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
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIClient.h
View file @
7735252a
...
...
@@ -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
)
request
Client
Announcement
:(
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
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIClient.mm
View file @
7735252a
...
...
@@ -867,9 +867,9 @@
[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;
}
// ✅ 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(@"✅
Client
Announcement 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:@"
Client
Announcement_%@", [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];
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIConfig.h
View file @
7735252a
...
...
@@ -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
;
...
...
framework/QmsPluginFramework/QmsPluginFramework/Utilities/API/APIConfig.mm
View file @
7735252a
...
...
@@ -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=%@",
...
...
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