Commit 15217008 authored by Jose Blaya's avatar Jose Blaya
Browse files

Merge branch '277-piax-network-management-tool-tile' into 'release/3.0.0'

Resolve "PIAX. Network Management Tool Tile"

See merge request ios/vpn-ios!389
parents 4c624961 f20c932d
This diff is collapsed.
......@@ -26,6 +26,7 @@ class DashboardViewController: AutolayoutViewController {
case ipTile
case subscription
case usage
case networkManagementTool
var identifier: String {
switch self {
......@@ -34,6 +35,7 @@ class DashboardViewController: AutolayoutViewController {
case .region: return "RegionTileCell"
case .subscription: return "SubscriptionTileCell"
case .usage: return "UsageTileCell"
case .networkManagementTool: return "NMTTileCell"
}
}
......@@ -44,6 +46,7 @@ class DashboardViewController: AutolayoutViewController {
case .region: return "RegionTileCollectionViewCell"
case .subscription: return "SubscriptionTileCollectionViewCell"
case .usage: return "UsageTileCollectionViewCell"
case .networkManagementTool: return "NetworkManagementToolTileCollectionViewCell"
}
}
}
......@@ -96,6 +99,7 @@ class DashboardViewController: AutolayoutViewController {
nc.addObserver(self, selector: #selector(viewHasRotated), name: .UIDeviceOrientationDidChange, object: nil)
nc.addObserver(self, selector: #selector(updateCurrentStatus), name: .PIAThemeDidChange, object: nil)
nc.addObserver(self, selector: #selector(updateTiles), name: .PIATilesDidChange, object: nil)
nc.addObserver(self, selector: #selector(vpnShouldReconnect), name: .PIASettingsHaveChanged, object: nil)
#if !TARGET_IPHONE_SIMULATOR
let types: UIUserNotificationType = [.alert, .badge, .sound]
......@@ -164,22 +168,32 @@ class DashboardViewController: AutolayoutViewController {
switch self.tileModeStatus { //change the status
case .normal:
navigationItem.leftBarButtonItem = UIBarButtonItem(
image: Asset.itemMenu.image,
style: .plain,
target: self,
action: #selector(openMenu(_:))
)
if let leftBarButton = navigationItem.leftBarButtonItem,
leftBarButton.accessibilityLabel != L10n.Global.cancel {
leftBarButton.image = Asset.itemMenu.image
leftBarButton.action = #selector(openMenu(_:))
} else {
navigationItem.leftBarButtonItem = UIBarButtonItem(
image: Asset.itemMenu.image,
style: .plain,
target: self,
action: #selector(openMenu(_:))
)
}
navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.item
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: Asset.Piax.Global.iconEditTile.image,
style: .plain,
target: self,
action: #selector(updateEditTileStatus(_:))
)
navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.Edit.tile
if navigationItem.rightBarButtonItem == nil {
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: Asset.Piax.Global.iconEditTile.image,
style: .plain,
target: self,
action: #selector(updateEditTileStatus(_:))
)
navigationItem.rightBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.Edit.tile
}
case .edit:
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .stop,
target: self,
......@@ -187,10 +201,11 @@ class DashboardViewController: AutolayoutViewController {
)
navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Global.cancel
navigationItem.rightBarButtonItem = nil
}
}
private func setupCollectionView() {
collectionView.register(UINib(nibName: Cells.ipTile.className,
bundle: nil),
......@@ -207,6 +222,9 @@ class DashboardViewController: AutolayoutViewController {
collectionView.register(UINib(nibName: Cells.usage.className,
bundle: nil),
forCellWithReuseIdentifier: Cells.usage.identifier)
collectionView.register(UINib(nibName: Cells.networkManagementTool.className,
bundle: nil),
forCellWithReuseIdentifier: Cells.networkManagementTool.identifier)
collectionView.backgroundColor = .clear
}
......@@ -324,6 +342,8 @@ class DashboardViewController: AutolayoutViewController {
return
}
menu.delegate = self
} else if let nmt = segue.destination as? TrustedNetworksViewController {
nmt.shouldReconnectAutomatically = true
}
}
......@@ -370,6 +390,27 @@ class DashboardViewController: AutolayoutViewController {
performSelector(onMainThread: #selector(updateCurrentStatusWithUserInfo(_:)), with: notification.userInfo, waitUntilDone: false)
}
@objc private func vpnShouldReconnect() {
if Client.providers.vpnProvider.vpnStatus != .disconnected {
let alert = Macros.alert(
title,
L10n.Settings.Commit.Messages.shouldReconnect
)
// reconnect -> reconnect VPN and close
alert.addActionWithTitle(L10n.Settings.Commit.Buttons.reconnect) {
Client.providers.vpnProvider.reconnect(after: nil, { error in
})
}
// later -> close
alert.addCancelActionWithTitle(L10n.Settings.Commit.Buttons.later) {
}
present(alert, animated: true, completion: nil)
}
}
// MARK: Helpers
@objc private func updateCurrentStatus() {
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "combinedShapeCopy4.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "group38Copy9.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -122,6 +122,8 @@ enum Asset {
static let regionSelected = ImageAsset(name: "region-selected")
static let scrollableMapDark = ImageAsset(name: "scrollableMap-dark")
static let scrollableMapLight = ImageAsset(name: "scrollableMap-light")
static let trustedIcon = ImageAsset(name: "trusted-icon")
static let untrustedIcon = ImageAsset(name: "untrusted-icon")
}
enum LightMap {
static let lightMapAUMelbourne = ImageAsset(name: "Light-Map-AU Melbourne")
......@@ -509,6 +511,8 @@ enum Asset {
Piax.Global.regionSelected,
Piax.Global.scrollableMapDark,
Piax.Global.scrollableMapLight,
Piax.Global.trustedIcon,
Piax.Global.untrustedIcon,
Piax.LightMap.lightMapAUMelbourne,
Piax.LightMap.lightMapAUSydney,
Piax.LightMap.lightMapAustria,
......
......@@ -19,6 +19,7 @@ internal enum StoryboardSegue {
case contentBlockerSegueIdentifier = "ContentBlockerSegueIdentifier"
case customDNSSegueIdentifier = "CustomDNSSegueIdentifier"
case menuSegueIdentifier = "MenuSegueIdentifier"
case networkManagementToolTileSegue = "NetworkManagementToolTileSegue"
case selectRegionAnimatedSegueIdentifier = "SelectRegionAnimatedSegueIdentifier"
case selectRegionSegueIdentifier = "SelectRegionSegueIdentifier"
case settingsSegueIdentifier = "SettingsSegueIdentifier"
......
......@@ -556,6 +556,16 @@ internal enum L10n {
}
internal enum Tiles {
internal enum Nmt {
/// Cellular
internal static let cellular = L10n.tr("Localizable", "tiles.nmt.cellular")
internal enum Accessibility {
/// Trusted network
internal static let trusted = L10n.tr("Localizable", "tiles.nmt.accessibility.trusted")
/// Untrusted network
internal static let untrusted = L10n.tr("Localizable", "tiles.nmt.accessibility.untrusted")
}
}
internal enum Quick {
internal enum Connect {
/// Quick connect
......
//
// NetworkManagementToolTile.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 22/02/2019.
// Copyright © 2019 London Trust Media. All rights reserved.
//
import UIKit
import PIALibrary
import Reachability
class NetworkManagementToolTile: UIView, Tileable {
var view: UIView!
var detailSegueIdentifier: String!
var status: TileStatus = .normal
@IBOutlet private weak var tileTitleLabel: UILabel!
@IBOutlet private weak var networkLabel: UILabel!
@IBOutlet private weak var statusButton: UIButton!
private var hotspotHelper: PIAHotspotHelper!
private var reachability:Reachability!
override init(frame: CGRect) {
super.init(frame: frame)
self.xibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.xibSetup()
self.setupView()
}
deinit {
self.reachability?.stopNotifier()
NotificationCenter.default.removeObserver(self)
}
func hasDetailView() -> Bool {
return true
}
private func setupView() {
self.reachability = Reachability()
try? reachability?.startNotifier()
self.hotspotHelper = PIAHotspotHelper()
self.detailSegueIdentifier = StoryboardSegue.Main.networkManagementToolTileSegue.rawValue
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(viewShouldRestyle), name: .PIAThemeDidChange, object: nil)
nc.addObserver(self, selector: #selector(updateNetwork), name: .PIADaemonsDidUpdateConnectivity, object: nil)
nc.addObserver(self, selector: #selector(updateNetwork), name: .PIASettingsHaveChanged, object: nil)
nc.addObserver(self, selector: #selector(updateNetwork), name: Notification.Name.reachabilityChanged, object: nil)
viewShouldRestyle()
self.tileTitleLabel.text = L10n.Settings.Hotspothelper.title.uppercased()
}
@objc private func viewShouldRestyle() {
tileTitleLabel.style(style: TextStyle.textStyle21)
Theme.current.applyPrincipalBackground(self)
Theme.current.applySettingsCellTitle(networkLabel, appearance: .dark)
updateNetwork()
}
@objc private func updateNetwork() {
if let ssid = hotspotHelper.currentWiFiNetwork() {
networkLabel.text = ssid.uppercased()
if Client.preferences.useWiFiProtection {
if Client.preferences.trustedNetworks.contains(ssid) {
statusButton.setImage(Asset.Piax.Global.trustedIcon.image, for: [])
statusButton.accessibilityLabel = L10n.Tiles.Nmt.Accessibility.trusted
} else {
statusButton.setImage(Asset.Piax.Global.untrustedIcon.image, for: [])
statusButton.accessibilityLabel = L10n.Tiles.Nmt.Accessibility.untrusted
}
} else {
statusButton.setImage(nil, for: [])
statusButton.accessibilityLabel = nil
}
} else {
networkLabel.text = L10n.Tiles.Nmt.cellular
if Client.preferences.trustCellularData {
statusButton.setImage(Asset.Piax.Global.trustedIcon.image, for: [])
statusButton.accessibilityLabel = L10n.Tiles.Nmt.Accessibility.trusted
} else {
statusButton.setImage(Asset.Piax.Global.untrustedIcon.image, for: [])
statusButton.accessibilityLabel = L10n.Tiles.Nmt.Accessibility.untrusted
}
}
}
@IBAction func changeNetworkTrustMode(_ sender: Any) {
if let ssid = hotspotHelper.currentWiFiNetwork() { //wifi
if Client.preferences.useWiFiProtection {
if Client.preferences.trustedNetworks.contains(ssid) {
hotspotHelper.removeTrustedNetwork(ssid)
} else {
hotspotHelper.saveTrustedNetwork(ssid)
}
statusButton.setImage(Asset.Piax.Global.trustedIcon.image, for: [])
}
} else { // cellular
let preferences = Client.preferences.editable()
preferences.trustCellularData = !Client.preferences.trustCellularData
preferences.commit()
}
updateProfile()
updateNetwork()
}
private func updateProfile() {
NotificationCenter.default.post(name: .PIASettingsHaveChanged,
object: self,
userInfo: nil)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="NetworkManagementToolTile" customModule="PIA_VPN" customModuleProvider="target">
<connections>
<outlet property="networkLabel" destination="OCb-o3-R7j" id="Bxx-g7-pvR"/>
<outlet property="statusButton" destination="1ck-xW-hDu" id="rIQ-ka-iQi"/>
<outlet property="tileTitleLabel" destination="3q3-XT-HIJ" id="eiv-0E-yrZ"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="9NY-Bj-4Z8">
<rect key="frame" x="0.0" y="0.0" width="375" height="89"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3q3-XT-HIJ">
<rect key="frame" x="25" y="25" width="271" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OCb-o3-R7j">
<rect key="frame" x="25" y="64" width="271" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1ck-xW-hDu">
<rect key="frame" x="306" y="22.5" width="44" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="2oR-IQ-nEX"/>
<constraint firstAttribute="height" constant="44" id="jNx-SM-We8"/>
</constraints>
<state key="normal" image="trusted-icon"/>
<connections>
<action selector="changeNetworkTrustMode:" destination="-1" eventType="touchUpInside" id="lJi-6x-b5f"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="1ck-xW-hDu" firstAttribute="leading" secondItem="3q3-XT-HIJ" secondAttribute="trailing" constant="10" id="Iya-PP-u6S"/>
<constraint firstItem="Xqf-b8-IaJ" firstAttribute="trailing" secondItem="1ck-xW-hDu" secondAttribute="trailing" constant="25" id="UFu-K5-Fh7"/>
<constraint firstItem="Xqf-b8-IaJ" firstAttribute="bottom" secondItem="OCb-o3-R7j" secondAttribute="bottom" constant="25" id="dNU-Sm-BX5"/>
<constraint firstItem="3q3-XT-HIJ" firstAttribute="top" secondItem="Xqf-b8-IaJ" secondAttribute="top" constant="25" id="f0x-IY-Qet"/>
<constraint firstItem="3q3-XT-HIJ" firstAttribute="leading" secondItem="Xqf-b8-IaJ" secondAttribute="leading" constant="25" id="izx-vU-Id5"/>
<constraint firstItem="OCb-o3-R7j" firstAttribute="leading" secondItem="Xqf-b8-IaJ" secondAttribute="leading" constant="25" id="tlm-Zs-V5X"/>
<constraint firstItem="1ck-xW-hDu" firstAttribute="centerY" secondItem="9NY-Bj-4Z8" secondAttribute="centerY" id="wnN-FM-swk"/>
<constraint firstItem="1ck-xW-hDu" firstAttribute="leading" secondItem="OCb-o3-R7j" secondAttribute="trailing" constant="10" id="z1k-y0-vf7"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="Xqf-b8-IaJ"/>
</view>
</objects>
<resources>
<image name="trusted-icon" width="23" height="28"/>
</resources>
</document>
//
// NetworkManagementToolTileCollectionViewCell.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 22/02/2019.
// Copyright © 2019 London Trust Media. All rights reserved.
//
import UIKit
import PIALibrary
class NetworkManagementToolTileCollectionViewCell: UICollectionViewCell, TileableCell {
var tileType: AvailableTiles = .networkManagementTool
typealias Entity = NetworkManagementToolTile
@IBOutlet private weak var tile: Entity!
@IBOutlet weak var accessoryImageRight: UIImageView!
@IBOutlet weak var accessoryButtonLeft: UIButton!
@IBOutlet weak var tileLeftConstraint: NSLayoutConstraint!
var tileRightConstraint: NSLayoutConstraint!
private var currentTileStatus: TileStatus?
func hasDetailView() -> Bool {
return tile.hasDetailView()
}
func segueIdentifier() -> String? {
return tile.detailSegueIdentifier
}
func setupCellForStatus(_ status: TileStatus) {
Theme.current.applyPrincipalBackground(self)
Theme.current.applyPrincipalBackground(self.contentView)
tile.status = status
let animationDuration = currentTileStatus != nil ? AppConfiguration.Animations.duration : 0
UIView.animate(withDuration: animationDuration, animations: {
switch status {
case .normal:
self.accessoryImageRight.image = Asset.Piax.Tiles.openTileDetails.image
self.tileLeftConstraint.constant = 0
self.accessoryButtonLeft.alpha = 0
case .edit:
self.accessoryImageRight.image = Theme.current.dragDropImage()
self.tileLeftConstraint.constant = self.leftConstraintValue
self.setupVisibilityButton()
self.accessoryButtonLeft.alpha = 1
}
self.layoutIfNeeded()
self.currentTileStatus = status
})
}
func highlightCell() {
accessoryImageRight.alpha = 0
Theme.current.applySecondaryBackground(tile)
Theme.current.applySecondaryBackground(self)
Theme.current.applySecondaryBackground(self.contentView)
}
func unhighlightCell() {
UIView.animate(withDuration: AppConfiguration.Animations.duration, animations: { [weak self] in
self?.accessoryImageRight.alpha = 1
})
Theme.current.applyPrincipalBackground(tile)
Theme.current.applyPrincipalBackground(self)
Theme.current.applyPrincipalBackground(self.contentView)
}
private func setupVisibilityButton() {
if Client.providers.tileProvider.visibleTiles.contains(tileType) {
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .highlighted)
} else {
accessoryButtonLeft.setImage(Theme.current.inactiveEyeImage(), for: .normal)
accessoryButtonLeft.setImage(Theme.current.activeEyeImage(), for: .highlighted)
}
}
@IBAction private func changeTileVisibility() {
var visibleTiles = Client.providers.tileProvider.visibleTiles
if Client.providers.tileProvider.visibleTiles.contains(tileType) {
let tiles = visibleTiles.filter { $0 != tileType }
Client.providers.tileProvider.visibleTiles = tiles
} else {
visibleTiles.append(tileType)
Client.providers.tileProvider.visibleTiles = visibleTiles
}
Macros.postNotification(.PIATilesDidChange)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NMTTileCell" id="6uH-n1-bjH" customClass="NetworkManagementToolTileCollectionViewCell" customModule="PIA_VPN" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="484" height="117"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="484" height="117"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HpK-YS-0fm">
<rect key="frame" x="0.0" y="41.5" width="34" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="34" id="1Bs-88-Kca"/>
<constraint firstAttribute="height" constant="34" id="FNc-om-G3k"/>
</constraints>
<state key="normal" image="eye-active-dark"/>
<connections>
<action selector="changeTileVisibility" destination="6uH-n1-bjH" eventType="touchUpInside" id="8Vf-ux-erl"/>
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="open-tile-details" translatesAutoresizingMaskIntoConstraints="NO" id="8QR-ML-8hB">
<rect key="frame" x="457" y="52.5" width="12" height="12"/>
<constraints>
<constraint firstAttribute="width" secondItem="8QR-ML-8hB" secondAttribute="height" multiplier="1:1" id="2OV-9a-W8V"/>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bnu-kr-urA" customClass="NetworkManagementToolTile" customModule="PIA_VPN" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="457" height="117"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<viewLayoutGuide key="safeArea" id="8Ss-S0-tXJ"/>
</view>
</subviews>
</view>
<constraints>
<constraint firstItem="bnu-kr-urA" firstAttribute="leading" secondItem="6uH-n1-bjH" secondAttribute="leading" id="FMB-4Y-ajt"/>
<constraint firstAttribute="bottom" secondItem="bnu-kr-urA" secondAttribute="bottom" id="GSh-Kr-fGn"/>
<constraint firstItem="bnu-kr-urA" firstAttribute="top" secondItem="6uH-n1-bjH" secondAttribute="top" id="Hva-HJ-K91"/>
<constraint firstAttribute="trailing" secondItem="8QR-ML-8hB" secondAttribute="trailing" constant="15" id="JkO-4s-fmt"/>
<constraint firstItem="8QR-ML-8hB" firstAttribute="centerY" secondItem="6uH-n1-bjH" secondAttribute="centerY" id="LLS-72-FlZ"/>
<constraint firstItem="8QR-ML-8hB" firstAttribute="leading" secondItem="bnu-kr-urA" secondAttribute="trailing" id="Ri8-id-6ZH"/>
<constraint firstItem="HpK-YS-0fm" firstAttribute="leading" secondItem="6uH-n1-bjH" secondAttribute="leading" id="Up4-dS-YPG"/>
<constraint firstItem="HpK-YS-0fm" firstAttribute="centerY" secondItem="6uH-n1-bjH" secondAttribute="centerY" id="o0L-Tx-wcU"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Ilo-HR-dRk"/>
<size key="customSize" width="484" height="117"/>
<connections>
<outlet property="accessoryButtonLeft" destination="HpK-YS-0fm" id="22t-LX-t36"/>
<outlet property="accessoryImageRight" destination="8QR-ML-8hB" id="xks-oK-i92"/>
<outlet property="tile" destination="bnu-kr-urA" id="Ner-zD-Lwm"/>
<outlet property="tileLeftConstraint" destination="FMB-4Y-ajt" id="uAN-Ul-whE"/>
</connections>
<point key="canvasLocation" x="112" y="127.28635682158921"/>
</collectionViewCell>
</objects>
<resources>
<image name="eye-active-dark" width="17" height="11"/>
<image name="open-tile-details" width="12" height="12"/>
</resources>
</document>
......@@ -40,10 +40,12 @@ class RegionTileCollectionViewCell: UICollectionViewCell, TileableCell {
case .normal: