chromium/remoting/ios/app/host_collection_view_cell.mm

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <UIKit/UIKit.h>

#import <MaterialComponents/MaterialTypography.h>

#include "base/strings/sys_string_conversions.h"
#include "remoting/base/string_resources.h"
#import "remoting/ios/app/host_collection_view_cell.h"
#import "remoting/ios/app/remoting_theme.h"
#import "remoting/ios/app/view_utils.h"
#import "remoting/ios/domain/host_info.h"
#include "ui/base/l10n/l10n_util.h"

static const CGFloat kLinePadding = 2.f;
static const CGFloat kHostCardIconInset = 10.f;
static const CGFloat kHostCardPadding = 4.f;
static const CGFloat kHostCardIconSize = 45.f;
static const CGFloat kTitleOpacity = 0.87f;
static const CGFloat kCaptionOpacity = 0.54f;

static NSString* const kSuccessExitOfflineReason = @"SUCCESS_EXIT";

// Maps an offline reason enum string to the l10n ID used to retrieve the
// localized message.
static NSDictionary<NSString*, NSNumber*>* const kOfflineReasonL10nId = @{
  @"INITIALIZATION_FAILED" : @(IDS_OFFLINE_REASON_INITIALIZATION_FAILED),
  @"INVALID_HOST_CONFIGURATION" :
      @(IDS_OFFLINE_REASON_INVALID_HOST_CONFIGURATION),
  @"INVALID_HOST_ID" : @(IDS_OFFLINE_REASON_INVALID_HOST_ID),
  @"INVALID_OAUTH_CREDENTIALS" :
      @(IDS_OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS),
  @"INVALID_HOST_DOMAIN" : @(IDS_OFFLINE_REASON_INVALID_HOST_DOMAIN),
  @"LOGIN_SCREEN_NOT_SUPPORTED" :
      @(IDS_OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED),
  @"POLICY_READ_ERROR" : @(IDS_OFFLINE_REASON_POLICY_READ_ERROR),
  @"POLICY_CHANGE_REQUIRES_RESTART" :
      @(IDS_OFFLINE_REASON_POLICY_CHANGE_REQUIRES_RESTART),
  @"USERNAME_MISMATCH" : @(IDS_OFFLINE_REASON_USERNAME_MISMATCH),
  @"X_SERVER_RETRIES_EXCEEDED" :
      @(IDS_OFFLINE_REASON_X_SERVER_RETRIES_EXCEEDED),
  @"SESSION_RETRIES_EXCEEDED" : @(IDS_OFFLINE_REASON_SESSION_RETRIES_EXCEEDED),
  @"HOST_RETRIES_EXCEEDED" : @(IDS_OFFLINE_REASON_HOST_RETRIES_EXCEEDED),
};

@interface HostCollectionViewCell () {
  UIImageView* _imageView;
  UILabel* _statusLabel;
  UILabel* _titleLabel;
  UIView* _labelView;
}
@end

//
// This is the implementation of the info card for a host's status shown in
// the host list. This will also be the selection for which host to connect
// to and other managements actions for a host in this list.
//
@implementation HostCollectionViewCell

@synthesize hostInfo = _hostInfo;

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    self.backgroundColor = [UIColor clearColor];
    [self commonInit];
  }
  return self;
}

- (void)commonInit {
  self.isAccessibilityElement = YES;

  _imageView = [[UIImageView alloc] init];
  _imageView.translatesAutoresizingMaskIntoConstraints = NO;
  _imageView.contentMode = UIViewContentModeCenter;
  _imageView.backgroundColor = RemotingTheme.hostOfflineColor;
  _imageView.layer.cornerRadius = kHostCardIconSize / 2.f;
  _imageView.layer.masksToBounds = YES;
  [self.contentView addSubview:_imageView];

  // Holds both of the labels.
  _labelView = [[UIView alloc] init];
  _labelView.translatesAutoresizingMaskIntoConstraints = NO;
  [self.contentView addSubview:_labelView];

  _titleLabel = [[UILabel alloc] init];
  _titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  UIFont* subheadFont = MDCTypography.subheadFont;
  UIFontDescriptor* subheadFontDescriptor = [subheadFont.fontDescriptor
      fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
  subheadFontDescriptor = subheadFontDescriptor ?: subheadFont.fontDescriptor;
  UIFont* boldSubheadFont = [UIFont fontWithDescriptor:subheadFontDescriptor
                                                  size:subheadFont.pointSize];

  _titleLabel.font = boldSubheadFont;
  _titleLabel.alpha = kTitleOpacity;
  _titleLabel.textColor = RemotingTheme.hostCellTitleColor;
  [_labelView addSubview:_titleLabel];

  _statusLabel = [[UILabel alloc] init];
  _statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
  _statusLabel.font = MDCTypography.captionFont;
  _statusLabel.alpha = kCaptionOpacity;
  _statusLabel.textColor = RemotingTheme.hostCellStatusTextColor;
  [_labelView addSubview:_statusLabel];

  UILayoutGuide* safeAreaLayoutGuide =
      remoting::SafeAreaLayoutGuideForView(self);

  // Constraints
  NSArray* constraints = @[
    // +------------+---------------+
    // | +--------+ |               |
    // | |        | | [Host Name]   |
    // | |  Icon  | | - - - - - - - | <- Center Y
    // | |        | | [Host Status] |
    // | +---^----+ |               |
    // +-----|------+-------^-------+
    //       |              |
    //  Image View     Label View
    [[_imageView leadingAnchor]
        constraintEqualToAnchor:safeAreaLayoutGuide.leadingAnchor
                       constant:kHostCardIconInset],
    [[_imageView centerYAnchor]
        constraintEqualToAnchor:safeAreaLayoutGuide.centerYAnchor],
    [[_imageView widthAnchor] constraintEqualToConstant:kHostCardIconSize],
    [[_imageView heightAnchor] constraintEqualToConstant:kHostCardIconSize],

    [[_labelView leadingAnchor]
        constraintEqualToAnchor:[_imageView trailingAnchor]
                       constant:kHostCardIconInset],
    [[_labelView trailingAnchor]
        constraintEqualToAnchor:safeAreaLayoutGuide.trailingAnchor
                       constant:-kHostCardPadding / 2.f],
    [[_labelView topAnchor]
        constraintEqualToAnchor:safeAreaLayoutGuide.topAnchor],
    [[_labelView bottomAnchor]
        constraintEqualToAnchor:safeAreaLayoutGuide.bottomAnchor],

    // Put titleLabel and statusLabel symmetrically around centerY.
    [[_titleLabel leadingAnchor]
        constraintEqualToAnchor:[_labelView leadingAnchor]],
    [[_titleLabel trailingAnchor]
        constraintEqualToAnchor:[_labelView trailingAnchor]],
    [[_titleLabel bottomAnchor]
        constraintEqualToAnchor:[_labelView centerYAnchor]],

    [[_statusLabel leadingAnchor]
        constraintEqualToAnchor:[_labelView leadingAnchor]],
    [[_statusLabel trailingAnchor]
        constraintEqualToAnchor:[_labelView trailingAnchor]],
    [[_statusLabel topAnchor] constraintEqualToAnchor:[_labelView centerYAnchor]
                                             constant:kLinePadding],
  ];

  [NSLayoutConstraint activateConstraints:constraints];
}

#pragma mark - HostCollectionViewCell Public

- (void)populateContentWithHostInfo:(HostInfo*)hostInfo {
  _hostInfo = hostInfo;

  _titleLabel.text = _hostInfo.hostName;

  _imageView.image = RemotingTheme.desktopIcon;

  if (_hostInfo.isOnline) {
    _imageView.backgroundColor = RemotingTheme.hostOnlineColor;
    _statusLabel.text = l10n_util::GetNSString(IDS_HOST_ONLINE_SUBTITLE);
  } else {
    NSString* statusText =
        hostInfo.updatedTime
            ? l10n_util::GetNSStringF(
                  IDS_LAST_ONLINE_SUBTITLE,
                  base::SysNSStringToUTF16(hostInfo.updatedTime))
            : l10n_util::GetNSString(IDS_HOST_OFFLINE_SUBTITLE);
    NSString* localizedOfflineReason = nil;
    if (hostInfo.offlineReason.length > 0 &&
        ![hostInfo.offlineReason isEqualToString:kSuccessExitOfflineReason]) {
      NSNumber* offlineReasonId = kOfflineReasonL10nId[hostInfo.offlineReason];
      if (offlineReasonId) {
        localizedOfflineReason =
            l10n_util::GetNSString(offlineReasonId.intValue);
      } else {
        localizedOfflineReason = l10n_util::GetNSStringF(
            IDS_OFFLINE_REASON_UNKNOWN,
            base::SysNSStringToUTF16(hostInfo.offlineReason));
      }
    }

    if (localizedOfflineReason) {
      _imageView.backgroundColor = RemotingTheme.hostWarningColor;
      _statusLabel.text = [NSString
          stringWithFormat:@"%@ %@", localizedOfflineReason, statusText];
    } else {
      _imageView.backgroundColor = RemotingTheme.hostOfflineColor;
      _statusLabel.text = statusText;
    }
  }

  self.accessibilityLabel = [NSString
      stringWithFormat:@"%@\n%@", _titleLabel.text, _statusLabel.text];
}

#pragma mark - UICollectionReusableView

- (void)prepareForReuse {
  [super prepareForReuse];
  _hostInfo = nil;
  _statusLabel.text = nil;
  _titleLabel.text = nil;
  self.accessibilityLabel = nil;
}

@end