Files
watrbx-game-engine/iOS/RobloxUI/Source/Components/RBBirthdayPicker.m
T
2025-09-18 17:55:52 -04:00

482 lines
20 KiB
Objective-C

//
// RBBirthdayPicker.m
// RobloxMobile
//
// Created by Kyler Mulherin on 7/15/15.
// Copyright (c) 2015 ROBLOX. All rights reserved.
//
#import "RBBirthdayPicker.h"
#import "RobloxTheme.h"
#import "RobloxInfo.h"
#import "UIView+Position.h"
@implementation RBBirthdayPicker
{
NSString* _birthdayString;
UILabel* _birthdayLabel;
UILabel* _birthdayMonth;
UILabel* _birthdayDay;
UILabel* _birthdayYear;
UIButton* _dateButton;
UIView* _dimView;
UIView* _datePickerContainer;
UINavigationBar* _datePickerNavBar;
UIButton* _datePickerAcceptButton;
UIButton* _datePickerCancelButton;
UIPickerView* _dateStringPicker;
CGRect _datePosOffScreen;
CGRect _datePosOnScreen;
bool isValid;
void (^validationBlock)();
NSMutableArray* _dataMonths;
NSMutableArray* _dataDays;
NSMutableArray* _dataYears;
NSMutableArray* _lastDateIndexes;
}
//Constructors and Initializers
-(void) initialize {
isValid = NO;
validationBlock = nil;
//initialize the data for some date pickers
//Days
NSString* placeHolder = @"----";
_dataDays = [NSMutableArray arrayWithCapacity:32];
[_dataDays addObject:placeHolder];
for (NSInteger i = 1; i <= 31; i++)
[_dataDays addObject:[[NSNumber numberWithInteger:i] stringValue]];
//Months
_dataMonths = [NSMutableArray arrayWithArray:@[placeHolder,
NSLocalizedString(@"MonthJanuary", nil),
NSLocalizedString(@"MonthFebruary", nil),
NSLocalizedString(@"MonthMarch", nil),
NSLocalizedString(@"MonthApril", nil),
NSLocalizedString(@"MonthMay", nil),
NSLocalizedString(@"MonthJune", nil),
NSLocalizedString(@"MonthJuly", nil),
NSLocalizedString(@"MonthAugust", nil),
NSLocalizedString(@"MonthSeptember", nil),
NSLocalizedString(@"MonthOctober", nil),
NSLocalizedString(@"MonthNovember", nil),
NSLocalizedString(@"MonthDecember", nil)]];
//Years
NSDateComponents* dateComps = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[NSDate date]];
_dataYears = [NSMutableArray arrayWithCapacity:((dateComps.year - 1900) + 1)];
int currentYear = 1900;
while (currentYear <= dateComps.year)
[_dataYears addObject:[NSNumber numberWithInt:(currentYear++)].stringValue];
[_dataYears addObject:placeHolder];
_lastDateIndexes = [NSMutableArray arrayWithCapacity:3];
_lastDateIndexes[0] = [NSNumber numberWithInt:_dataMonths.count * 250];
_lastDateIndexes[1] = [NSNumber numberWithInt:_dataDays.count * 250];
_lastDateIndexes[2] = [NSNumber numberWithInt:_dataYears.count - 1];
_dateStringPicker = [[UIPickerView alloc] init];
[_dateStringPicker setDelegate:self];
[_dateStringPicker setDataSource:self];
[_dateStringPicker selectRow:[_lastDateIndexes[0] integerValue] inComponent:0 animated:NO];
[_dateStringPicker selectRow:[_lastDateIndexes[1] integerValue] inComponent:1 animated:NO];
[_dateStringPicker selectRow:[_lastDateIndexes[2] integerValue] inComponent:2 animated:NO];
_birthdayString = @"";
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// UI ELEMENTS
[self.layer setBorderWidth:1];
[self.layer setBorderColor:[RobloxTheme colorGray4].CGColor];
[self.layer setCornerRadius:5.0];
[self setClipsToBounds:YES];
_birthdayLabel = [[UILabel alloc] init];
[_birthdayLabel setText:NSLocalizedString(@"BirthdayWord", nil)];
[_birthdayLabel setFont:[RobloxTheme fontBodyBold]];
[_birthdayLabel setTextColor:[RobloxTheme colorGray1]];
_birthdayMonth = [[UILabel alloc] init];
[_birthdayMonth setTextAlignment:NSTextAlignmentCenter];
[_birthdayMonth setText: placeHolder];
[_birthdayMonth.layer setBorderWidth:1];
[_birthdayMonth.layer setBorderColor:[RobloxTheme colorGray4].CGColor];
[RobloxTheme applyToModalLoginTitleLabel:_birthdayMonth];
_birthdayDay = [[UILabel alloc] init];
[_birthdayDay setTextAlignment:NSTextAlignmentCenter];
[_birthdayDay setText: placeHolder];
[_birthdayDay.layer setBorderWidth:1];
[_birthdayDay.layer setBorderColor:[RobloxTheme colorGray4].CGColor];
[RobloxTheme applyToModalLoginTitleLabel:_birthdayDay];
_birthdayYear = [[UILabel alloc] init];
[_birthdayYear setTextAlignment:NSTextAlignmentCenter];
[_birthdayYear setText: placeHolder];
[_birthdayYear.layer setBorderWidth:1];
[_birthdayYear.layer setBorderColor:[RobloxTheme colorGray4].CGColor];
[RobloxTheme applyToModalLoginTitleLabel:_birthdayYear];
_dateButton = [[UIButton alloc] init];
[_dateButton addTarget:self action:@selector(openBirthday:) forControlEvents:UIControlEventTouchUpInside];
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// DATE PICKER
if (NSClassFromString(@"UIVisualEffectView") && NSClassFromString(@"UIBlurEffect"))
{
_dimView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
[_dimView setAlpha:0.0];
}
else
{
_dimView = [[UIView alloc] init];
[_dimView setBackgroundColor:[UIColor colorWithRed:160.0/255.0 green:160.0/255.0 blue:160.0/255.0 alpha:0.85]];
[_dimView setAlpha:0.0];
}
_datePickerContainer = [[UIView alloc] init];
[_datePickerContainer setOpaque:YES];
[_datePickerContainer setBackgroundColor:[UIColor whiteColor]];
[_datePickerContainer setClipsToBounds:YES];
[_datePickerContainer.layer setBorderColor:[[RobloxTheme colorGray4] CGColor]];
[_datePickerContainer.layer setBorderWidth:1];
[_datePickerContainer.layer setCornerRadius:5.0];
[_datePickerContainer.layer setShadowColor:[UIColor blackColor].CGColor];
[_datePickerContainer.layer setShadowOpacity:0.6];
[_datePickerContainer.layer setShadowOffset:CGSizeMake(0, 2)];
[_datePickerContainer.layer setShadowRadius:4];
[_datePickerContainer setClipsToBounds:YES];
_datePickerNavBar = [[UINavigationBar alloc] init];
[_datePickerNavBar pushNavigationItem:[[UINavigationItem alloc] init] animated:NO];
[_datePickerNavBar.topItem setTitle:NSLocalizedString(@"SelectBirthday", nil)];
[RobloxTheme applyToModalPopupNavBar:_datePickerNavBar];
_datePickerAcceptButton = [[UIButton alloc] init];
[_datePickerAcceptButton setTitle:NSLocalizedString(@"AcceptWord", nil) forState:UIControlStateNormal];
[_datePickerAcceptButton addTarget:self action:@selector(closeBirthdayAccept:) forControlEvents:UIControlEventTouchUpInside];
[_datePickerAcceptButton setEnabled:NO];
[RobloxTheme applyToDisabledModalSubmitButton:_datePickerAcceptButton];
_datePickerCancelButton = [[UIButton alloc] init];
[_datePickerCancelButton setTitle:NSLocalizedString(@"CancelWord", nil) forState:UIControlStateNormal];
[_datePickerCancelButton addTarget:self action:@selector(closeBirthday:) forControlEvents:UIControlEventTouchUpInside];
[RobloxTheme applyToModalCancelButton:_datePickerCancelButton];
if ([RobloxInfo thisDeviceIsATablet])
{
//insert the buttons into the navigation bar
[_datePickerNavBar.topItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithCustomView:_datePickerAcceptButton]];
[_datePickerNavBar.topItem setLeftBarButtonItem:[[UIBarButtonItem alloc] initWithCustomView:_datePickerCancelButton] ];
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// ADD THE SUBVIEWS
[self addSubview:_birthdayLabel];
[self addSubview:_birthdayMonth];
[self addSubview:_birthdayDay];
[self addSubview:_birthdayYear];
[self addSubview:_dateButton];
[_datePickerContainer addSubview:_dateStringPicker];
[_datePickerContainer addSubview:_datePickerNavBar];
if (![RobloxInfo thisDeviceIsATablet])
{
[_datePickerContainer addSubview:_datePickerAcceptButton];
[_datePickerContainer addSubview:_datePickerCancelButton];
}
}
-(void) layoutSubviews {
[_dateButton setFrame:CGRectMake(0, 0, self.width, self.height)];
int margin = 12; //self.width * 0.05;
[_birthdayLabel setFrame:CGRectMake(margin, 0, (self.width / 3) - margin, self.height)];
[_birthdayMonth setFrame:CGRectMake(_birthdayLabel.right, -1, self.width * 0.33, self.height+2)];
[_birthdayDay setFrame:CGRectMake(_birthdayMonth.right-1, -1, self.width * 0.15, self.height+2)];
[_birthdayYear setFrame:CGRectMake(_birthdayDay.right-1, -1, (self.width - _birthdayDay.right)+2,self.height+2)];
CGFloat datePickerNavHeight = 40;
CGFloat datePickerHeight = 220;
CGFloat dateButtonWidth = [RobloxInfo thisDeviceIsATablet] ? 80 : 100;
CGFloat containerHeight = datePickerHeight + datePickerNavHeight + ([RobloxInfo thisDeviceIsATablet] ? 0 : datePickerNavHeight + 16);
_datePosOffScreen = CGRectMake(0, self.superview.height + 300, self.superview.width, containerHeight);
_datePosOnScreen = CGRectMake(0, MIN((self.superview.height * 0.5) - (containerHeight * 0.5), self.superview.height - containerHeight), self.superview.width, containerHeight);
[_datePickerContainer setFrame:CGRectMake(0, _datePosOffScreen.origin.y, self.superview.width, containerHeight)];
[_datePickerNavBar setFrame:CGRectMake(0, 6, _datePickerContainer.width, datePickerNavHeight)];
[_dateStringPicker setFrame:CGRectMake(0, datePickerNavHeight, _datePickerContainer.width, datePickerHeight)];
[_dateStringPicker setNeedsLayout];
if ([RobloxInfo thisDeviceIsATablet])
{
[_datePickerNavBar.topItem.rightBarButtonItem.customView setFrame:CGRectMake(0, 0, dateButtonWidth, datePickerNavHeight)];
[_datePickerNavBar.topItem.leftBarButtonItem.customView setFrame:_datePickerNavBar.topItem.rightBarButtonItem.customView.frame];
}
else
{
float containerMargin = 10;
float center = _datePickerContainer.center.x;
[_datePickerAcceptButton setFrame:CGRectMake(center + (containerMargin * 0.5), _dateStringPicker.bottom, dateButtonWidth, datePickerNavHeight)];
[_datePickerCancelButton setFrame:CGRectMake(_datePickerAcceptButton.x - _datePickerAcceptButton.width - containerMargin,
_datePickerAcceptButton.y,
_datePickerAcceptButton.width,
_datePickerAcceptButton.height)];
}
int edgeOffset = 400;
[_dimView setFrame:CGRectMake(-edgeOffset, -edgeOffset, self.superview.width + (edgeOffset * 2), self.superview.height + (edgeOffset * 2))];
}
-(id) init {
self = [super init];
if (self)
[self initialize];
return self;
}
-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self)
[self initialize];
return self;
}
-(id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
[self initialize];
return self;
}
//Accessors
-(NSDate*) getDate {
int selectedDay = [_dateStringPicker selectedRowInComponent:1] % _dataDays.count;
int selectedMonth = [_dateStringPicker selectedRowInComponent:0] % _dataMonths.count;
int selectedYear= [_dataYears[[_dateStringPicker selectedRowInComponent:2]] integerValue];
NSDateComponents* dateConstructor = [[NSDateComponents alloc] init];
[dateConstructor setDay:selectedDay];
[dateConstructor setMonth:selectedMonth];
[dateConstructor setYear:selectedYear];
NSDate* validDate = [[NSCalendar currentCalendar] dateFromComponents:dateConstructor];
if (validDate)
{
//Verify the validity of the created date,
//If the components of the created date match those of the inputted date, it must be correct
//Ex: Feb 31st, 2000 as input yields 2/31/2000
// The created date would result in something like 3/3/2000
// Thus, 2/31/2000 is not a valid date
NSDateComponents* dateComps = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:validDate];
bool matchesDay = (selectedDay == [dateComps day]);
bool matchesMonth = (selectedMonth == [dateComps month]);
bool matchesYear = (selectedYear == [dateComps year]);
if (!matchesDay || !matchesMonth || !matchesYear)
return nil;
}
//check to see if the created date is in the future
NSTimeInterval time = [validDate timeIntervalSinceDate:[NSDate date]];
if (time > 0)
{
//The date is in the future
return nil;
}
return validDate;
}
-(NSString*) getBirthday { return _birthdayString; }
-(BOOL) getWasBornToday {
NSDate* pickedDate = [self getDate];
if (!pickedDate)
return NO;
return [pickedDate isEqualToDate:[NSDate date]];
}
-(BOOL) userUnder13 {
//getDate is guaranteed to return a valid date, as it is only called after a successful validation
NSDate* dateToday = [NSDate date];
NSTimeInterval secondsSince = [dateToday timeIntervalSinceDate:[self getDate]];
float numberOfYears = secondsSince / (60.0f * 60.0f * 24.0f * 365.25f);
return (numberOfYears < 13.0f);
}
-(BOOL) isValid { return isValid; }
//Mutators
-(void) setValidationBlock:(void(^)())callback { validationBlock = callback; }
//Delegate Functions
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
NSDate* currentDate = [self getDate];
[_datePickerAcceptButton setEnabled:(currentDate != nil)];
if (_datePickerAcceptButton.enabled)
[RobloxTheme applyToModalSubmitButton:_datePickerAcceptButton];
else
[RobloxTheme applyToDisabledModalSubmitButton:_datePickerAcceptButton];
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString* title = @"";
switch (component)
{
case (0): title = _dataMonths[row % _dataMonths.count]; break;
case (1): title = _dataDays[row % _dataDays.count]; break;
case (2): title = _dataYears[row % _dataYears.count]; break;
}
return title;
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 3;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSInteger numRows = 0;
switch (component)
{
case 0: numRows = _dataMonths.count * 500; break;
case 1: numRows = _dataDays.count * 500; break;
case 2: numRows = _dataYears.count; break;
}
return numRows;
}
-(CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
CGFloat compWidth = pickerView.width;
switch (component)
{
case (0) : compWidth *= 0.40f; break;
case (1) : compWidth *= 0.20f; break;
case (2) : compWidth *= 0.40f; break;
}
return compWidth;
}
//Button Actions
- (void)openBirthday:(id)sender {
[self.superview addSubview:_dimView];
[self.superview addSubview:_datePickerContainer];
//set the date of the last date that was accepted
[_dateStringPicker selectRow:[_lastDateIndexes[0] integerValue] inComponent:0 animated:NO];
[_dateStringPicker selectRow:[_lastDateIndexes[1] integerValue] inComponent:1 animated:NO];
[_dateStringPicker selectRow:[_lastDateIndexes[2] integerValue] inComponent:2 animated:NO];
[_dateStringPicker becomeFirstResponder];
[UIView animateWithDuration:0.3
animations:^
{
_datePickerContainer.frame = _datePosOnScreen;
_dimView.alpha = 1.0;
}
completion:nil];
}
- (void)closeBirthday:(id)sender {
[_dateStringPicker resignFirstResponder];
[UIView animateWithDuration:0.3
animations:^
{
_datePickerContainer.frame = _datePosOffScreen;
_dimView.alpha = 0.0;
}
completion:^(BOOL finished)
{
[_datePickerContainer removeFromSuperview];
[_dimView removeFromSuperview];
}];
}
- (void)closeBirthdayAccept:(id)sender {
isValid = YES;
[self updateBirthdate];
[self closeBirthday:sender];
[self markAsValid];
}
- (void)updateBirthdate {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
//save the string for the signup string
NSDate* acceptedDate = [self getDate];
_birthdayString = [dateFormatter stringFromDate:acceptedDate];
//update the indexes of the last dates
_lastDateIndexes[0] = [NSNumber numberWithInteger:[_dateStringPicker selectedRowInComponent:0]];
_lastDateIndexes[1] = [NSNumber numberWithInteger:[_dateStringPicker selectedRowInComponent:1]];
_lastDateIndexes[2] = [NSNumber numberWithInteger:[_dateStringPicker selectedRowInComponent:2]];
//update the UI
NSDateComponents* dateComps = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:acceptedDate];
[_birthdayMonth setText:[dateFormatter.monthSymbols objectAtIndex:(dateComps.month - 1)]];
[_birthdayDay setText:[NSString stringWithFormat:@"%li", (long)dateComps.day]];
[_birthdayYear setText:[NSString stringWithFormat:@"%li", (long)dateComps.year]];
//run some code for some other controllers
if (validationBlock != nil)
validationBlock();
}
//Miscellaneous functions
- (BOOL)becomeFirstResponder {
[super becomeFirstResponder];
return [_dateStringPicker becomeFirstResponder];
}
- (BOOL)resignFirstResponder {
[super resignFirstResponder];
if (_datePickerContainer.superview)
[self closeBirthday:nil];
return [_dateStringPicker resignFirstResponder];
}
//Colors and crap
-(void) changeBorderColor:(UIColor*)color {
dispatch_async(dispatch_get_main_queue(), ^
{
self.layer.borderColor = [color CGColor];
_birthdayMonth.layer.borderColor = [color CGColor];
_birthdayDay.layer.borderColor = [color CGColor];
_birthdayYear.layer.borderColor = [color CGColor];
});
}
-(void) markAsValid {
[self changeBorderColor:[RobloxTheme colorGreen1]];
[_birthdayLabel setTextColor:[RobloxTheme colorGray1]];
[_birthdayMonth setTextColor:[RobloxTheme colorGray1]];
[_birthdayDay setTextColor:[RobloxTheme colorGray1]];
[_birthdayYear setTextColor:[RobloxTheme colorGray1]];
}
-(void) markAsNormal {
[self changeBorderColor:[RobloxTheme colorGray4]];
[_birthdayLabel setTextColor:[UIColor colorWithWhite:(0xA9/255.f) alpha:1.0f]];
[_birthdayMonth setTextColor:[UIColor colorWithWhite:(0xA9/255.f) alpha:1.0f]];
[_birthdayDay setTextColor:[UIColor colorWithWhite:(0xA9/255.f) alpha:1.0f]];
[_birthdayYear setTextColor:[UIColor colorWithWhite:(0xA9/255.f) alpha:1.0f]];
}
-(void) markAsInvalid {
[self changeBorderColor:[RobloxTheme colorRed1]];
//[_birthdayLabel setTextColor:[RobloxTheme colorGray1]];
//[_birthdayMonth setTextColor:[RobloxTheme colorGray1]];
//[_birthdayDay setTextColor:[RobloxTheme colorGray1]];
//[_birthdayYear setTextColor:[RobloxTheme colorGray1]];
}
@end