summaryrefslogtreecommitdiff
path: root/ios/iosremote/iosremote/Classes/ServerListViewController.m
diff options
context:
space:
mode:
Diffstat (limited to 'ios/iosremote/iosremote/Classes/ServerListViewController.m')
-rw-r--r--ios/iosremote/iosremote/Classes/ServerListViewController.m542
1 files changed, 542 insertions, 0 deletions
diff --git a/ios/iosremote/iosremote/Classes/ServerListViewController.m b/ios/iosremote/iosremote/Classes/ServerListViewController.m
new file mode 100644
index 000000000000..e9e65836a257
--- /dev/null
+++ b/ios/iosremote/iosremote/Classes/ServerListViewController.m
@@ -0,0 +1,542 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+//
+// This file is part of the LibreOffice project.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#import "ServerListViewController.h"
+#import "CommunicationManager.h"
+#import "NewServerViewController.h"
+#import "Server.h"
+#import "Client.h"
+#import "PopoverView.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+@interface ServerListViewController () <NSNetServiceBrowserDelegate, NSNetServiceDelegate, PopoverViewDelegate>
+
+@property (nonatomic, strong) CommunicationManager *comManager;
+@property (nonatomic, weak) NSNotificationCenter* center;
+@property (nonatomic, strong) id slideShowPreviewStartObserver;
+@property (nonatomic, strong) id pinValidationObserver;
+@property (atomic, strong) NSIndexPath *lastSpinningCellIndex;
+@property (nonatomic, strong) NSNetServiceBrowser *serviceBrowser;
+
+@property (nonatomic, strong) NSTimer *searchLabelTimer;
+@property (nonatomic, strong) NSTimer *searchTimeoutTimer;
+@property (nonatomic, strong) NSString *searchStateText;
+@property (nonatomic) UITableViewCellSelectionStyle style;
+
+@end
+
+@implementation ServerListViewController
+
+@synthesize style = _style;
+@synthesize searchStateText = _searchStateText;
+@synthesize searchLabelTimer = _searchLabelTimer;
+@synthesize center = _center;
+@synthesize comManager = _comManager;
+@synthesize lastSpinningCellIndex = _lastSpinningCellIndex;
+@synthesize slideShowPreviewStartObserver = _slideShowPreviewStartObserver;
+@synthesize pinValidationObserver = _pinValidationObserver;
+@synthesize serviceBrowser = _serviceBrowser;
+
+- (IBAction)onClickMenuButton:(id)sender {
+ [PopoverView showPopoverAtPoint:CGPointMake([[sender view] center].x, [[sender view] center].y + [[sender view] frame].size.height * 0.5) inView:[sender view].superview withStringArray:
+ [NSArray arrayWithObjects:NSLocalizedString(@"Connection Help", @"Popover option"), NSLocalizedString(@"About Impress Remote", @"Popover option"), nil]
+ delegate:self];
+}
+
+- (void)popoverView:(PopoverView *)popoverView didSelectItemAtIndex:(NSInteger)index
+{
+ [popoverView dismiss];
+ switch (index) {
+ case 0:
+ [self performSegueWithIdentifier:@"howtoSegue" sender:self];
+ break;
+ case 1:
+ [self performSegueWithIdentifier:@"aboutSegue" sender:self];
+ break;
+ default:
+ break;
+ }
+}
+
+#pragma mark - helper
+- (void) startSearching
+{
+ [self.serviceBrowser stop];
+ [self.comManager.autoDiscoveryServers removeAllObjects];
+ [self.serviceBrowser searchForServicesOfType:@"_impressremote._tcp" inDomain:@"local"];
+ [self.serviceBrowser scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+}
+
+- (void) setSearchStateText:(NSString *)searchStateText
+{
+ _searchStateText = searchStateText;
+ // This doesn't work well on iOS7, might be a bug. The text will get duplicated, it seems that the older section header view was not removed
+ [self.serverTable reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
+ // [self.serverTable reloadData];
+}
+
+- (void) setStyle:(UITableViewCellSelectionStyle)style
+{
+ _style = style;
+ [self.serverTable reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
+}
+
+#pragma mark - netservice resolve delegate
+-(void) netServiceDidResolveAddress:(NSNetService *)sender
+{
+ if ([sender.addresses count]){
+ NSData * address = [[sender addresses] objectAtIndex: 0];
+ struct sockaddr_in *socketAddress = (struct sockaddr_in *) [address bytes];
+ NSString * ipString = [NSString stringWithFormat: @"%s",inet_ntoa(socketAddress->sin_addr)];
+ int port = socketAddress->sin_port;
+ NSLog(@"Resolved at %@:%u", ipString, port);
+
+ [self.comManager connectToServer:[[Server alloc] initWithProtocol:NETWORK atAddress:ipString ofName:sender.name]];
+ }
+}
+
+-(void) netService:(NSNetService *)sender didNotResolve:(NSDictionary *)errorDict
+{
+ NSLog(@"Failed to resolve");
+ [self.comManager.autoDiscoveryServers removeAllObjects];
+ [self startSearching];
+
+ UIAlertView *message = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Failed to reach your computer", nil)
+ message:NSLocalizedString(@"Please consider restart LibreOffice Impress on your computer.", nil)
+ delegate:self
+ cancelButtonTitle:NSLocalizedString(@"OK", nil)
+ otherButtonTitles:nil];
+ [self disableSpinner];
+ [message show];
+}
+
+#pragma mark - bonjour service discovery
+
+-(void) handleSearchTimeout
+{
+ [self.serviceBrowser stop];
+}
+
+-(void) updateSearchLabel
+{
+ static short count = 1;
+ NSString * searchText = NSLocalizedString(@"Searching", nil);
+ for (uint i = 0; i<=count; ++i) {
+ searchText = [searchText stringByAppendingString:@"."];
+ }
+ NSLog(@"Updating count = %u SearchText = %@", count, searchText);
+ self.searchStateText = searchText;
+ count++;
+ count = count % 3;
+}
+
+-(void) netServiceBrowserWillSearch:(NSNetServiceBrowser *)aNetServiceBrowser
+{
+ NSLog(@"Will search");
+ self.comManager.searchState = SEARCHING;
+ self.searchStateText = NSLocalizedString(@"Searching", nil);
+ [self.searchLabelTimer invalidate];
+ [self.searchTimeoutTimer invalidate];
+ self.searchLabelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
+ target:self
+ selector:@selector(updateSearchLabel)
+ userInfo:nil
+ repeats:YES];
+
+ self.searchTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
+ target:self
+ selector:@selector(handleSearchTimeout)
+ userInfo:nil
+ repeats:NO];
+
+ [[NSRunLoop currentRunLoop] addTimer:self.searchLabelTimer forMode:NSRunLoopCommonModes];
+ self.style = UITableViewCellSelectionStyleNone;
+}
+
+-(void) netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)aNetServiceBrowser
+{
+ NSLog(@"End search");
+ self.comManager.searchState = WAITING;
+ [self.searchLabelTimer invalidate];
+ [self.searchTimeoutTimer invalidate];
+ self.searchStateText = NSLocalizedString(@"Click to refresh", nil);
+ self.style = UITableViewCellSelectionStyleBlue;
+}
+
+- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict
+{
+ NSLog(@"search error");
+}
+
+- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser
+ didFindService:(NSNetService *)aNetService
+ moreComing:(BOOL)moreComing
+{
+ // [self.comManager.autoDiscoveryServers removeObject:aNetService];
+ [self.comManager.autoDiscoveryServers addObject:aNetService];
+
+ NSLog(@"Got service %p with hostname %@\n", aNetService,
+ [aNetService name]);
+
+ [aNetService setDelegate:self];
+
+ if(!moreComing)
+ {
+ [self.searchTimeoutTimer invalidate];
+ [self.searchLabelTimer invalidate];
+ self.comManager.searchState = WAITING;
+ [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
+ }
+}
+
+// Sent when a service disappears
+- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
+ didRemoveService:(NSNetService *)aNetService
+ moreComing:(BOOL)moreComing
+{
+ [self.comManager.autoDiscoveryServers removeObject:aNetService];
+
+ if(!moreComing)
+ {
+ // [self.tableView reloadData];
+ [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
+ [self startSearching];
+ }
+}
+
+# pragma mark - System defaults
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
+{
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ return YES;
+ else {
+ return toInterfaceOrientation == UIInterfaceOrientationMaskPortrait;
+ }
+}
+
+
+- (id)initWithStyle:(UITableViewStyle)style
+{
+ self = [super initWithStyle:style];
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Uncomment the following line to preserve selection between presentations.
+ // self.clearsSelectionOnViewWillAppear = NO;
+
+ // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
+ // self.navigationItem.rightBarButtonItem = self.editButtonItem;
+
+
+ self.center = [NSNotificationCenter defaultCenter];
+ self.comManager = [CommunicationManager sharedComManager];
+ self.serverTable.dataSource = self;
+ self.serverTable.delegate = self;
+ _style = UITableViewCellSelectionStyleNone;
+
+ [self setTitle:NSLocalizedString(@"Impress Remote", @"App name displayed on navbar")];
+}
+
+- (void) viewDidAppear:(BOOL)animated
+{
+ NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
+ self.pinValidationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:STATUS_PAIRING_PINVALIDATION
+ object:nil
+ queue:mainQueue
+ usingBlock:^(NSNotification *note) {
+ [self.comManager.client stopConnectionTimeoutTimer];
+ self.comManager.state = CONNECTED;
+ [self disableSpinner];
+ [self performSegueWithIdentifier:@"pinValidation" sender:self ];
+ }];
+ self.slideShowPreviewStartObserver = [[NSNotificationCenter defaultCenter] addObserverForName:STATUS_PAIRING_PAIRED
+ object:nil
+ queue:mainQueue
+ usingBlock:^(NSNotification *note) {
+ [self.comManager.client stopConnectionTimeoutTimer];
+ self.comManager.state = CONNECTED;
+ [self disableSpinner];
+ [self performSegueWithIdentifier:@"SlideShowPreview" sender:self ];
+ }];
+ NSLog(@"Clear auto discovered servers");
+ [self.comManager.autoDiscoveryServers removeAllObjects];
+ // [self.serverTable reloadData];
+ [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
+ self.serviceBrowser = [[NSNetServiceBrowser alloc] init];
+ [self.serviceBrowser setDelegate:self];
+ [self startSearching];
+
+ if (![[NSUserDefaults standardUserDefaults] boolForKey:@"HOWTO_GUIDE_SEEN"])
+ {
+ UIAlertView * av = [[UIAlertView alloc] initWithTitle:@"How-To guide" message:NSLocalizedString(@"Take a tour of iOS Impress Remote?", nil) delegate:self cancelButtonTitle:NSLocalizedString(@"No, thanks", nil) otherButtonTitles:NSLocalizedString(@"Yes", nil), nil];
+
+ [av show];
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"HOWTO_GUIDE_SEEN"];
+ }
+
+ [super viewDidAppear:animated];
+}
+
+- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
+ if (buttonIndex == 0){
+ [alertView dismissWithClickedButtonIndex:0 animated:YES];
+ }else if (buttonIndex == 1){
+ [alertView dismissWithClickedButtonIndex:0 animated:YES];
+ [self performSegueWithIdentifier:@"howtoSegue" sender:self];
+ }
+}
+
+- (void) viewWillDisappear:(BOOL)animated
+{
+ [self disableSpinner];
+ [self.serviceBrowser stop];
+ [self.serviceBrowser removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ [super viewWillDisappear:animated];
+}
+
+- (void) viewDidDisappear:(BOOL)animated
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self.slideShowPreviewStartObserver];
+ [[NSNotificationCenter defaultCenter] removeObserver:self.pinValidationObserver];
+ [super viewDidDisappear:animated];
+}
+
+
+-(void)viewWillAppear:(BOOL)animated
+{
+ [self.comManager setDelegate:self];
+ [self.serverTable reloadData];
+ [super viewWillAppear:animated];
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+#pragma mark - Table view delegate
+
+- (void)disableSpinner
+{
+ @synchronized(self.lastSpinningCellIndex){
+ if ([[self.tableView cellForRowAtIndexPath:self.lastSpinningCellIndex] respondsToSelector:@selector(accessoryView)]) {
+ if (self.tableView && [self.tableView cellForRowAtIndexPath:self.lastSpinningCellIndex]) {
+ [[self.tableView cellForRowAtIndexPath:self.lastSpinningCellIndex] setAccessoryView:nil];
+ }
+ }
+ }
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ [tableView deselectRowAtIndexPath:indexPath animated:YES];
+
+ if ([self.comManager.autoDiscoveryServers count] == 0 && indexPath.section == 0){
+ // No discovered server and not searching => in a "click to refresh" state, so we restart searching process
+ if (self.comManager.searchState == WAITING){
+ [self startSearching];
+ }
+ // Return when browser is still searching...
+ return;
+ }
+
+ // Return when nothing should be done
+ if (self.comManager.state == CONNECTING)
+ return;
+
+ UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
+
+ // Setting up the spinner to the right cell
+ self.lastSpinningCellIndex = indexPath;
+ UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
+ [activityView startAnimating];
+ [cell setAccessoryView:activityView];
+
+ if (indexPath.section == 1){
+ NSLog(@"Connecting to %@:%@", [[self.comManager.servers objectAtIndex:indexPath.row] serverName], [[self.comManager.servers objectAtIndex:indexPath.row] serverAddress]);
+ [self.comManager connectToServer:[self.comManager.servers objectAtIndex:indexPath.row]];
+ } else if (indexPath.section == 0){
+ NSLog(@"Connecting to %@", [[self.comManager.autoDiscoveryServers objectAtIndex:indexPath.row] name]);
+ [[self.comManager.autoDiscoveryServers objectAtIndex:indexPath.row] resolveWithTimeout:0.0];
+ }
+}
+
+- (void)viewDidUnload {
+ [self setServerTable:nil];
+ [self setMenuButton:nil];
+ [super viewDidUnload];
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
+
+ NSString *sectionName = nil;
+
+ switch (section) {
+ case 0:
+ sectionName = [NSString stringWithFormat:NSLocalizedString(@"Detected Connections", @"Server list section title")];
+ break;
+ case 1:
+ sectionName = [NSString stringWithFormat:NSLocalizedString(@"Custom Connections", @"Server list section title")];
+ break;
+ }
+ UILabel *sectionHeader;
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
+ sectionHeader = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 310, 40)];
+ } else {
+ sectionHeader = [[UILabel alloc] initWithFrame:CGRectMake(25, 10, 295, 40)];
+ }
+ sectionHeader.backgroundColor = [UIColor clearColor];
+ sectionHeader.font = kAppTextFont;
+ sectionHeader.textColor = [UIColor darkTextColor];
+ sectionHeader.text = sectionName;
+ UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, [self tableView:tableView heightForHeaderInSection:section])];
+ [view addSubview:sectionHeader];
+
+ if (self.comManager.searchState == SEARCHING && section == 0) {
+ UIActivityIndicatorView * aiv = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
+ aiv.center = CGPointMake([sectionHeader.text sizeWithFont: sectionHeader.font].width + 2 * aiv.frame.size.width, sectionHeader.center.y);
+ [aiv startAnimating];
+ [aiv setTag:4];
+ [view addSubview:aiv];
+ }
+ return view;
+}
+
+- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
+ if ([self.comManager.servers count] == 0 && section == 1) {
+ UILabel *sectionFooter = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, tableView.frame.size.width - 50, 100)];
+ [sectionFooter setLineBreakMode:NSLineBreakByWordWrapping];
+ [sectionFooter setNumberOfLines:5];
+ sectionFooter.backgroundColor = [UIColor clearColor];
+ sectionFooter.font = kAppSmallTextFont;
+ sectionFooter.textColor = kTextTintColor;
+ sectionFooter.text = NSLocalizedString(@"Customize server config instruction", @"Displayed when no customized server is available");
+
+ UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, [self tableView:tableView heightForFooterInSection:section])];
+ [view addSubview:sectionFooter];
+ return view;
+ }
+ return nil;
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
+{
+ return 50.0;
+}
+
+//- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
+//{
+//// return 50.0;
+//}
+
+-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 2;
+}
+
+-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ switch (section) {
+ case 0:
+ return [self.comManager.autoDiscoveryServers count] == 0 ? 1 : [self.comManager.autoDiscoveryServers count];
+ break;
+ case 1:
+ return [self.comManager.servers count];
+ default:
+ return -1;
+ break;
+ }
+}
+
+-(void)revealHelpInfo:(UIView *)sender
+{
+ if (sender) {
+ if ([[CommunicationManager fetchSSIDInfo] valueForKey:@"SSID"])
+ [PopoverView showPopoverAtPoint:CGPointMake(sender.center.x,
+ sender.frame.origin.y + sender.frame.size.height/2)
+ inView:[sender superview]
+ withText:[NSString
+ stringWithFormat:NSLocalizedString(@"* Launch Impress on your computer\n* Connect both devices to the same WiFi network\n* P.S. Your iOS device is connected to \"%@\" now", nil), [[CommunicationManager fetchSSIDInfo] valueForKey:@"SSID"]]
+ delegate:nil];
+ else {
+ [PopoverView showPopoverAtPoint:CGPointMake(sender.center.x,
+ sender.frame.origin.y + sender.frame.size.height/2)
+ inView:[sender superview]
+ withText:[NSString
+ stringWithFormat:NSLocalizedString(@"* You don't have a WiFi connection now.\n* Connect your iOS device and your computer to the same network\n* Refresh\n* hint: you may create a personal hotspot on your computer/iPhone", nil), [[CommunicationManager fetchSSIDInfo] valueForKey:@"SSID"]]
+ delegate:nil];
+ }
+ }
+}
+
+-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ static NSString *cellIdentifier = @"server_item_cell";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
+
+ if (indexPath.section == 0){
+ if ([self.comManager.autoDiscoveryServers count] == 0){
+ cell.textLabel.text = NSLocalizedString(self.searchStateText, nil);
+ cell.textLabel.lineBreakMode = UILineBreakModeClip;
+ cell.selectionStyle = self.style;
+
+ UIButton *infoBtn = [UIButton buttonWithType:UIButtonTypeInfoDark];
+ infoBtn.frame = CGRectMake(3, 8, 30, 30);
+ [infoBtn addTarget:self action:@selector(revealHelpInfo:) forControlEvents:UIControlEventTouchUpInside];
+
+ cell.accessoryView = infoBtn;
+ } else {
+ id s = [self.comManager.autoDiscoveryServers objectAtIndex:indexPath.row];
+ [cell.textLabel setText:[s name]];
+ [cell.detailTextLabel setText:@""];
+ cell.accessoryView = nil;
+ }
+ }
+ else {
+ Server *s = [self.comManager.servers objectAtIndex:indexPath.row];
+
+ [cell.textLabel setText:[s serverName]];
+ [cell.detailTextLabel setText:[s serverAddress]];
+ }
+ return cell;
+}
+
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.section == 1)
+ return YES;
+ else
+ return NO;
+}
+
+- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
+ if (indexPath.section == 1) {
+ return UITableViewCellEditingStyleDelete;
+ } else {
+ return UITableViewCellEditingStyleNone;
+ }
+}
+
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
+
+ if(editingStyle == UITableViewCellEditingStyleDelete){
+ [self.comManager removeServerAtIndex:indexPath.row];
+ [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
+ }
+}
+
+@end