// // ViewController.m // iSH // // Created by Theodore Dubois on 10/17/17. // #import "TerminalViewController.h" #import "AppDelegate.h" #import "TerminalView.h" #import "BarButton.h" #import "ArrowBarButton.h" #import "UserPreferences.h" #import "AboutViewController.h" @interface TerminalViewController () @property UITapGestureRecognizer *tapRecognizer; @property (weak, nonatomic) IBOutlet TerminalView *termView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; @property (weak, nonatomic) IBOutlet UIButton *controlKey; @property (strong, nonatomic) IBOutletCollection(id) NSArray *barButtons; @property (strong, nonatomic) IBOutletCollection(id) NSArray *barControls; @property (weak, nonatomic) IBOutlet UIInputView *barView; @property (weak, nonatomic) IBOutlet UIStackView *bar; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *barTop; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *barBottom; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *barLeading; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *barTrailing; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *barButtonWidth; @property (weak, nonatomic) IBOutlet UIButton *hideKeyboardButton; @end @implementation TerminalViewController - (void)viewDidLoad { [super viewDidLoad]; if (UserPreferences.shared.bootEnabled) { self.terminal = [Terminal terminalWithType:4 number:7]; } else { self.terminal = [Terminal terminalWithType:4 number:1]; } [self.termView becomeFirstResponder]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(keyboardDidSomething:) name:UIKeyboardWillShowNotification object:nil]; [center addObserver:self selector:@selector(keyboardDidSomething:) name:UIKeyboardWillHideNotification object:nil]; [self _updateStyleFromPreferences:NO]; [[UserPreferences shared] addObserver:self forKeyPath:@"theme" options:NSKeyValueObservingOptionNew context:nil]; if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) { [self.bar removeArrangedSubview:self.hideKeyboardButton]; [self.hideKeyboardButton removeFromSuperview]; } if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) { self.barView.frame = CGRectMake(0, 0, 100, 48); } else { self.barView.frame = CGRectMake(0, 0, 100, 55); } } - (void)dealloc { @try { [[UserPreferences shared] removeObserver:self forKeyPath:@"theme"]; } @catch (NSException * __unused exception) {} } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == [UserPreferences shared]) { [self _updateStyleFromPreferences:YES]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)_updateStyleFromPreferences:(BOOL)animated { NSTimeInterval duration = animated ? 0.1 : 0; [UIView animateWithDuration:duration animations:^{ self.view.backgroundColor = UserPreferences.shared.theme.backgroundColor; UIKeyboardAppearance keyAppearance = UserPreferences.shared.theme.keyboardAppearance; self.termView.keyboardAppearance = keyAppearance; for (BarButton *button in self.barButtons) { button.keyAppearance = keyAppearance; } UIColor *tintColor = keyAppearance == UIKeyboardAppearanceLight ? UIColor.blackColor : UIColor.whiteColor; for (UIControl *control in self.barControls) { control.tintColor = tintColor; } }]; } - (UIStatusBarStyle)preferredStatusBarStyle { return UserPreferences.shared.theme.statusBarStyle; } - (BOOL)prefersStatusBarHidden { BOOL isIPhoneX = UIApplication.sharedApplication.delegate.window.safeAreaInsets.top > 20; return !isIPhoneX; } - (void)keyboardDidSomething:(NSNotification *)notification { BOOL initialLayout = self.termView.needsUpdateConstraints; CGFloat pad = 0; if ([notification.name isEqualToString:UIKeyboardWillShowNotification]) { NSValue *frame = notification.userInfo[UIKeyboardFrameEndUserInfoKey]; pad = frame.CGRectValue.size.height; } if (pad == 0) { pad = self.view.safeAreaInsets.bottom; } self.bottomConstraint.constant = -pad; [self.view setNeedsUpdateConstraints]; if (!initialLayout) { // if initial layout hasn't happened yet, the terminal view is going to be at a really weird place, so animating it is going to look really bad NSNumber *interval = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]; NSNumber *curve = notification.userInfo[UIKeyboardAnimationCurveUserInfoKey]; [UIView animateWithDuration:interval.doubleValue delay:0 options:curve.integerValue << 16 animations:^{ [self.view layoutIfNeeded]; } completion:nil]; } } - (void)ishExited:(NSNotification *)notification { [self performSelectorOnMainThread:@selector(displayExitThing) withObject:nil waitUntilDone:YES]; } - (void)displayExitThing { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"attempted to kill init" message:nil preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"exit" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { id delegate = [UIApplication sharedApplication].delegate; [delegate exitApp]; }]]; if ([UserPreferences.shared hasChangedLaunchCommand]) [alert addAction:[UIAlertAction actionWithTitle:@"i typed the init command wrong, let me fix it" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } #pragma mark Bar - (IBAction)showAbout:(id)sender { UINavigationController *navigationController = [[UIStoryboard storyboardWithName:@"About" bundle:nil] instantiateInitialViewController]; if ([sender isKindOfClass:[UIGestureRecognizer class]]) { UIGestureRecognizer *recognizer = sender; if (recognizer.state == UIGestureRecognizerStateBegan) { AboutViewController *aboutViewController = (AboutViewController *) navigationController.topViewController; aboutViewController.includeDebugPanel = YES; } else { return; } } [self presentViewController:navigationController animated:YES completion:nil]; } - (void)resizeBar { CGSize screen = UIScreen.mainScreen.bounds.size; CGSize bar = self.barView.bounds.size; // set sizing parameters on bar // numbers stolen from iVim and modified somewhat if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) { // phone [self setBarHorizontalPadding:6 verticalPadding:6 buttonWidth:32]; } else if (bar.width == screen.width || bar.width == screen.height) { // full-screen ipad [self setBarHorizontalPadding:15 verticalPadding:8 buttonWidth:43]; } else if (bar.width <= 320) { // slide over [self setBarHorizontalPadding:8 verticalPadding:8 buttonWidth:26]; } else { // split view [self setBarHorizontalPadding:10 verticalPadding:8 buttonWidth:36]; } [UIView performWithoutAnimation:^{ [self.barView layoutIfNeeded]; }]; } - (void)setBarHorizontalPadding:(CGFloat)horizontal verticalPadding:(CGFloat)vertical buttonWidth:(CGFloat)buttonWidth { self.barLeading.constant = self.barTrailing.constant = horizontal; self.barTop.constant = self.barBottom.constant = vertical; self.barButtonWidth.constant = buttonWidth; } - (IBAction)pressEscape:(id)sender { [self pressKey:@"\x1b"]; } - (IBAction)pressTab:(id)sender { [self pressKey:@"\t"]; } - (void)pressKey:(NSString *)key { [self.termView insertText:key]; } - (IBAction)pressControl:(id)sender { self.controlKey.selected = !self.controlKey.selected; } - (IBAction)pressArrow:(ArrowBarButton *)sender { switch (sender.direction) { case ArrowUp: [self pressKey:[self.terminal arrow:'A']]; break; case ArrowDown: [self pressKey:[self.terminal arrow:'B']]; break; case ArrowLeft: [self pressKey:[self.terminal arrow:'D']]; break; case ArrowRight: [self pressKey:[self.terminal arrow:'C']]; break; case ArrowNone: break; } } - (void)switchTerminal:(UIKeyCommand *)sender { unsigned i = (unsigned) sender.input.integerValue; self.terminal = [Terminal terminalWithType:4 number:i]; } - (NSArray *)keyCommands { static NSMutableArray *commands = nil; if (commands == nil) { commands = [NSMutableArray new]; for (unsigned i = 1; i <= 7; i++) { [commands addObject: [UIKeyCommand keyCommandWithInput:[NSString stringWithFormat:@"%d", i] modifierFlags:UIKeyModifierCommand|UIKeyModifierAlternate|UIKeyModifierShift action:@selector(switchTerminal:)]]; } } return commands; } - (void)setTerminal:(Terminal *)terminal { _terminal = terminal; self.termView.terminal = self.terminal; } @end @interface BarView : UIInputView @property (weak) IBOutlet TerminalViewController *terminalViewController; @end @implementation BarView - (void)layoutSubviews { [self.terminalViewController resizeBar]; } @end