Commit 72a404a7 authored by Wei Han's avatar Wei Han

code update

parent d7d2568a
// AnnounceDetailsViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AnnouncementDetailsViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// AnnounceDetailsViewController.mm
#import "AnnounceDetailsViewController.h"
#import "APIClient.h"
@interface AnnouncementDetailsViewController ()
// Header (existing)
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@end
@implementation AnnouncementDetailsViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
}
#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];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
// Title
//need to make it detect which screen its navigating from then set differently (Project Update Details if from client announce else Announcement Details from announce)
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;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
// AnnounceViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AnnouncementViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// AnnounceViewController.mm
#import "AnnounceViewController.h"
#import "APIClient.h"
@interface AnnouncementViewController ()
// Header (existing)
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@end
@implementation AnnouncementViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
}
#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];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[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;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
// ClientAnnounceViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ClientAnnouncementViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// ClientAnnounceViewController.mm
#import "ClientAnnounceViewController.h"
#import "APIClient.h"
@interface ClientAnnouncementViewController ()
// Header (existing)
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
@end
@implementation ClientAnnouncementViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
}
#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];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Project Update";
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;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
// DrawerViewController.m
#import "DrawerViewController.h"
#import "DrawerController.h"
#import "SyncScreenViewController.h"
@interface DrawerViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) UIImageView *avatarView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *emailLabel;
@property (nonatomic, strong) UITableView *table;
@property (nonatomic, strong) UIView *headerContainer;
@end
@implementation DrawerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:0.02 green:0.36 blue:0.47 alpha:1.0]; // #035E79-ish
[self buildUI];
[self loadDefaultsIfNeeded];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDrawerAction:)
name:DrawerDidSelectItemNotification
object:nil];
}
- (void)buildUI {
self.view.backgroundColor = [UIColor colorWithRed:0.02 green:0.36 blue:0.47 alpha:1.0];
CGFloat topHeight = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 180 : 120;
// // HEADER CONTAINER
// self.headerContainer = [[UIView alloc] init];
// self.headerContainer.translatesAutoresizingMaskIntoConstraints = NO;
// self.headerContainer.backgroundColor = [UIColor colorWithRed:0 green:0.42 blue:0.55 alpha:1.0];
// [self.view addSubview:self.headerContainer];
//
// [NSLayoutConstraint activateConstraints:@[
// [self.headerContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
// [self.headerContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
// [self.headerContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
// [self.headerContainer.heightAnchor constraintEqualToConstant:topHeight],
// ]];
//
// // TOUCHABLE HEADER
// UIButton *profileTap = [UIButton buttonWithType:UIButtonTypeCustom];
// profileTap.translatesAutoresizingMaskIntoConstraints = NO;
// [profileTap addTarget:self action:@selector(profileTapped) forControlEvents:UIControlEventTouchUpInside];
// [self.headerContainer addSubview:profileTap];
//
// [NSLayoutConstraint activateConstraints:@[
// [profileTap.leadingAnchor constraintEqualToAnchor:self.headerContainer.leadingAnchor],
// [profileTap.trailingAnchor constraintEqualToAnchor:self.headerContainer.trailingAnchor],
// [profileTap.topAnchor constraintEqualToAnchor:self.headerContainer.topAnchor],
// [profileTap.bottomAnchor constraintEqualToAnchor:self.headerContainer.bottomAnchor],
// ]];
//
// // AVATAR
// CGFloat avatarSize = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 100 : 50;
// self.avatarView = [[UIImageView alloc] init];
// self.avatarView.translatesAutoresizingMaskIntoConstraints = NO;
// self.avatarView.layer.cornerRadius = avatarSize / 2.0;
// self.avatarView.clipsToBounds = YES;
// self.avatarView.layer.borderColor = [UIColor whiteColor].CGColor;
// self.avatarView.layer.borderWidth = 1.0;
// self.avatarView.contentMode = UIViewContentModeScaleAspectFill;
// [self.headerContainer addSubview:self.avatarView];
//
// [NSLayoutConstraint activateConstraints:@[
// [self.avatarView.leadingAnchor constraintEqualToAnchor:self.headerContainer.leadingAnchor constant:18],
// [self.avatarView.centerYAnchor constraintEqualToAnchor:self.headerContainer.centerYAnchor],
// [self.avatarView.widthAnchor constraintEqualToConstant:avatarSize],
// [self.avatarView.heightAnchor constraintEqualToConstant:avatarSize],
// ]];
// NAME LABEL
// self.nameLabel = [[UILabel alloc] init];
// self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
// self.nameLabel.textColor = UIColor.whiteColor;
// self.nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
// self.nameLabel.numberOfLines = 2;
// [self.headerContainer addSubview:self.nameLabel];
//
// // EMAIL LABEL
// self.emailLabel = [[UILabel alloc] init];
// self.emailLabel.translatesAutoresizingMaskIntoConstraints = NO;
// self.emailLabel.textColor = UIColor.whiteColor;
// self.emailLabel.font = [UIFont systemFontOfSize:14];
// [self.headerContainer addSubview:self.emailLabel];
//
// [NSLayoutConstraint activateConstraints:@[
// [self.nameLabel.leadingAnchor constraintEqualToAnchor:self.avatarView.trailingAnchor constant:16],
// [self.nameLabel.trailingAnchor constraintEqualToAnchor:self.headerContainer.trailingAnchor constant:-18],
// [self.nameLabel.bottomAnchor constraintEqualToAnchor:self.headerContainer.centerYAnchor constant:-4],
//
// [self.emailLabel.leadingAnchor constraintEqualToAnchor:self.nameLabel.leadingAnchor],
// [self.emailLabel.trailingAnchor constraintEqualToAnchor:self.nameLabel.trailingAnchor],
// [self.emailLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:4],
// ]];
// MENU TABLE
self.table = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.table.translatesAutoresizingMaskIntoConstraints = NO;
self.table.dataSource = self;
self.table.delegate = self;
self.table.backgroundColor = UIColor.clearColor;
self.table.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:self.table];
[NSLayoutConstraint activateConstraints:@[
// [self.table.topAnchor constraintEqualToAnchor:self.headerContainer.bottomAnchor],
[self.table.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:topHeight],
[self.table.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.table.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.table.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
}
- (void)loadDefaultsIfNeeded {
// if (!self.profileName) self.profileName = @"Guest User";
// if (!self.profileEmail) self.profileEmail = @"guest@example.com";
// if (!self.profileImageURL) self.profileImageURL = nil;
//
// self.nameLabel.text = self.profileName;
// self.emailLabel.text = self.profileEmail;
//
// if (self.profileImageURL) {
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// NSData *d = [NSData dataWithContentsOfURL:self.profileImageURL];
// UIImage *img = d ? [UIImage imageWithData:d] : [UIImage imageNamed:@"small_placeholder"];
// dispatch_async(dispatch_get_main_queue(), ^{
// self.avatarView.image = img;
// });
// });
// } else {
// self.avatarView.image = [UIImage imageNamed:@"small_placeholder"];
// }
if (!self.menuItems) {
self.menuItems = @[
// @{@"title": @"Project List", @"action": @"projectList"},
// @{@"title": @"Notification", @"action": @"notification"},
// @{@"title": @"Announcement", @"action": @"announcement"},
// @{@"title": @"Help", @"action": @"help"},
@{@"title": @"Sync", @"action": @"sync"},
// @{@"title": @"Logout", @"action": @"logout"}
];
}
[self.table reloadData];
}
#pragma mark - Profile tap
- (void)profileTapped {
// Post a notification to navigate
// [[NSNotificationCenter defaultCenter] postNotificationName:DrawerDidSelectItemNotification object:nil userInfo:@{@"action": @"profile"}];
}
#pragma mark - Table datasource/delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.menuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @"drawerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont systemFontOfSize:16];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSDictionary *item = self.menuItems[indexPath.row];
cell.textLabel.text = item[@"title"] ?: @"";
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56; }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *item = self.menuItems[indexPath.row];
NSString *action = item[@"action"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DrawerDidSelectItemNotification"
object:action];
}
- (void)handleDrawerAction:(NSNotification *)notification {
NSString *action = notification.object;
if ([action isEqualToString:@"projectList"]) {
// [self openProjectList];
NSLog(@"navigating to Project List");
}
else if ([action isEqualToString:@"notification"]) {
// [self openNotification];
NSLog(@"navigating to Notification");
}
else if ([action isEqualToString:@"announcement"]) {
// [self openAnnouncement];
NSLog(@"navigating to Announcement");
}
else if ([action isEqualToString:@"help"]) {
// [self openHelp];
NSLog(@"navigating to Help");
}
else if ([action isEqualToString:@"sync"]) {
NSLog(@"navigating to Sync");
SyncScreenViewController *nextVC = [[SyncScreenViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"logout"]) {
// [self performLogout];
NSLog(@"navigating to Logout");
}
}
@end
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#import "UnitViewController.h" #import "UnitViewController.h"
#import "ImageCacheHelper.h" #import "ImageCacheHelper.h"
static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification";
@interface ProjectCardCell : UICollectionViewCell @interface ProjectCardCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UILabel *nameLabel;
...@@ -111,29 +113,29 @@ ...@@ -111,29 +113,29 @@
} }
- (void)checkAndRefreshAPIs { - (void)checkAndRefreshAPIs {
NSLog(@"🔍 Checking credentials..."); // NSLog(@"🔍 Checking credentials...");
NSLog(@"ClientID: %@", self.ClientID); // NSLog(@"ClientID: %@", self.ClientID);
NSLog(@"ClientCode: %@", self.ClientCode); // NSLog(@"ClientCode: %@", self.ClientCode);
NSLog(@"user_token: %@", self.user_token); // NSLog(@"user_token: %@", self.user_token);
//
[self showLoadingOverlay:@"Loading..."]; // [self showLoadingOverlay:@"Loading..."];
//
if (![self isReadyToLoadDashboard]) { // if (![self isReadyToLoadDashboard]) {
NSLog(@"⏳ Waiting for all credentials to be ready..."); // NSLog(@"⏳ Waiting for all credentials to be ready...");
return; // return;
} // }
//
if (_isLoadingDashboard) return; // if (_isLoadingDashboard) return;
//
self.isLoadingDashboard = YES; // self.isLoadingDashboard = YES;
//
NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@", // NSLog(@"✅ All credentials ready. ClientID: %@, ClientCode: %@, user_token: %@",
self.ClientID, self.ClientCode, self.user_token); // self.ClientID, self.ClientCode, self.user_token);
//
//
[APIConfig setAuthTokem:self.user_token]; // [APIConfig setAuthTokem:self.user_token];
[APIConfig setClientId:self.ClientID]; // [APIConfig setClientId:self.ClientID];
// self.ClientCode = @"spt"; self.ClientCode = @"spt";
[self requestCompanyCode]; [self requestCompanyCode];
} }
...@@ -337,12 +339,13 @@ ...@@ -337,12 +339,13 @@
- (void)requestProjectList { - (void)requestProjectList {
[APIClient requestProjectList:^(BOOL success, NSDictionary *data, NSError *error) { [APIClient requestProjectList:^(BOOL success, NSDictionary *data, NSError *error) {
// debug // Save debug file
NSString *plainText = [NSString stringWithFormat:@"%@", data]; NSString *plainText = [NSString stringWithFormat:@"%@", data];
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [docsPath stringByAppendingPathComponent:@"projectList.txt"]; NSString *filePath = [docsPath stringByAppendingPathComponent:@"projectList.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; [plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 projectList saved as plain text to: %@", filePath); NSLog(@"📁 projectList saved as plain text to: %@", filePath);
[self hideLoadingOverlay]; [self hideLoadingOverlay];
self.isLoadingDashboard = NO; self.isLoadingDashboard = NO;
...@@ -351,7 +354,6 @@ ...@@ -351,7 +354,6 @@
return; return;
} }
// RN parity: status_code check
if (data[@"status_code"]) { if (data[@"status_code"]) {
NSLog(@"❌ API status error: %@", data[@"message"]); NSLog(@"❌ API status error: %@", data[@"message"]);
return; return;
...@@ -368,17 +370,17 @@ ...@@ -368,17 +370,17 @@
NSLog(@"❌ Invalid project list format"); NSLog(@"❌ Invalid project list format");
return; return;
} }
if (!error) { self.projects = projects;
self.projects = projects;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData]; [self.collectionView reloadData];
if (self.projects.count == 1) { // Auto-select first project if only 1
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; if (self.projects.count == 1) {
[self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
} [self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
}); }
} });
}]; }];
} }
...@@ -427,10 +429,60 @@ ...@@ -427,10 +429,60 @@
NSLog(@"baseURL: %@", baseURL); NSLog(@"baseURL: %@", baseURL);
[APIConfig setBaseURL:baseURL]; [APIConfig setBaseURL:baseURL];
[self requestProjectList]; [self requestProjectList];
[self requestProfile];
} }
}]; }];
} }
-(void) requestProfile {
[APIClient requestProfile:^(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:@"profile.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 profile saved as plain text to: %@", filePath);
[self hideLoadingOverlay];
self.isLoadingDashboard = NO;
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;
}
NSDictionary *profile = data[@"Data"];
if (![profile isKindOfClass:[NSDictionary class]]) {
NSLog(@"❌ Invalid profile list format");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *profileInfo = @{
@"name": profile[@"name"] ?: @"",
@"email": profile[@"email"] ?: @"",
@"avatar": profile[@"avatar"] ?: @""
};
NSLog(@"profile info ready to be used");
[[NSNotificationCenter defaultCenter] postNotificationName:ProfileDidUpdateNotification
object:nil
userInfo:profileInfo];
});
}];
}
#pragma mark - Helper #pragma mark - Helper
- (void)onBack { - (void)onBack {
[self.drawerController closeDrawer]; [self.drawerController closeDrawer];
......
// DrawerViewController.m
#import "DrawerViewController.h"
#import "DrawerController.h"
#import "SyncScreenViewController.h"
#import "ImageCacheHelper.h"
#import "ProfileViewController.h"
#import "NotificationViewController.h"
static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification";
@interface DrawerViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) UIImageView *avatarView;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *emailLabel;
@property (nonatomic, strong) UITableView *table;
@property (nonatomic, strong) UIView *headerContainer;
@end
@implementation DrawerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithRed:0.02 green:0.36 blue:0.47 alpha:1.0];
[self buildUI];
[self loadDefaultsIfNeeded];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDrawerAction:)
name:DrawerDidSelectItemNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onProfileUpdated:)
name:ProfileDidUpdateNotification
object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)buildUI {
self.view.backgroundColor = [UIColor clearColor];
CGFloat topHeight = 110;
// HEADER CONTAINER
self.headerContainer = [[UIView alloc] init];
self.headerContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.headerContainer.backgroundColor = [UIColor colorWithRed:0 green:0.42 blue:0.55 alpha:1.0];
[self.view addSubview:self.headerContainer];
[NSLayoutConstraint activateConstraints:@[
[self.headerContainer.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerContainer.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[self.headerContainer.heightAnchor constraintEqualToConstant:topHeight],
]];
// TOUCHABLE HEADER
UIButton *profileTap = [UIButton buttonWithType:UIButtonTypeCustom];
profileTap.translatesAutoresizingMaskIntoConstraints = NO;
[profileTap addTarget:self action:@selector(profileTapped) forControlEvents:UIControlEventTouchUpInside];
[self.headerContainer addSubview:profileTap];
[NSLayoutConstraint activateConstraints:@[
[profileTap.leadingAnchor constraintEqualToAnchor:self.headerContainer.leadingAnchor],
[profileTap.trailingAnchor constraintEqualToAnchor:self.headerContainer.trailingAnchor],
[profileTap.topAnchor constraintEqualToAnchor:self.headerContainer.topAnchor],
[profileTap.bottomAnchor constraintEqualToAnchor:self.headerContainer.bottomAnchor],
]];
// AVATAR
CGFloat avatarSize = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 100 : 50;
self.avatarView = [[UIImageView alloc] init];
self.avatarView.translatesAutoresizingMaskIntoConstraints = NO;
self.avatarView.layer.cornerRadius = avatarSize / 2.0;
self.avatarView.clipsToBounds = YES;
self.avatarView.layer.borderColor = [UIColor whiteColor].CGColor;
self.avatarView.layer.borderWidth = 1.0;
self.avatarView.contentMode = UIViewContentModeScaleAspectFill;
[self.headerContainer addSubview:self.avatarView];
[NSLayoutConstraint activateConstraints:@[
[self.avatarView.leadingAnchor constraintEqualToAnchor:self.headerContainer.leadingAnchor constant:18],
[self.avatarView.centerYAnchor constraintEqualToAnchor:self.headerContainer.centerYAnchor],
[self.avatarView.widthAnchor constraintEqualToConstant:avatarSize],
[self.avatarView.heightAnchor constraintEqualToConstant:avatarSize],
]];
//NAME LABEL
self.nameLabel = [[UILabel alloc] init];
self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.nameLabel.textColor = UIColor.whiteColor;
self.nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
self.nameLabel.numberOfLines = 2;
[self.headerContainer addSubview:self.nameLabel];
// EMAIL LABEL
self.emailLabel = [[UILabel alloc] init];
self.emailLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.emailLabel.textColor = UIColor.whiteColor;
self.emailLabel.font = [UIFont systemFontOfSize:14];
[self.headerContainer addSubview:self.emailLabel];
[NSLayoutConstraint activateConstraints:@[
[self.nameLabel.leadingAnchor constraintEqualToAnchor:self.avatarView.trailingAnchor constant:16],
[self.nameLabel.trailingAnchor constraintEqualToAnchor:self.headerContainer.trailingAnchor constant:-18],
[self.nameLabel.bottomAnchor constraintEqualToAnchor:self.headerContainer.centerYAnchor constant:-4],
[self.emailLabel.leadingAnchor constraintEqualToAnchor:self.nameLabel.leadingAnchor],
[self.emailLabel.trailingAnchor constraintEqualToAnchor:self.nameLabel.trailingAnchor],
[self.emailLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:4],
]];
// MENU TABLE
self.table = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.table.translatesAutoresizingMaskIntoConstraints = NO;
self.table.dataSource = self;
self.table.delegate = self;
self.table.backgroundColor = [UIColor colorWithRed:0.02 green:0.36 blue:0.47 alpha:1.0];
self.table.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:self.table];
[NSLayoutConstraint activateConstraints:@[
[self.table.topAnchor constraintEqualToAnchor:self.headerContainer.bottomAnchor],
// [self.table.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:topHeight],
[self.table.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.table.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.table.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
}
- (void)loadDefaultsIfNeeded {
if (!self.profileName) self.profileName = @"Guest User";
if (!self.profileEmail) self.profileEmail = @"guest@example.com";
if (!self.profileImageURL) self.profileImageURL = nil;
self.nameLabel.text = self.profileName;
self.emailLabel.text = self.profileEmail;
if (self.profileImageURL) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *d = [NSData dataWithContentsOfURL:self.profileImageURL];
UIImage *img = d ? [UIImage imageWithData:d] : [UIImage imageNamed:@"small_placeholder"];
dispatch_async(dispatch_get_main_queue(), ^{
self.avatarView.image = img;
});
});
} else {
self.avatarView.image = [UIImage imageNamed:@"small_placeholder"];
}
if (!self.menuItems) {
self.menuItems = @[
@{@"title": @"Notification", @"action": @"notification", @"icon": @"bell.fill"},
@{@"title": @"Announcement", @"action": @"announcement", @"icon": @"megaphone"},
@{@"title": @"Help", @"action": @"help", @"icon": @"questionmark"},
@{@"title": @"Sync", @"action": @"sync", @"icon": @"arrow.trianglehead.2.clockwise.rotate.90"},
@{@"title": @"Logout", @"action": @"logout", @"icon": @"rectangle.portrait.and.arrow.right" }
];
}
[self.table reloadData];
}
#pragma mark - Profile tap
- (void)profileTapped {
// Post a notification to navigate
NSLog(@"Sidemenu header tapped");
ProfileViewController *vc = [[ProfileViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
}
#pragma mark - Table datasource/delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.menuItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @"drawerCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font = [UIFont systemFontOfSize:16];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
NSDictionary *item = self.menuItems[indexPath.row];
cell.textLabel.text = item[@"title"] ?: @"";
NSString *iconName = item[@"icon"];
if (iconName.length > 0) {
UIImage *icon = [UIImage systemImageNamed:iconName];
cell.imageView.image = icon;
cell.imageView.tintColor = UIColor.whiteColor; // optional, make it match text color
} else {
cell.imageView.image = nil;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56; }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *item = self.menuItems[indexPath.row];
NSString *action = item[@"action"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DrawerDidSelectItemNotification"
object:action];
}
- (void)handleDrawerAction:(NSNotification *)notification {
NSString *action = notification.object;
if ([action isEqualToString:@"notification"]) {
NSLog(@"navigating to Notification");
NotificationViewController *nextVC = [[NotificationViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"announcement"]) {
// [self openAnnouncement];
NSLog(@"navigating to Announcement");
}
else if ([action isEqualToString:@"help"]) {
// [self openHelp];
NSLog(@"navigating to Help");
}
else if ([action isEqualToString:@"sync"]) {
NSLog(@"navigating to Sync");
SyncScreenViewController *nextVC = [[SyncScreenViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
}
else if ([action isEqualToString:@"logout"]) {
// [self performLogout];
NSLog(@"navigating to Logout");
}
}
- (void)onProfileUpdated:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSLog(@"profile update received");
NSString *name = info[@"name"] ?: @"Guest User";
NSString *email = info[@"email"] ?: @"guest@example.com";
NSString *avatarURLString = info[@"avatar"];
dispatch_async(dispatch_get_main_queue(), ^{
self.nameLabel.text = name;
self.emailLabel.text = email;
if (avatarURLString.length > 0) {
NSURL *url = [NSURL URLWithString:avatarURLString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
self.avatarView.image = image;
}];
});
} else {
self.avatarView.image = [UIImage imageNamed:@"small_placeholder"];
}
});
}
@end
// HelpViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface HelpViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// HelpViewController.mm
#import "HelpViewController.h"
#import <WebKit/WebKit.h>
@interface HelpViewController () <WKNavigationDelegate>
// Header
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
// WebView & loading
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) UIActivityIndicatorView *spinner;
// Bottom toolbar
@property (nonatomic, strong) UIView *bottomBar;
@property (nonatomic, strong) UIButton *btnBack;
@property (nonatomic, strong) UIButton *btnHome;
@property (nonatomic, strong) UIButton *btnForward;
@end
@implementation HelpViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
[self setupWebView];
[self setupBottomBar];
}
#pragma mark - Header
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
self.headerBar = [[UIView alloc] init];
self.headerBar.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.headerBar];
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44]
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Help";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:self.titleLabel];
// Back button
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:self.backButton];
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor]
]];
}
#pragma mark - WebView
- (void)setupWebView {
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero];
self.webView.navigationDelegate = self;
self.webView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.webView];
// Spinner
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
self.spinner.color = [UIColor systemBlueColor];
self.spinner.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.spinner];
[self.spinner startAnimating];
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:@[
[self.webView.topAnchor constraintEqualToAnchor:self.headerBar.bottomAnchor],
[self.webView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.webView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.webView.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor constant:-48], // leave space for bottom bar
[self.spinner.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.spinner.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]
]];
NSURL *url = [NSURL URLWithString:@"https://qms.support.commudesk.com/support/solutions"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
#pragma mark - Bottom Toolbar
- (void)setupBottomBar {
self.bottomBar = [[UIView alloc] init];
self.bottomBar.backgroundColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1.0];
self.bottomBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.bottomBar];
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:@[
[self.bottomBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.bottomBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.bottomBar.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor],
[self.bottomBar.heightAnchor constraintEqualToConstant:48]
]];
// Buttons
self.btnBack = [self createBottomButtonWithImage:@"arrow.backward"];
self.btnHome = [self createBottomButtonWithImage:@"house"];
self.btnForward = [self createBottomButtonWithImage:@"arrow.forward"];
[self.btnBack addTarget:self action:@selector(webBack) forControlEvents:UIControlEventTouchUpInside];
[self.btnHome addTarget:self action:@selector(webHome) forControlEvents:UIControlEventTouchUpInside];
[self.btnForward addTarget:self action:@selector(webForward) forControlEvents:UIControlEventTouchUpInside];
NSArray *buttons = @[self.btnBack, self.btnHome, self.btnForward];
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:buttons];
stack.axis = UILayoutConstraintAxisHorizontal;
stack.distribution = UIStackViewDistributionFillEqually;
stack.translatesAutoresizingMaskIntoConstraints = NO;
[self.bottomBar addSubview:stack];
[NSLayoutConstraint activateConstraints:@[
[stack.topAnchor constraintEqualToAnchor:self.bottomBar.topAnchor],
[stack.leadingAnchor constraintEqualToAnchor:self.bottomBar.leadingAnchor],
[stack.trailingAnchor constraintEqualToAnchor:self.bottomBar.trailingAnchor],
[stack.bottomAnchor constraintEqualToAnchor:self.bottomBar.bottomAnchor]
]];
}
- (UIButton *)createBottomButtonWithImage:(NSString *)systemName {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setImage:[UIImage systemImageNamed:systemName] forState:UIControlStateNormal];
btn.tintColor = [UIColor blackColor];
return btn;
}
#pragma mark - WebView Navigation Actions
- (void)webBack {
if ([self.webView canGoBack]) {
[self.webView goBack];
}
}
- (void)webForward {
if ([self.webView canGoForward]) {
[self.webView goForward];
}
}
- (void)webHome {
NSURL *url = [NSURL URLWithString:@"https://qms.support.commudesk.com/support/solutions"];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
[self.spinner startAnimating];
}
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[self.spinner stopAnimating];
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
[self.spinner stopAnimating];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Alert"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Back
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
// NotificationViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface NotificationViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
// NotificationViewController.mm
#import "NotificationViewController.h"
#import "APIClient.h"
@interface NotificationViewController ()
// 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) UIView *footerView;
@property (nonatomic, strong) UIStackView *stackView;
@end
@implementation NotificationViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
[self setupHeader];
[self setupScrollView];
[self setupFooterButton];
[self setupNotificationList];
[self addDummyNotifications];
}
#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];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
// Title
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"Notification";
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;
[self.backButton addTarget:self
action:@selector(onBack)
forControlEvents:UIControlEventTouchUpInside];
for (UIView *sub in @[self.titleLabel, self.backButton]) {
sub.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:sub];
}
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - Scroll View
- (void)setupScrollView {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.alwaysBounceVertical = YES;
[self.view addSubview:self.scrollView];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 64, 0);
self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset;
[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],
]];
self.contentView = [[UIView alloc] init];
[self.scrollView addSubview:self.contentView];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[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],
]];
self.footerView = [[UIView alloc] init];
self.footerView.backgroundColor = UIColor.clearColor;
[self.view addSubview:self.footerView];
self.footerView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.footerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.footerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.footerView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.footerView.heightAnchor constraintEqualToConstant:64],
]];
}
#pragma mark - Stack View
- (void)setupNotificationList {
self.stackView = [[UIStackView alloc] init];
self.stackView.axis = UILayoutConstraintAxisVertical;
self.stackView.spacing = 12;
self.stackView.alignment = UIStackViewAlignmentFill;
self.stackView.distribution = UIStackViewDistributionFill;
[self.contentView addSubview:self.stackView];
self.stackView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.stackView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:16],
[self.stackView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.stackView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.stackView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16],
]];
}
- (UIView *)createNotificationCardWithText:(NSString *)text date:(NSString *)date {
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];
icon.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *messageLabel = [[UILabel alloc] init];
messageLabel.text = text;
messageLabel.numberOfLines = 0;
messageLabel.font = [UIFont systemFontOfSize:15];
messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *dateLabel = [[UILabel alloc] init];
dateLabel.text = date;
dateLabel.font = [UIFont systemFontOfSize:12];
dateLabel.textColor = UIColor.secondaryLabelColor;
dateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[card addSubview:icon];
[card addSubview:messageLabel];
[card addSubview:dateLabel];
[NSLayoutConstraint activateConstraints:@[
[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],
]];
return card;
}
#pragma mark - Footer
- (void) setupFooterButton {
UIButton *clearButton = [UIButton buttonWithType:UIButtonTypeSystem];
[clearButton setTitle:@"Clear All Notification" forState:UIControlStateNormal];
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;
[self.footerView addSubview:clearButton];
clearButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[clearButton.bottomAnchor constraintEqualToAnchor:self.footerView.safeAreaLayoutGuide.bottomAnchor],
[clearButton.centerXAnchor constraintEqualToAnchor:self.footerView.centerXAnchor],
[clearButton.heightAnchor constraintEqualToConstant:48],
[clearButton.widthAnchor constraintEqualToConstant:245],
]];
}
#pragma mark - helpers
- (void)onBack {
[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"]];
[self.stackView addArrangedSubview:card];
}
UIView *endView = [self createEndOfListView];
[self.stackView addArrangedSubview:endView];
}
- (UIView *)createEndOfListView {
UILabel *label = [[UILabel alloc] init];
label.text = @"All items displayed";
label.font = [UIFont systemFontOfSize:13];
label.textColor = UIColor.secondaryLabelColor;
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
UIView *container = [[UIView alloc] init];
[container addSubview:label];
label.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[label.topAnchor constraintEqualToAnchor:container.topAnchor],
[label.bottomAnchor constraintEqualToAnchor:container.bottomAnchor],
[label.centerXAnchor constraintEqualToAnchor:container.centerXAnchor],
]];
return container;
}
@end
// ProfileViewController.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ProfileViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
//
// ProfileViewController.mm
#import "ProfileViewController.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
@interface ProfileViewController ()
// Header
@property (nonatomic, strong) UIView *headerBar;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIButton *backButton;
// ScrollView for content
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
// Profile Image
@property (nonatomic, strong) UIImageView *profileImageView;
@property (nonatomic, strong) UIButton *editProfileImageButton;
// Display Name
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UITextField *nameTextField;
@property (nonatomic, strong) UIButton *editNameButton;
// Buttons
@property (nonatomic, strong) UIButton *saveChangesButton;
@property (nonatomic, strong) UIButton *logoutButton;
@property (nonatomic, strong) UIButton *deleteAccountButton;
// Sections
@property (nonatomic, strong) UIView *profileSectionView;
@property (nonatomic, strong) UIView *settingsSectionView;
@property (nonatomic, strong) UILabel *emailLabel;
@property (nonatomic, strong) UILabel *contactLabel;
@property (nonatomic, strong) UITextField *linkNameField;
@property (nonatomic, strong) UIButton *createButton;
@property (nonatomic, strong) UIView *dimmedOverlay;
@property (nonatomic, strong) UIView *addLinkSheet;
@end
@implementation ProfileViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.systemBackgroundColor;
[self setupHeader];
[self setupScrollView];
[self setupProfile];
[self requestProfile];
}
#pragma mark - Header
- (void)setupHeader {
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
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];
self.headerBar.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.headerBar.topAnchor constraintEqualToAnchor:safe.topAnchor],
[self.headerBar.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.headerBar.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.headerBar.heightAnchor constraintEqualToConstant:44],
]];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.text = @"My Profile";
self.titleLabel.textColor = UIColor.whiteColor;
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.backButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.backButton setImage:[UIImage systemImageNamed:@"chevron.backward"] forState:UIControlStateNormal];
self.backButton.tintColor = UIColor.whiteColor;
[self.backButton addTarget:self action:@selector(onBack) forControlEvents:UIControlEventTouchUpInside];
self.backButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.headerBar addSubview:self.titleLabel];
[self.headerBar addSubview:self.backButton];
[NSLayoutConstraint activateConstraints:@[
[self.backButton.leadingAnchor constraintEqualToAnchor:self.headerBar.leadingAnchor constant:16],
[self.backButton.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
[self.backButton.heightAnchor constraintEqualToConstant:24],
[self.backButton.widthAnchor constraintEqualToConstant:24],
[self.titleLabel.centerXAnchor constraintEqualToAnchor:self.headerBar.centerXAnchor],
[self.titleLabel.centerYAnchor constraintEqualToAnchor:self.headerBar.centerYAnchor],
]];
}
#pragma mark - ScrollView Setup
- (void)setupScrollView {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
[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:safe.bottomAnchor],
]];
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 for vertical scroll
]];
}
#pragma mark - Profile Skeleton
- (void)setupProfile {
// --- Profile Section ---
self.profileSectionView = [[UIView alloc] init];
self.profileSectionView.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0]; // match header
self.profileSectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.profileSectionView];
// --- Settings Section ---
self.settingsSectionView = [[UIView alloc] init];
self.settingsSectionView.backgroundColor = UIColor.systemBackgroundColor;
self.settingsSectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:self.settingsSectionView];
// Layout constraints
[NSLayoutConstraint activateConstraints:@[
[self.profileSectionView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
[self.profileSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
[self.profileSectionView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
[self.settingsSectionView.topAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:16],
[self.settingsSectionView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:16],
[self.settingsSectionView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-16],
[self.settingsSectionView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-16],
]];
// --- Profile Section Contents ---
[self setupProfileSectionContents];
// --- Settings Section Contents ---
[self setupSettingsSectionContents];
}
- (void)setupProfileSectionContents {
CGFloat imageSize = 120.0;
// Profile Image
self.profileImageView = [[UIImageView alloc] init];
self.profileImageView.backgroundColor = UIColor.systemGray5Color;
self.profileImageView.layer.cornerRadius = imageSize / 2;
self.profileImageView.clipsToBounds = YES;
self.profileImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.profileImageView];
// Edit Image Button
self.editProfileImageButton =
[UIButton buttonWithType:UIButtonTypeSystem];
[self.editProfileImageButton setImage:
[UIImage systemImageNamed:@"camera"]
forState:UIControlStateNormal];
self.editProfileImageButton.tintColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
self.editProfileImageButton.backgroundColor = [UIColor whiteColor];
[self.editProfileImageButton addTarget:self
action:@selector(editProfileImageTapped)
forControlEvents:UIControlEventTouchUpInside];
self.editProfileImageButton.layer.cornerRadius = 18;
self.editProfileImageButton.translatesAutoresizingMaskIntoConstraints = NO;
[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.textColor = UIColor.whiteColor;
self.nameLabel.textAlignment = NSTextAlignmentCenter;
self.nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.nameLabel];
// Edit Name Button
self.editNameButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.editNameButton setImage:[UIImage systemImageNamed:@"pencil"] forState:UIControlStateNormal];
self.editNameButton.tintColor = UIColor.whiteColor;
self.editNameButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.editNameButton];
[self.editNameButton addTarget:self
action:@selector(showAddLinkSheet)
forControlEvents:UIControlEventTouchUpInside];
// Email Label
self.emailLabel = [[UILabel alloc] init];
self.emailLabel.text = @"example@mail.com";
self.emailLabel.font = [UIFont systemFontOfSize:16];
self.emailLabel.textColor = UIColor.whiteColor;
self.emailLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.emailLabel];
// Contact Label
self.contactLabel = [[UILabel alloc] init];
self.contactLabel.text = @"60123456789";
self.contactLabel.font = [UIFont systemFontOfSize:16];
self.contactLabel.textColor = UIColor.whiteColor;
self.contactLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.contactLabel];
// Layout
[NSLayoutConstraint activateConstraints:@[
[self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:16],
[self.profileImageView.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.profileImageView.widthAnchor constraintEqualToConstant:imageSize],
[self.profileImageView.heightAnchor constraintEqualToConstant:imageSize],
[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.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.emailLabel.topAnchor constraintEqualToAnchor:self.editNameButton.bottomAnchor constant:12],
[self.emailLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:8],
[self.contactLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.contactLabel.bottomAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:-16],
]];
}
- (void)setupSettingsSectionContents {
// Title
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"Account Settings";
titleLabel.font = [UIFont boldSystemFontOfSize:18];
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];
self.deleteAccountButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.deleteAccountButton setTitle:@"Delete My Account" forState:UIControlStateNormal];
[self.deleteAccountButton setTitleColor:UIColor.systemRedColor forState:UIControlStateNormal];
self.deleteAccountButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.settingsSectionView addSubview:self.deleteAccountButton];
[self.deleteAccountButton addTarget:self
action:@selector(deleteAccountTapped)
forControlEvents:UIControlEventTouchUpInside];
// Layout
[NSLayoutConstraint activateConstraints:@[
[titleLabel.topAnchor constraintEqualToAnchor:self.settingsSectionView.topAnchor constant:16],
[titleLabel.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
[changeLanguageButton.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:16],
[changeLanguageButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
[self.logoutButton.topAnchor constraintEqualToAnchor:changeLanguageButton.bottomAnchor constant:16],
[self.logoutButton.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor constant:16],
[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],
]];
}
#pragma mark - API
-(void) requestProfile {
[APIClient requestProfile:^(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:@"profile.txt"];
[plainText writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"📁 profile saved as plain text to: %@", filePath);
if (error || !data) {
NSLog(@"❌ profile API failed: %@", error);
return;
}
if (data[@"status_code"]) {
NSLog(@"❌ profile status error: %@", data[@"message"]);
return;
}
NSDictionary *appData = data[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ profile failure: %@", appData[@"message"]);
return;
}
NSDictionary *profile = data[@"Data"];
if (![profile isKindOfClass:[NSDictionary class]]) {
NSLog(@"❌ Invalid profile format");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [NSURL URLWithString:profile[@"avatar"]];
[ImageCacheHelper loadImageWithURL:url completion:^(UIImage *image) {
self.profileImageView.image = image;
}];
self.nameLabel.text = profile[@"name"] ?: @"";
self.emailLabel.text = profile[@"email"] ?: @"";
self.contactLabel.text = profile[@"contact"] ?: @"";
});
}];
}
#pragma mark - Helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)logoutTapped {
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:@"Alert"
message:@"Are you sure you want to logout?"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel =
[UIAlertAction actionWithTitle:@"No"
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *confirm =
[UIAlertAction actionWithTitle:@"Yes"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * _Nonnull action) {
// Clear session
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"userToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
// Exit profile
[self dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:cancel];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)deleteAccountTapped {
NSString *message =
@"By submitting this account deletion request, you are acknowledging that your account will be permanently deleted. Upon submitting this request, the account will be suspended and queued for deletion. You will no longer have access.";
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:@"Delete Account Confirmation"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel =
[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *confirm =
[UIAlertAction actionWithTitle:@"Confirm"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * _Nonnull action) {
[self performDeleteAccount];
}];
[alert addAction:cancel];
[alert addAction:confirm];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)performDeleteAccount {
// Example API call
[APIClient deleteProfile:^(BOOL success, NSDictionary *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error || !success) {
UIAlertController *fail = [UIAlertController alertControllerWithTitle:@"Error"
message:@"Failed to delete account. Please try again."
preferredStyle:UIAlertControllerStyleAlert];
[fail addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:fail animated:YES completion:nil];
return;
}
// Account deleted successfully
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"userToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
UIAlertController *successAlert = [UIAlertController alertControllerWithTitle:@"Account Deleted"
message:@"Your account has been successfully deleted."
preferredStyle:UIAlertControllerStyleAlert];
[successAlert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// Return to login / welcome screen
[self dismissViewControllerAnimated:YES completion:nil];
}]];
[self presentViewController:successAlert animated:YES completion:nil];
});
}];
}
#pragma mark - Add Link
- (UIView *)buildDimmedOverlay {
UIView *overlay = [[UIView alloc] initWithFrame:self.view.bounds];
overlay.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
overlay.alpha = 0;
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(dismissAddLinkSheet)];
[overlay addGestureRecognizer:tap];
return overlay;
}
- (UIView *)buildAddLinkSheet {
UIView *sheet = [[UIView alloc] init];
sheet.backgroundColor = UIColor.whiteColor;
sheet.layer.cornerRadius = 24;
sheet.layer.maskedCorners =
kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
sheet.translatesAutoresizingMaskIntoConstraints = NO;
UILabel *title = [[UILabel alloc] init];
title.text = @"Change Profile Name";
title.font = [UIFont systemFontOfSize:18 weight:UIFontWeightBold];
title.textAlignment = NSTextAlignmentCenter;
title.translatesAutoresizingMaskIntoConstraints = NO;
[sheet addSubview:title];
UILabel *subtitle = [[UILabel alloc] init];
subtitle.text = @"Modify your name and select Update to apply changes";
subtitle.font = [UIFont systemFontOfSize:14];
subtitle.textAlignment = NSTextAlignmentCenter;
subtitle.textColor = UIColor.darkGrayColor;
subtitle.numberOfLines = 0;
subtitle.lineBreakMode = NSLineBreakByWordWrapping;
subtitle.translatesAutoresizingMaskIntoConstraints = NO;
[sheet addSubview:subtitle];
self.linkNameField = [[UITextField alloc] init];
self.linkNameField.placeholder = @"Enter New Name";
self.linkNameField.borderStyle = UITextBorderStyleRoundedRect;
[self.linkNameField addTarget:self
action:@selector(onTextChanged:)
forControlEvents:UIControlEventEditingChanged];
self.linkNameField.translatesAutoresizingMaskIntoConstraints = NO;
[sheet addSubview:self.linkNameField];
self.createButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.createButton setTitle:@"Create" forState:UIControlStateNormal];
self.createButton.enabled = NO;
self.createButton.backgroundColor = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
self.createButton.layer.cornerRadius = 10;
[self.createButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[self.createButton addTarget:self
action:@selector(updateTapped)
forControlEvents:UIControlEventTouchUpInside];
self.createButton.translatesAutoresizingMaskIntoConstraints = NO;
[sheet addSubview:self.createButton];
[NSLayoutConstraint activateConstraints:@[
[title.centerXAnchor constraintEqualToAnchor:sheet.centerXAnchor],
[title.topAnchor constraintEqualToAnchor:sheet.topAnchor constant:28],
[subtitle.topAnchor constraintEqualToAnchor:title.bottomAnchor constant:12],
[subtitle.leadingAnchor constraintEqualToAnchor:sheet.leadingAnchor constant:16],
[subtitle.trailingAnchor constraintEqualToAnchor:sheet.trailingAnchor constant:-16],
[self.linkNameField.topAnchor constraintEqualToAnchor:subtitle.bottomAnchor constant:24],
[self.linkNameField.leadingAnchor constraintEqualToAnchor:sheet.leadingAnchor constant:20],
[self.linkNameField.trailingAnchor constraintEqualToAnchor:sheet.trailingAnchor constant:-20],
[self.linkNameField.heightAnchor constraintEqualToConstant:48],
[self.createButton.topAnchor constraintEqualToAnchor:self.linkNameField.bottomAnchor constant:32],
[self.createButton.leadingAnchor constraintEqualToAnchor:subtitle.leadingAnchor],
[self.createButton.trailingAnchor constraintEqualToAnchor:subtitle.trailingAnchor],
[self.createButton.heightAnchor constraintEqualToConstant:48],
[self.createButton.bottomAnchor constraintEqualToAnchor:sheet.safeAreaLayoutGuide.bottomAnchor constant:-24],
]];
return sheet;
}
- (void)showAddLinkSheet {
self.dimmedOverlay = [self buildDimmedOverlay];
self.addLinkSheet = [self buildAddLinkSheet];
[self.view addSubview:self.dimmedOverlay];
[self.view addSubview:self.addLinkSheet];
NSLayoutConstraint *bottom =
[self.addLinkSheet.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:300];
bottom.active = YES;
[NSLayoutConstraint activateConstraints:@[
[self.addLinkSheet.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.addLinkSheet.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.addLinkSheet.heightAnchor constraintEqualToConstant:320],
]];
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.3 animations:^{
self.dimmedOverlay.alpha = 1;
bottom.constant = 0;
[self.view layoutIfNeeded];
}];
}
- (void)dismissAddLinkSheet {
[UIView animateWithDuration:0.25 animations:^{
self.dimmedOverlay.alpha = 0;
self.addLinkSheet.transform = CGAffineTransformMakeTranslation(0, 300);
} completion:^(BOOL finished) {
[self.addLinkSheet removeFromSuperview];
[self.dimmedOverlay removeFromSuperview];
self.linkNameField.text = @"";
}];
}
- (void)onTextChanged:(UITextField *)textField {
BOOL hasText = textField.text.length > 0;
self.createButton.enabled = hasText;
self.createButton.backgroundColor =
hasText ? [UIColor colorWithRed:0.05 green:0.45 blue:0.57 alpha:1.0]
: UIColor.systemGray5Color;
}
-(void)updateTapped{
NSLog(@"update tapped");
[APIClient editProfile:self.linkNameField.text completion:^(BOOL success, NSDictionary * _Nullable data, NSError * _Nullable error) {
if (!success) {
NSLog(@"❌ API returned success = NO");
return;
}
if (error || !data) {
NSLog(@"❌ Project list API failed: %@", error);
return;
}
// RN parity: status_code check
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;
}
if (!error) {
NSLog(@"Successfully edited profile name");
[self dismissAddLinkSheet];
[self requestProfile];
}
}];
}
- (void)editProfileImageTapped {
NSLog(@"Edit profile image tapped");
}
@end
...@@ -89,6 +89,13 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -89,6 +89,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)updateThirdPartyLink:(NSDictionary *)payload + (void)updateThirdPartyLink:(NSDictionary *)payload
completion:(void(^)(NSDictionary * _Nullable response,NSError * _Nullable error))completion; completion:(void(^)(NSDictionary * _Nullable response,NSError * _Nullable error))completion;
+ (void)requestProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)deleteProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)editProfile:(NSString *)name
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -2128,6 +2128,197 @@ ...@@ -2128,6 +2128,197 @@
[task resume]; [task resume];
} }
+ (void)requestProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Profile" completion:completion]) {
return;
}
NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/user"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *jsonBody = @{
@"data": @{
@"os": [APIConfig os] ,
@"information": [APIConfig deviceInfo],
}
};
NSLog(@"🌍 request profile: %@", url.absoluteString);
NSLog(@"📦 request body 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:@"Profile_%@", [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)deleteProfile:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/accDeleteRequest"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *jsonBody = @{
@"data": @{
@"os": [APIConfig os] ,
@"information": [APIConfig deviceInfo],
}
};
NSLog(@"🌍 delete profile: %@", url.absoluteString);
NSLog(@"📦 delete body 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;
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(YES, json, nil);
});
}];
[task resume];
}
+ (void)editProfile:(NSString *)name
completion:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/editUser"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSDictionary *jsonBody = @{
@"data": @{
@"name": name ?: @"",
@"language": @"2",
@"os": [APIConfig os] ,
@"information": [APIConfig deviceInfo],
}
};
NSLog(@"🌍 edit profile: %@", url.absoluteString);
NSLog(@"📦 edit profile 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;
}
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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment