Commit 1323f31a authored by Wei Han's avatar Wei Han

fixed drawer logic

parent 7735252a
......@@ -330,7 +330,7 @@
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
@end
......
......@@ -4,8 +4,6 @@
@interface AppointmentDetailsViewController : UIViewController
@property (nonatomic, strong) NSString *appointmentID;
@property (nonatomic, strong) NSDictionary *appointmentData;
@end
......@@ -72,6 +72,8 @@
NSLog(@"dashboard.drawerController = %@", self.drawerController);
//[[OfflineSyncManager shared] clearOfflineQueue];
[[NSNotificationCenter defaultCenter] postNotificationName:@"ShowHiddenButtonNotification" object:@(YES)];
}
- (void)viewWillAppear:(BOOL)animated {
......@@ -102,7 +104,7 @@
}];
dispatch_group_enter(group);
[APIClient requestAnnouncement:^(BOOL success, NSDictionary *data, NSError *error) {
[APIClient requestClientAnnouncement:^(BOOL success, NSDictionary *data, NSError *error) {
if (!error) announcementData = data;
dispatch_group_leave(group);
}];
......@@ -1431,7 +1433,6 @@
[self presentViewController:vc animated:YES completion:nil];
}
- (void)handleIssueTap:(UITapGestureRecognizer *)gesture {
UIView *tappedCard = gesture.view;
NSDictionary *issueData = objc_getAssociatedObject(tappedCard, "issueData");
......
// DrawerController.m
#import "DrawerController.h"
#import "ProjectViewController.h"
#import "DrawerViewController.h"
NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotification";
@interface DrawerController () <UIGestureRecognizerDelegate>
@property (nonatomic, strong) ProjectViewController *mainVC;
@property (nonatomic, strong) UIViewController *mainVC;
@property (nonatomic, strong) UINavigationController *mainNav;
@property (nonatomic, strong) UIViewController *drawerVC;
@property (nonatomic, strong) UIView *overlayView;
@property (nonatomic, assign) BOOL drawerOpen;
......@@ -27,12 +29,14 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
if (!self) return nil;
// 1️⃣ Initialize DashboardViewController with props
_mainVC = [[ProjectViewController alloc] initWithClientID:clientID
ProjectViewController *rootVC = [[ProjectViewController alloc] initWithClientID:clientID
clientCode:clientCode
userToken:user_token];
// 2️⃣ Set back-reference to drawerController
_mainVC.drawerController = self;
rootVC.drawerController = self;
_mainNav = [[UINavigationController alloc] initWithRootViewController:rootVC];
_mainVC = _mainNav;
// 3️⃣ Save props locally
_ClientID = [clientID copy];
......@@ -41,6 +45,11 @@ NSString * const DrawerDidSelectItemNotification = @"DrawerDidSelectItemNotifica
// 4️⃣ Set drawer VC and drawer width
_drawerVC = drawer;
if ([_drawerVC isKindOfClass:[DrawerViewController class]]) {
[(DrawerViewController *)_drawerVC attachDrawerController:self];
}
_drawerWidth = MIN([UIScreen mainScreen].bounds.size.width * 0.78, 360);
return self;
......
// DrawerViewController.h
#import <UIKit/UIKit.h>
#import "DrawerController.h"
NS_ASSUME_NONNULL_BEGIN
......@@ -10,7 +11,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSString *profileEmail;
@property (nonatomic, strong) NSURL *profileImageURL;
@property (nonatomic, strong) NSArray<NSDictionary *> *menuItems; // array of { @"title":..., @"action":... }
@property (nonatomic, weak, readonly) DrawerController *drawerController;
- (void)attachDrawerController:(DrawerController *)drawerController;
@end
NS_ASSUME_NONNULL_END
......@@ -7,6 +7,7 @@
#import "NotificationViewController.h"
#import "HelpViewController.h"
#import "AnnounceViewController.h"
#import "ProjectViewController.h"
static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotification";
......@@ -17,7 +18,7 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
@property (nonatomic, strong) UILabel *emailLabel;
@property (nonatomic, strong) UITableView *table;
@property (nonatomic, strong) UIView *headerContainer;
@property (nonatomic, weak, readwrite) DrawerController *drawerController;
@end
@implementation DrawerViewController
......@@ -38,6 +39,12 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
selector:@selector(onProfileUpdated:)
name:ProfileDidUpdateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleShowHiddenButton:)
name:@"ShowHiddenButtonNotification"
object:nil];
}
- (void)dealloc {
......@@ -157,21 +164,23 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
}
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.menuItems = [@[
[@{@"title": @"Project List", @"action": @"list", @"icon": @"", @"hidden": @YES} mutableCopy],
[@{@"title": @"Notification", @"action": @"notification", @"icon": @"bell.fill"} mutableCopy],
[@{@"title": @"Announcement", @"action": @"announcement", @"icon": @"megaphone"} mutableCopy],
[@{@"title": @"Help", @"action": @"help", @"icon": @"questionmark"} mutableCopy],
[@{@"title": @"Sync", @"action": @"sync", @"icon": @"arrow.trianglehead.2.clockwise.rotate.90"} mutableCopy],
[@{@"title": @"Logout", @"action": @"logout", @"icon": @"rectangle.portrait.and.arrow.right"} mutableCopy]
] mutableCopy];
}
[self.table reloadData];
}
#pragma mark - Profile tap
- (void)profileTapped {
// Post a notification to navigate
NSLog(@"Sidemenu header tapped");
DrawerController *drawerController = self.drawerController;
[drawerController closeDrawer];
ProfileViewController *vc = [[ProfileViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
......@@ -179,7 +188,12 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
#pragma mark - Table datasource/delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.menuItems.count;
NSPredicate *predicate =
[NSPredicate predicateWithBlock:^BOOL(NSDictionary *item, NSDictionary *bindings) {
return ![item[@"hidden"] boolValue];
}];
return [[self.menuItems filteredArrayUsingPredicate:predicate] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
......@@ -194,7 +208,7 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
NSDictionary *item = self.menuItems[indexPath.row];
NSDictionary *item = [self visibleMenuItems][indexPath.row];
cell.textLabel.text = item[@"title"] ?: @"";
NSString *iconName = item[@"icon"];
......@@ -212,49 +226,85 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 56; }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *item = self.menuItems[indexPath.row];
NSDictionary *item = [self visibleMenuItems][indexPath.row];
NSString *action = item[@"action"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"DrawerDidSelectItemNotification"
object:action];
}
- (NSArray *)visibleMenuItems {
NSPredicate *predicate =
[NSPredicate predicateWithBlock:^BOOL(NSDictionary *item, NSDictionary *bindings) {
return ![item[@"hidden"] boolValue];
}];
return [self.menuItems filteredArrayUsingPredicate:predicate];
}
- (void)showHiddenButton:(BOOL)show {
for (NSMutableDictionary *item in self.menuItems) {
if ([item[@"action"] isEqualToString:@"list"]) {
item[@"hidden"] = @(!show);
}
}
[self.table reloadData]; // ensures table updates immediately
}
- (void)handleShowHiddenButton:(NSNotification *)notification {
NSLog(@"hidden button toggled");
BOOL show = [notification.object boolValue];
[self showHiddenButton:show];
}
- (void)handleDrawerAction:(NSNotification *)notification {
NSString *action = notification.object;
NSLog(@"Drawer action: %@", action);
if ([action isEqualToString:@"notification"]) {
NSLog(@"navigating to Notification");
DrawerController *drawerController = self.drawerController;
NSLog(@"drawerController: %@", drawerController);
if (!drawerController) return;
NotificationViewController *nextVC = [[NotificationViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
UIViewController *mainVC = drawerController.mainViewController;
UINavigationController *nav = nil;
if ([mainVC isKindOfClass:[UINavigationController class]]) {
nav = (UINavigationController *)mainVC;
} else {
NSLog(@"❌ mainVC is not UINavigationController");
return;
}
// Always close drawer first
[drawerController closeDrawer];
if ([action isEqualToString:@"list"]) {
ProjectViewController *vc = [[ProjectViewController alloc] init];
vc.drawerController = drawerController;
[nav setViewControllers:@[vc] animated:NO]; // reset stack to Project list
}
else if ([action isEqualToString:@"notification"]) {
NotificationViewController *vc = [[NotificationViewController alloc] init];
vc.drawerController = drawerController;
[nav pushViewController:vc animated:YES];
}
else if ([action isEqualToString:@"announcement"]) {
NSLog(@"navigating to Announcement");
AnnouncementViewController *nextVC = [[AnnouncementViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
AnnouncementViewController *vc = [[AnnouncementViewController alloc] init];
[nav pushViewController:vc animated:YES];
}
else if ([action isEqualToString:@"help"]) {
NSLog(@"navigating to Help");
HelpViewController *nextVC = [[HelpViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
HelpViewController *vc = [[HelpViewController alloc] init];
[nav pushViewController:vc animated:YES];
}
else if ([action isEqualToString:@"sync"]) {
NSLog(@"navigating to Sync");
SyncScreenViewController *nextVC = [[SyncScreenViewController alloc] init];
nextVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nextVC animated:YES completion:nil];
SyncScreenViewController *vc = [[SyncScreenViewController alloc] init];
[nav pushViewController:vc animated:YES];
}
else if ([action isEqualToString:@"logout"]) {
// [self performLogout];
NSLog(@"navigating to Logout");
[self logoutTapped];
}
}
- (void)onProfileUpdated:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
......@@ -281,4 +331,37 @@ static NSString * const ProfileDidUpdateNotification = @"ProfileDidUpdateNotific
});
}
- (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)attachDrawerController:(DrawerController *)drawerController {
_drawerController = drawerController;
}
@end
......@@ -190,7 +190,7 @@
#pragma mark - Back
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
@end
// NotificationViewController.h
#import "DrawerController.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface NotificationViewController : UIViewController
@property (nonatomic, weak) DrawerController *drawerController;
@end
NS_ASSUME_NONNULL_END
......@@ -3,6 +3,12 @@
#import "NotificationViewController.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
#import <objc/runtime.h>
#import "APIConfig.h"
#import "AppointmentDetailsViewController.h"
#import "DashboardViewController.h"
static char kNotificationKey;
@interface NotificationViewController ()
// Header (existing)
......@@ -15,6 +21,7 @@
@property (nonatomic, strong) UIStackView *stackView;
@property (nonatomic, strong) NSArray *notifications;
@property (nonatomic, strong) NSArray *projectList;
@property (nonatomic, strong) NSString *oldUnitId;
@end
@implementation NotificationViewController
......@@ -22,6 +29,9 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
if (self.oldUnitId) {
[APIConfig setDrawingPlanId:self.oldUnitId];
}
[self requestProjectList];
[self setupHeader];
......@@ -157,7 +167,6 @@
[card addGestureRecognizer:tap];
card.userInteractionEnabled = YES;
UIImageView *icon = [[UIImageView alloc] init];
icon.translatesAutoresizingMaskIntoConstraints = NO;
icon.contentMode = UIViewContentModeScaleAspectFit;
......@@ -172,15 +181,12 @@
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 =
......@@ -266,6 +272,11 @@
BOOL unread = [item[@"read_status"] isEqualToString:@"0"];
unreadDot.hidden = !unread;
objc_setAssociatedObject(card,
&kNotificationKey,
item,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return card;
}
......@@ -291,7 +302,7 @@
#pragma mark - helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)requestNotification {
......@@ -399,22 +410,158 @@
}
- (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];
// }
NSDictionary *item =
objc_getAssociatedObject(card, &kNotificationKey);
if (!item) return;
NSString *notificationType = item[@"type"];
NSDictionary *strData = item;
self.oldUnitId = [APIConfig drawingPlanId];
NSString *unitID = @"";
if (strData[@"draw_plan_id"]) {
unitID = strData[@"draw_plan_id"];
[APIConfig setDrawingPlanId:unitID];
}
// ---- PAYMENT REMINDER ----
if ([notificationType isEqualToString:@"payment_reminder"]) {
NSLog(@"➡️ payment_reminder tapped");
return;
}
// ---- APPOINTMENT ----
if ([notificationType isEqualToString:@"appointment_reminder"] ||
[notificationType isEqualToString:@"unattend_appt_reminder"]) {
NSString *appointmentID = @"";
if (strData[@"appointment_id"]) {
appointmentID = strData[@"appointment_id"];
}
[self openAppointmentWithUnit:unitID appointmentID:appointmentID];
return;
}
// ---- CLEARANCE LETTER ----
if ([notificationType isEqualToString:@"clearance_letter"]) {
NSString *projectID = @"";
if (strData[@"project_id"]) {
projectID = strData[@"project_id"];
}
[self openClearanceWithProject:projectID unitID:unitID];
return;
}
// ---- DEFAULT: ISSUE FLOW ----
NSString *issueID = @"";
if (strData[@"issue_id"]) {
issueID = strData[@"issue_id"];
}
[self openIssueWithUnit:unitID issueID:issueID];
}
#pragma mark - Navigation (ported from RN)
- (void)openAppointmentWithUnit:(NSString *)unitID appointmentID:(NSString *)appointmentID {
NSLog(@"➡️ Open Appointment | unit=%@ appt=%@", unitID, appointmentID);
[APIClient requestDashboardInfo:^(BOOL success, NSDictionary * _Nullable data, NSError * _Nonnull error) {
if (!success || !data) {
return;
}
id appointment = data[@"Data"][@"appointment"];
if (!appointment ||
appointment == [NSNull null] ||
![appointment isKindOfClass:[NSArray class]] ||
appointmentID.length == 0) {
return;
}
NSDictionary *matchedAppointment = nil;
for (NSDictionary *appt in appointment) {
if (![appt isKindOfClass:[NSDictionary class]]) {
continue;
}
NSString *apptID = appt[@"id"];
if ([apptID isKindOfClass:[NSString class]] &&
[apptID isEqualToString:appointmentID]) {
matchedAppointment = appt;
break;
}
}
if (!matchedAppointment) {
// No match found
return;
}
// 👉 Navigate to Appointment Details
dispatch_async(dispatch_get_main_queue(), ^{
AppointmentDetailsViewController *vc = [[AppointmentDetailsViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
vc.appointmentData = matchedAppointment;
[self presentViewController:vc animated:YES completion:nil];
});
}];
}
- (void)openClearanceWithProject:(NSString *)projectID unitID:(NSString *)unitID {
NSLog(@"➡️ Open Clearance | project=%@ unit=%@", projectID, unitID);
NSArray *projects = self.projectList;
if (!projects || projects.count == 0) {
NSLog(@"❌ Project list empty");
return;
}
for (NSDictionary *project in projects) {
if (![project isKindOfClass:[NSDictionary class]]) continue;
NSString *projID = [NSString stringWithFormat:@"%@", project[@"id"]];
if (![projID isEqualToString:projectID]) continue;
NSArray *units = project[@"unit"];
if (![units isKindOfClass:[NSArray class]]) break;
for (NSDictionary *unit in units) {
if (![unit isKindOfClass:[NSDictionary class]]) continue;
NSString *uID = [NSString stringWithFormat:@"%@", unit[@"id"]];
if (![uID isEqualToString:unitID]) continue;
NSLog(@"✅ Found project %@ and unit %@", projectID, unitID);
dispatch_async(dispatch_get_main_queue(), ^{
DashboardViewController *vc = [[DashboardViewController alloc] init];
vc.projectCode = unit[@"name"] ?: @"";
vc.projectName = project[@"project_name"] ?: @"";
vc.projectId = projectID ?: @"";
vc.drawingPlanId = unitID ?: @"";
vc.projectLogo = project[@"logo"] ?: @"";
vc.lotId = unit[@"lot_id"] ?: @"";
vc.drawerController = self.drawerController;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController pushViewController:vc animated:YES];
});
return;
}
}
NSLog(@"❌ No matching project/unit found");
}
- (void)openIssueWithUnit:(NSString *)unitID issueID:(NSString *)issueID {
NSLog(@"➡️ Open Issue | unit=%@ issue=%@", unitID, issueID);
// TODO: push IssueDetailViewController
}
@end
......@@ -3,8 +3,9 @@
#import "ProfileViewController.h"
#import "APIClient.h"
#import "ImageCacheHelper.h"
#import <Photos/Photos.h>
@interface ProfileViewController ()
@interface ProfileViewController () <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIScrollViewDelegate>
// Header
@property (nonatomic, strong) UIView *headerBar;
......@@ -42,6 +43,13 @@
@property (nonatomic, strong) UIView *addLinkSheet;
@property (nonatomic, strong) UILabel *usernameLabel;
@property (nonatomic, strong) UIImage *selectedProfileImage;
@property (nonatomic, strong) NSData *selectedProfileImageData;
@property (nonatomic, strong) UIView *imageViewer;
@property (nonatomic, strong) UIScrollView *imageScrollView;
@property (nonatomic, strong) UIImageView *fullscreenImageView;
@end
@implementation ProfileViewController
......@@ -167,9 +175,16 @@
// Profile Image
self.profileImageView = [[UIImageView alloc] init];
self.profileImageView.backgroundColor = UIColor.systemGray5Color;
self.profileImageView.layer.cornerRadius = imageSize / 2;
self.profileImageView.clipsToBounds = YES;
self.profileImageView.layer.cornerRadius = imageSize / 2;
self.profileImageView.translatesAutoresizingMaskIntoConstraints = NO;
self.profileImageView.contentMode = UIViewContentModeScaleAspectFill;
self.profileImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(profileImageTapped)];
[self.profileImageView addGestureRecognizer:tap];
[self.profileSectionView addSubview:self.profileImageView];
// Edit Image Button
......@@ -208,7 +223,7 @@
self.editNameButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.profileSectionView addSubview:self.editNameButton];
[self.editNameButton addTarget:self
action:@selector(showAddLinkSheet)
action:@selector(editNamePressed)
forControlEvents:UIControlEventTouchUpInside];
self.usernameLabel = [[UILabel alloc] init];
......@@ -247,7 +262,7 @@
// Layout
[NSLayoutConstraint activateConstraints:@[
[self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:16],
[self.profileImageView.topAnchor constraintEqualToAnchor:self.profileSectionView.topAnchor constant:24],
[self.profileImageView.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.profileImageView.widthAnchor constraintEqualToConstant:imageSize],
[self.profileImageView.heightAnchor constraintEqualToConstant:imageSize],
......@@ -257,23 +272,23 @@
[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.topAnchor constraintEqualToAnchor:self.editProfileImageButton.bottomAnchor constant:35],
[self.nameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.editNameButton.centerYAnchor constraintEqualToAnchor:self.nameLabel.centerYAnchor],
[self.editNameButton.leadingAnchor constraintEqualToAnchor:self.nameLabel.trailingAnchor constant:6],
[self.usernameLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:12],
[self.usernameLabel.topAnchor constraintEqualToAnchor:self.nameLabel.bottomAnchor constant:19],
[self.usernameLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.usernameLabel.heightAnchor constraintEqualToConstant:28],
[self.usernameLabel.widthAnchor constraintGreaterThanOrEqualToConstant:120],
[self.emailLabel.topAnchor constraintEqualToAnchor:self.usernameLabel.bottomAnchor constant:12],
[self.emailLabel.topAnchor constraintEqualToAnchor:self.usernameLabel.bottomAnchor constant:16],
[self.emailLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:8],
[self.contactLabel.topAnchor constraintEqualToAnchor:self.emailLabel.bottomAnchor constant:12],
[self.contactLabel.centerXAnchor constraintEqualToAnchor:self.profileSectionView.centerXAnchor],
[self.contactLabel.bottomAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:-16],
[self.contactLabel.bottomAnchor constraintEqualToAnchor:self.profileSectionView.bottomAnchor constant:-32],
]];
}
......@@ -314,6 +329,16 @@
UIView *separator = [self separatorView];
[card addSubview:separator];
UIButton *switchAccountButton = [self settingsRowWithTitle:@"Switch Account"
icon:@"person.crop.circle.badge.plus"
textColor:UIColor.labelColor
action:@selector(switchAccountTapped)];
[card addSubview:switchAccountButton];
UIView *separator2 = [self separatorView];
[card addSubview:separator2];
// ---- Logout row ----
UIButton *logoutButton =
[self settingsRowWithTitle:@"Log Out"
......@@ -323,9 +348,9 @@
[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 = [self settingsRowWithTitle:@"Delete My Account"
icon:@"person" textColor:UIColor.systemRedColor
action:@selector(deleteAccountTapped)];
self.deleteAccountButton.backgroundColor =
[[UIColor systemRedColor] colorWithAlphaComponent:0.1];
self.deleteAccountButton.layer.cornerRadius = 14;
......@@ -339,7 +364,7 @@
// ---- Layout ----
[NSLayoutConstraint activateConstraints:@[
// Title
[titleLabel.topAnchor constraintEqualToAnchor:self.settingsSectionView.topAnchor constant:16],
[titleLabel.topAnchor constraintEqualToAnchor:self.settingsSectionView.topAnchor constant:24],
[titleLabel.leadingAnchor constraintEqualToAnchor:self.settingsSectionView.leadingAnchor],
// Card
......@@ -351,7 +376,7 @@
[changeLanguageButton.topAnchor constraintEqualToAnchor:card.topAnchor],
[changeLanguageButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor],
[changeLanguageButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor],
[changeLanguageButton.heightAnchor constraintEqualToConstant:52],
[changeLanguageButton.heightAnchor constraintEqualToConstant:48],
// Language text
[langLabel.centerYAnchor constraintEqualToAnchor:changeLanguageButton.centerYAnchor],
......@@ -363,11 +388,23 @@
[separator.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[separator.heightAnchor constraintEqualToConstant:1],
// Switch Button row
[switchAccountButton.topAnchor constraintEqualToAnchor:separator.topAnchor],
[switchAccountButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor],
[switchAccountButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor],
[switchAccountButton.heightAnchor constraintEqualToConstant:48],
// Separator2
[separator2.topAnchor constraintEqualToAnchor:switchAccountButton.bottomAnchor],
[separator2.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:16],
[separator2.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-16],
[separator2.heightAnchor constraintEqualToConstant:1],
// Logout row
[logoutButton.topAnchor constraintEqualToAnchor:separator.bottomAnchor],
[logoutButton.topAnchor constraintEqualToAnchor:separator2.bottomAnchor],
[logoutButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor],
[logoutButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor],
[logoutButton.heightAnchor constraintEqualToConstant:52],
[logoutButton.heightAnchor constraintEqualToConstant:48],
// Card bottom
[card.bottomAnchor constraintEqualToAnchor:logoutButton.bottomAnchor],
......@@ -469,6 +506,32 @@
}];
}
- (void)uploadProfileImage {
if (!self.selectedProfileImageData) return;
[APIClient uploadProfileImage:self.selectedProfileImageData
name:self.nameLabel.text
completion:^(BOOL success,
NSDictionary *response,
NSError *error) {
if (!success || error) {
NSLog(@"❌ Upload failed: %@", error);
return;
}
NSDictionary *appData = response[@"AppData"];
if (![appData[@"status"] isEqualToString:@"success"]) {
NSLog(@"❌ API error: %@", appData[@"message"]);
return;
}
NSLog(@"✅ Profile image updated");
[self requestProfile];
}];
}
- (UIView *)separatorView {
UIView *view = [[UIView alloc] init];
view.backgroundColor = UIColor.systemGray4Color;
......@@ -510,7 +573,6 @@
[self presentViewController:alert animated:YES completion:nil];
}
- (void)deleteAccountTapped {
NSString *message =
......@@ -652,7 +714,7 @@
return sheet;
}
- (void)showAddLinkSheet {
- (void)editNamePressed {
self.dimmedOverlay = [self buildDimmedOverlay];
self.addLinkSheet = [self buildAddLinkSheet];
......@@ -730,8 +792,148 @@
}];
}
#pragma mark - Profile Picture
- (void)editProfileImageTapped {
NSLog(@"Edit profile image tapped");
UIAlertController *sheet =
[UIAlertController alertControllerWithTitle:@"Change Profile Picture"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *camera =
[UIAlertAction actionWithTitle:@"Camera"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openImagePicker:UIImagePickerControllerSourceTypeCamera];
}];
UIAlertAction *gallery =
[UIAlertAction actionWithTitle:@"Photo Library"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
[self openImagePicker:UIImagePickerControllerSourceTypePhotoLibrary];
}];
UIAlertAction *cancel =
[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
[sheet addAction:camera];
[sheet addAction:gallery];
[sheet addAction:cancel];
[self presentViewController:sheet animated:YES completion:nil];
}
- (void)openImagePicker:(UIImagePickerControllerSourceType)type {
if (![UIImagePickerController isSourceTypeAvailable:type]) {
return;
}
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = type;
picker.delegate = self;
picker.allowsEditing = YES;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
UIImage *image = info[UIImagePickerControllerEditedImage]
?: info[UIImagePickerControllerOriginalImage];
self.selectedProfileImage = image;
// JPEG compression (RN used image/jpeg)
self.selectedProfileImageData = UIImageJPEGRepresentation(image, 0.85);
// Preview immediately
self.profileImageView.image = image;
[picker dismissViewControllerAnimated:YES completion:^{
[self uploadProfileImage];
}];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void) changeLanguageTapped {
NSLog(@"Change Language Tapped");
}
-(void) switchAccountTapped{
NSLog(@"Switch Account Tapped");
}
- (void)profileImageTapped {
if (!self.profileImageView.image) return;
self.imageViewer = [[UIView alloc] initWithFrame:self.view.bounds];
self.imageViewer.backgroundColor = UIColor.blackColor;
self.imageViewer.alpha = 0;
[self.view addSubview:self.imageViewer];
self.imageScrollView =
[[UIScrollView alloc] initWithFrame:self.imageViewer.bounds];
self.imageScrollView.minimumZoomScale = 1.0;
self.imageScrollView.maximumZoomScale = 4.0;
self.imageScrollView.delegate = self;
[self.imageViewer addSubview:self.imageScrollView];
self.fullscreenImageView =
[[UIImageView alloc] initWithImage:self.profileImageView.image];
self.fullscreenImageView.frame = self.imageScrollView.bounds;
self.fullscreenImageView.contentMode = UIViewContentModeScaleAspectFit;
self.fullscreenImageView.userInteractionEnabled = YES;
[self.imageScrollView addSubview:self.fullscreenImageView];
// Dismiss tap
UITapGestureRecognizer *dismissTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(dismissImageViewer)];
[self.imageViewer addGestureRecognizer:dismissTap];
// Double tap zoom
UITapGestureRecognizer *doubleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(doubleTapZoom:)];
doubleTap.numberOfTapsRequired = 2;
[self.imageScrollView addGestureRecognizer:doubleTap];
[UIView animateWithDuration:0.25 animations:^{
self.imageViewer.alpha = 1;
}];
}
#pragma mark - Zoom
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.fullscreenImageView;
}
- (void)doubleTapZoom:(UITapGestureRecognizer *)gesture {
if (self.imageScrollView.zoomScale > 1.0) {
[self.imageScrollView setZoomScale:1.0 animated:YES];
} else {
CGPoint point = [gesture locationInView:self.fullscreenImageView];
CGRect zoomRect = CGRectMake(point.x - 50, point.y - 50, 100, 100);
[self.imageScrollView zoomToRect:zoomRect animated:YES];
}
}
- (void)dismissImageViewer {
[UIView animateWithDuration:0.25 animations:^{
self.imageViewer.alpha = 0;
} completion:^(BOOL finished) {
[self.imageScrollView removeFromSuperview];
[self.imageViewer removeFromSuperview];
self.imageScrollView = nil;
self.imageViewer = nil;
self.fullscreenImageView = nil;
}];
}
@end
......@@ -447,7 +447,7 @@ static NSString * const kLastOfflineSyncDateKey = @"LAST_OFFLINE_SYNC_DATE";
#pragma mark - Helpers
- (void)onBack {
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
- (void)addTapToCard:(UIView *)card action:(SEL)selector {
......
......@@ -100,6 +100,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)requestAnnouncement:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion;
+ (void)uploadProfileImage:(NSData *)imageData
name:(NSString *)name
completion:(void(^)(BOOL success,
NSDictionary * _Nullable response,
NSError * _Nullable error))completion;
@end
NS_ASSUME_NONNULL_END
......@@ -2320,6 +2320,101 @@
[task resume];
}
+ (void)uploadProfileImage:(NSData *)imageData
name:(NSString *)name
completion:(void(^)(BOOL success,
NSDictionary * _Nullable response,
NSError * _Nullable error))completion {
NSURL *url = [APIConfig urlWithPath:@"/framework/owner/auth/editUser"];
NSString *boundary = @"----ProfileImageBoundary";
NSMutableData *body = [NSMutableData data];
void (^appendString)(NSString *) = ^(NSString *string) {
[body appendData:[string dataUsingEncoding:NSUTF8StringEncoding]];
};
// name
appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
appendString(@"Content-Disposition: form-data; name=\"data[name]\"\r\n\r\n");
appendString(name ?: @"");
appendString(@"\r\n");
// language
appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
appendString(@"Content-Disposition: form-data; name=\"data[language]\"\r\n\r\n");
appendString(@"2");
appendString(@"\r\n");
// os
appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
appendString(@"Content-Disposition: form-data; name=\"data[os]\"\r\n\r\n");
appendString([APIConfig os]);
appendString(@"\r\n");
// information
appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
appendString(@"Content-Disposition: form-data; name=\"data[information]\"\r\n\r\n");
NSDictionary *info = [APIConfig deviceInfo];
NSData *jsonData =
[NSJSONSerialization dataWithJSONObject:info options:0 error:nil];
NSString *infoString =
[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
appendString(infoString ?: @"");
appendString(@"\r\n");
// avatar
if (imageData) {
appendString([NSString stringWithFormat:@"--%@\r\n", boundary]);
appendString(@"Content-Disposition: form-data; name=\"data[avatar]\"; filename=\"profile.jpg\"\r\n");
appendString(@"Content-Type: image/jpeg\r\n\r\n");
[body appendData:imageData];
appendString(@"\r\n");
}
appendString([NSString stringWithFormat:@"--%@--\r\n", boundary]);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]
forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
request.HTTPBody = body;
NSLog(@"🌍 upload profile image: %@", url.absoluteString);
NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil, error);
});
return;
}
NSError *parseError;
NSDictionary *json =
[NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
if (parseError) {
completion(NO, nil, parseError);
} else {
completion(YES, json, nil);
}
});
}];
[task resume];
}
+ (void)requestNotification:(void(^)(BOOL success, NSDictionary * _Nullable response, NSError * _Nullable error))completion {
if ([APIConfig handleOfflineForAPI:@"Notification" completion:completion]) {
......@@ -2395,6 +2490,7 @@
}
// NSURL *url = [APIConfig urlWithPath:@"/announcement"];
//place holder, need to change back eventually
NSURL *url = [NSURL URLWithString:@"https://kitadev.commudesk.com/api/announcement?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImF0aGlyYWh6YWlkaUBjb252ZXAuY29tIiwicGFzc3dvcmQiOiIkMnkkMTAkNU9xYklqMnZ6UHJwckJrcVd5c3I2LmJCc1hVYS9Hd014eUhnL3RhUUhPUkNQcGdmTG8yakciLCJzdWIiOjIxNzAsImlzcyI6Imh0dHBzOi8va2l0YWRldi5jb21tdWRlc2suY29tL2FwaS9vd25lci9hdXRoL3Bhc3N3b3JkbGVzc19sb2dpbiIsImlhdCI6MTc2MDMxNTM1MiwiZXhwIjoyMDc1ODg0ODcyLCJuYmYiOjE3NjAzMTUzNTIsImp0aSI6IktoQmNyQnJEdDVNdDZlWmMifQ.JnxGbZ7hTeoOr6oibIzZMphgyKtTo2Aq9Efx6mrGfFA"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
......
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