Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,9 @@
#import "MWMViewController.h"
#include <CoreApi/Framework.h>
@interface MWMAutoupdateController : MWMViewController
+ (instancetype)instanceWithPurpose:(Framework::DoAfterUpdate)todo;
@end

View file

@ -0,0 +1,309 @@
#import "MWMAutoupdateController.h"
#import "MWMCircularProgress.h"
#import "MWMStorage+UI.h"
#import "SwiftBridge.h"
#include "platform/downloader_defines.hpp"
#include <string>
#include <unordered_set>
namespace
{
NSString *RootId() { return @(GetFramework().GetStorage().GetRootId().c_str()); }
enum class State
{
Downloading,
Waiting
};
} // namespace
using namespace storage;
@interface MWMAutoupdateView : UIView
@property(weak, nonatomic) IBOutlet UIImageView * image;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * imageMinHeight;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * imageHeight;
@property(weak, nonatomic) IBOutlet UILabel * title;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * titleTopOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * titleImageOffset;
@property(weak, nonatomic) IBOutlet UILabel * text;
@property(weak, nonatomic) IBOutlet UIButton * primaryButton;
@property(weak, nonatomic) IBOutlet UIButton * secondaryButton;
@property(weak, nonatomic) IBOutlet UIView * spinnerView;
@property(weak, nonatomic) IBOutlet UILabel * progressLabel;
@property(weak, nonatomic) IBOutlet UILabel * legendLabel;
@property(weak, nonatomic) id<MWMCircularProgressProtocol> delegate;
@property(nonatomic) MWMCircularProgress * spinner;
@property(copy, nonatomic) NSString * updateSize;
@property(nonatomic) State state;
- (void)startSpinner;
- (void)stopSpinner;
- (void)updateForSize:(CGSize)size;
@end
@implementation MWMAutoupdateView
- (void)setFrame:(CGRect)frame
{
[self updateForSize:frame.size];
super.frame = frame;
}
- (void)updateForSize:(CGSize)size
{
BOOL const hideImage = (self.imageHeight.multiplier * size.height <= self.imageMinHeight.constant);
self.titleImageOffset.priority = hideImage ? UILayoutPriorityDefaultLow : UILayoutPriorityDefaultHigh;
self.image.hidden = hideImage;
[self layoutIfNeeded];
}
-(void)setUpdateSize:(NSString *)updateSize
{
_updateSize = updateSize;
self.primaryButton.localizedText =
[NSString stringWithFormat:L(@"whats_new_auto_update_button_size"), self.updateSize];
}
- (void)stateDownloading
{
self.state = State::Downloading;
self.primaryButton.hidden = YES;
[self startSpinner];
self.secondaryButton.localizedText = L(@"downloader_hide_screen");
}
- (void)stateWaiting
{
self.state = State::Waiting;
[self stopSpinner];
self.primaryButton.hidden = NO;
self.secondaryButton.localizedText = L(@"whats_new_auto_update_button_later");
}
- (void)startSpinner
{
self.primaryButton.hidden = YES;
self.spinnerView.hidden = NO;
self.progressLabel.hidden = NO;
self.legendLabel.hidden = NO;
self.spinner = [MWMCircularProgress downloaderProgressForParentView:self.spinnerView];
self.spinner.delegate = self.delegate;
self.spinner.state = MWMCircularProgressStateSpinner;
}
- (void)stopSpinner
{
self.primaryButton.hidden = NO;
self.spinnerView.hidden = YES;
self.progressLabel.hidden = YES;
self.legendLabel.hidden = YES;
self.spinner = nil;
}
- (void)setStatusForNodeName:(NSString *)nodeName rootAttributes:(NodeAttrs const &)nodeAttrs
{
auto const progress = nodeAttrs.m_downloadingProgress;
if (progress.m_bytesTotal > 0)
{
CGFloat const prog = kMaxProgress * static_cast<CGFloat>(progress.m_bytesDownloaded) / progress.m_bytesTotal;
self.spinner.progress = prog;
NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterPercentStyle];
[numberFormatter setMaximumFractionDigits:0];
[numberFormatter setMultiplier:@100];
NSString * percent = [numberFormatter stringFromNumber:@(prog)];
NSString * downloadedSize = formattedSize(progress.m_bytesDownloaded);
NSString * totalSize = formattedSize(progress.m_bytesTotal);
self.progressLabel.text = [NSString stringWithFormat:L(@"downloader_percent"), percent, downloadedSize, totalSize];
}
else
{
self.progressLabel.text = @"";
}
BOOL const isApplying = nodeAttrs.m_status == storage::NodeStatus::Applying;
NSString * format = L(isApplying ? @"downloader_applying" : @"downloader_process");
self.legendLabel.text = [NSString stringWithFormat:format, nodeName];
}
@end
@interface MWMAutoupdateController () <MWMCircularProgressProtocol, MWMStorageObserver>
{
std::unordered_set<CountryId> m_updatingCountries;
}
@property(nonatomic) Framework::DoAfterUpdate todo;
@property(nonatomic) MwmSize sizeInMB;
@property(nonatomic) NodeErrorCode errorCode;
@property(nonatomic) BOOL progressFinished;
@end
@implementation MWMAutoupdateController
+ (instancetype)instanceWithPurpose:(Framework::DoAfterUpdate)todo
{
MWMAutoupdateController * controller =
[[MWMAutoupdateController alloc] initWithNibName:[self className] bundle:NSBundle.mainBundle];
controller.todo = todo;
auto view = static_cast<MWMAutoupdateView *>(controller.view);
view.delegate = controller;
[[MWMStorage sharedStorage] addObserver:controller];
[controller updateSize];
return controller;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.progressFinished = NO;
MWMAutoupdateView *view = (MWMAutoupdateView *)self.view;
if (self.todo == Framework::DoAfterUpdate::AutoupdateMaps)
{
[view stateDownloading];
[[MWMStorage sharedStorage] updateNode:RootId() onCancel:^{
[self updateSize];
[view stateWaiting];
}];
}
else
{
[view stateWaiting];
}
}
- (void)dismiss
{
[static_cast<MWMAutoupdateView *>(self.view) stopSpinner];
[self dismissViewControllerAnimated:YES completion:^{
[[MWMStorage sharedStorage] removeObserver:self];
}];
}
- (void)updateSize
{
auto containerView = static_cast<MWMAutoupdateView *>(self.view);
auto const & s = GetFramework().GetStorage();
storage::Storage::UpdateInfo updateInfo;
s.GetUpdateInfo(s.GetRootId(), updateInfo);
MwmSize const updateSizeInBytes = updateInfo.m_totalDownloadSizeInBytes;
containerView.updateSize = formattedSize(updateSizeInBytes);
_sizeInMB = updateSizeInBytes / MB;
}
- (IBAction)updateTap
{
MWMAutoupdateView *view = (MWMAutoupdateView *)self.view;
[view stateDownloading];
[[MWMStorage sharedStorage] updateNode:RootId() onCancel:^{
[self updateSize];
[view stateWaiting];
}];
}
- (IBAction)hideTap { [self dismiss]; }
- (void)cancel
{
auto view = static_cast<MWMAutoupdateView *>(self.view);
UIAlertController * alertController =
[UIAlertController alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
alertController.popoverPresentationController.sourceView = view.secondaryButton;
alertController.popoverPresentationController.sourceRect = view.secondaryButton.bounds;
auto cancelDownloadAction =
[UIAlertAction actionWithTitle:L(@"cancel_download")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * action) {
[[MWMStorage sharedStorage] cancelDownloadNode:RootId()];
[self dismiss];
}];
[alertController addAction:cancelDownloadAction];
auto cancelAction =
[UIAlertAction actionWithTitle:L(@"cancel") style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[static_cast<MWMAutoupdateView *>(self.view) updateForSize:size];
} completion:nil];
}
- (void)updateProcessStatus:(CountryId const &)countryId
{
auto const & s = GetFramework().GetStorage();
NodeAttrs nodeAttrs;
s.GetNodeAttrs(s.GetRootId(), nodeAttrs);
auto view = static_cast<MWMAutoupdateView *>(self.view);
NSString * nodeName = @(s.GetNodeLocalName(countryId).c_str());
[view setStatusForNodeName:nodeName rootAttributes:nodeAttrs];
if (nodeAttrs.m_downloadingProgress.m_bytesDownloaded == nodeAttrs.m_downloadingProgress.m_bytesTotal)
self.progressFinished = YES;
}
#pragma mark - MWMCircularProgressProtocol
- (void)progressButtonPressed:(MWMCircularProgress *)progress { [self cancel]; }
#pragma mark - MWMStorageObserver
- (void)processCountryEvent:(NSString *)countryId
{
NodeStatuses nodeStatuses;
GetFramework().GetStorage().GetNodeStatuses(countryId.UTF8String, nodeStatuses);
if (nodeStatuses.m_status == NodeStatus::Error)
{
self.errorCode = nodeStatuses.m_error;
SEL const process = @selector(processError);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:process object:nil];
[self performSelector:process withObject:nil afterDelay:0.2];
}
if (!nodeStatuses.m_groupNode)
{
switch (nodeStatuses.m_status)
{
case NodeStatus::Error:
case NodeStatus::OnDisk: m_updatingCountries.erase(countryId.UTF8String); break;
default: m_updatingCountries.insert(countryId.UTF8String);
}
}
if (self.progressFinished && m_updatingCountries.empty())
[self dismiss];
else
[self updateProcessStatus:countryId.UTF8String];
}
- (void)processError
{
[self updateSize];
[static_cast<MWMAutoupdateView *>(self.view) stateWaiting];
[[MWMStorage sharedStorage] cancelDownloadNode:RootId()];
}
- (void)processCountry:(NSString *)countryId
downloadedBytes:(uint64_t)downloadedBytes
totalBytes:(uint64_t)totalBytes
{
if (m_updatingCountries.find(countryId.UTF8String) != m_updatingCountries.end())
[self updateProcessStatus:countryId.UTF8String];
}
@end

View file

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MWMAutoupdateController">
<connections>
<outlet property="view" destination="4Q8-tQ-qqq" id="Efv-Uv-ihD"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="4Q8-tQ-qqq" customClass="MWMAutoupdateView" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CrL-6X-EJl" userLabel="Container">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Cw-y3-ipv" userLabel="BoundsView">
<rect key="frame" x="16" y="100" width="382" height="650"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vVR-Wh-jf5" userLabel="CenteredView">
<rect key="frame" x="0.0" y="198.5" width="382" height="253.5"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalCompressionResistancePriority="749" image="Logo" translatesAutoresizingMaskIntoConstraints="NO" id="ym1-j4-Fn7">
<rect key="frame" x="111" y="0.0" width="160" height="160"/>
<constraints>
<constraint firstAttribute="height" relation="lessThanOrEqual" priority="800" constant="240" id="0ZT-MS-DwR"/>
<constraint firstAttribute="height" relation="lessThanOrEqual" priority="800" constant="160" id="lKy-F4-tpj"/>
<constraint firstAttribute="width" secondItem="ym1-j4-Fn7" secondAttribute="height" multiplier="1:1" id="yrH-8J-BCh"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Update your downloaded maps " textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MdA-9O-3gH" userLabel="Title">
<rect key="frame" x="0.0" y="180" width="382" height="24"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="20"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium18:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="whats_new_auto_update_title"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Updated maps supports information about objects in the current state" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WLZ-Z2-pj2" userLabel="Text">
<rect key="frame" x="0.0" y="220" width="382" height="33.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="whats_new_auto_update_message"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="CenteredView"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="400" id="3h0-Vi-2o8"/>
<constraint firstItem="WLZ-Z2-pj2" firstAttribute="top" secondItem="MdA-9O-3gH" secondAttribute="bottom" constant="16" id="7GI-WD-Pbc"/>
<constraint firstItem="MdA-9O-3gH" firstAttribute="centerX" secondItem="vVR-Wh-jf5" secondAttribute="centerX" id="EEg-qo-ze1"/>
<constraint firstItem="ym1-j4-Fn7" firstAttribute="centerX" secondItem="vVR-Wh-jf5" secondAttribute="centerX" id="Fdz-HB-l8c"/>
<constraint firstItem="ym1-j4-Fn7" firstAttribute="top" secondItem="vVR-Wh-jf5" secondAttribute="top" id="Jen-Ph-CoE"/>
<constraint firstAttribute="bottom" secondItem="WLZ-Z2-pj2" secondAttribute="bottom" id="KyM-Hx-UGh"/>
<constraint firstItem="WLZ-Z2-pj2" firstAttribute="width" secondItem="vVR-Wh-jf5" secondAttribute="width" id="Mvt-BV-8I4"/>
<constraint firstItem="WLZ-Z2-pj2" firstAttribute="centerX" secondItem="vVR-Wh-jf5" secondAttribute="centerX" id="RGt-pT-PAB"/>
<constraint firstItem="MdA-9O-3gH" firstAttribute="width" secondItem="vVR-Wh-jf5" secondAttribute="width" id="Vmk-jQ-wvC"/>
<constraint firstItem="MdA-9O-3gH" firstAttribute="top" secondItem="vVR-Wh-jf5" secondAttribute="top" priority="740" id="dh3-iA-fGg"/>
<constraint firstItem="MdA-9O-3gH" firstAttribute="top" secondItem="ym1-j4-Fn7" secondAttribute="bottom" priority="750" constant="20" id="vk2-tC-DOE"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="BoundsView"/>
<constraints>
<constraint firstItem="vVR-Wh-jf5" firstAttribute="height" relation="lessThanOrEqual" secondItem="5Cw-y3-ipv" secondAttribute="height" id="6PW-WV-Zsn"/>
<constraint firstItem="vVR-Wh-jf5" firstAttribute="leading" secondItem="5Cw-y3-ipv" secondAttribute="leading" priority="999" id="9Eo-eG-hWU"/>
<constraint firstItem="vVR-Wh-jf5" firstAttribute="centerY" secondItem="5Cw-y3-ipv" secondAttribute="centerY" id="TGt-HK-faY"/>
<constraint firstAttribute="trailing" secondItem="vVR-Wh-jf5" secondAttribute="trailing" priority="999" id="ZA8-2V-Loy"/>
<constraint firstItem="vVR-Wh-jf5" firstAttribute="centerX" secondItem="5Cw-y3-ipv" secondAttribute="centerX" id="oRE-Lp-pk2"/>
</constraints>
</view>
<button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5nF-zu-Yw0">
<rect key="frame" x="87" y="778" width="240" height="44"/>
<color key="backgroundColor" red="0.12549019610000001" green="0.58823529409999997" blue="0.95294117649999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="ARV-BQ-Avk"/>
<constraint firstAttribute="height" constant="44" id="eRe-p3-Uls"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Update All Maps">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="FlatNormalButton"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="updateTap" destination="-1" eventType="touchUpInside" id="Xf3-wC-jZS"/>
</connections>
</button>
<button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="wordWrap" translatesAutoresizingMaskIntoConstraints="NO" id="iEE-M5-NnV">
<rect key="frame" x="87" y="832" width="240" height="44"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="240" id="adp-HR-zDl"/>
<constraint firstAttribute="height" constant="44" id="qzn-GV-spI"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" title="Manualy update later ">
<color key="titleColor" red="0.01176470588" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="FlatNormalTransButton"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="hideTap" destination="-1" eventType="touchUpInside" id="fN3-tg-y0A"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SqI-nm-lNO">
<rect key="frame" x="189" y="766" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="Lfg-Ln-a6V"/>
<constraint firstAttribute="height" constant="36" id="oVz-gc-j9W"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bax-Ah-It4">
<rect key="frame" x="207" y="810" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BGi-NW-WUx" propertyAccessControl="none">
<rect key="frame" x="207" y="812" width="0.0" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="ContainerView"/>
<constraints>
<constraint firstItem="SqI-nm-lNO" firstAttribute="centerX" secondItem="CrL-6X-EJl" secondAttribute="centerX" id="2zw-eu-fGT"/>
<constraint firstItem="BGi-NW-WUx" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="CrL-6X-EJl" secondAttribute="leading" constant="8" id="3Zi-QM-5As"/>
<constraint firstItem="5nF-zu-Yw0" firstAttribute="centerX" secondItem="CrL-6X-EJl" secondAttribute="centerX" id="9se-pw-oRi"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="BGi-NW-WUx" secondAttribute="trailing" constant="8" id="DES-sa-ZaG"/>
<constraint firstItem="iEE-M5-NnV" firstAttribute="top" secondItem="5nF-zu-Yw0" secondAttribute="bottom" priority="500" constant="10" id="LWi-4H-ezA"/>
<constraint firstItem="BGi-NW-WUx" firstAttribute="top" secondItem="Bax-Ah-It4" secondAttribute="bottom" constant="2" id="M4K-cH-fj1"/>
<constraint firstAttribute="bottom" secondItem="iEE-M5-NnV" secondAttribute="bottom" constant="20" id="Mag-7y-dYi"/>
<constraint firstItem="SqI-nm-lNO" firstAttribute="top" relation="greaterThanOrEqual" secondItem="5Cw-y3-ipv" secondAttribute="bottom" constant="16" id="ORg-MD-Tx2"/>
<constraint firstAttribute="trailing" secondItem="5Cw-y3-ipv" secondAttribute="trailing" constant="16" id="QsU-yA-a5N"/>
<constraint firstItem="5Cw-y3-ipv" firstAttribute="top" secondItem="CrL-6X-EJl" secondAttribute="top" constant="100" id="S3w-Tp-ulT"/>
<constraint firstItem="ym1-j4-Fn7" firstAttribute="height" secondItem="CrL-6X-EJl" secondAttribute="height" multiplier="0.3" priority="750" id="fWB-qe-uuj"/>
<constraint firstItem="Bax-Ah-It4" firstAttribute="centerX" secondItem="SqI-nm-lNO" secondAttribute="centerX" id="hBq-hC-lp9"/>
<constraint firstItem="BGi-NW-WUx" firstAttribute="centerX" secondItem="Bax-Ah-It4" secondAttribute="centerX" id="hg3-Yn-odO"/>
<constraint firstItem="5Cw-y3-ipv" firstAttribute="leading" secondItem="CrL-6X-EJl" secondAttribute="leading" constant="16" id="iWS-dU-Nwv"/>
<constraint firstItem="iEE-M5-NnV" firstAttribute="top" secondItem="BGi-NW-WUx" secondAttribute="bottom" constant="20" id="icj-Sj-aYh"/>
<constraint firstItem="5nF-zu-Yw0" firstAttribute="top" secondItem="5Cw-y3-ipv" secondAttribute="bottom" priority="250" constant="24" id="jli-Ut-tZT"/>
<constraint firstItem="iEE-M5-NnV" firstAttribute="centerX" secondItem="CrL-6X-EJl" secondAttribute="centerX" id="pHJ-KJ-l9f"/>
<constraint firstItem="Bax-Ah-It4" firstAttribute="top" secondItem="SqI-nm-lNO" secondAttribute="bottom" constant="8" id="uM5-w3-HCC"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MapAutoupdateView"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="CrL-6X-EJl" firstAttribute="width" secondItem="4Q8-tQ-qqq" secondAttribute="width" id="TM3-Rp-oI1"/>
<constraint firstItem="CrL-6X-EJl" firstAttribute="height" secondItem="4Q8-tQ-qqq" secondAttribute="height" id="nfT-Ff-8TY"/>
<constraint firstItem="CrL-6X-EJl" firstAttribute="centerX" secondItem="4Q8-tQ-qqq" secondAttribute="centerX" id="owQ-ao-M5Q"/>
<constraint firstItem="CrL-6X-EJl" firstAttribute="centerY" secondItem="4Q8-tQ-qqq" secondAttribute="centerY" id="sM6-Cf-eHx"/>
</constraints>
<connections>
<outlet property="image" destination="ym1-j4-Fn7" id="XrK-nD-Jii"/>
<outlet property="imageHeight" destination="fWB-qe-uuj" id="k07-Lv-4Ui"/>
<outlet property="imageMinHeight" destination="lKy-F4-tpj" id="zef-yg-5d3"/>
<outlet property="legendLabel" destination="BGi-NW-WUx" id="VoN-Ko-l6w"/>
<outlet property="primaryButton" destination="5nF-zu-Yw0" id="CAx-NV-Vke"/>
<outlet property="progressLabel" destination="Bax-Ah-It4" id="Gcb-na-cit"/>
<outlet property="secondaryButton" destination="iEE-M5-NnV" id="clP-02-Qne"/>
<outlet property="spinnerView" destination="SqI-nm-lNO" id="dBJ-ee-nQn"/>
<outlet property="text" destination="WLZ-Z2-pj2" id="XFP-yM-UH3"/>
<outlet property="title" destination="MdA-9O-3gH" id="7NW-eO-on5"/>
<outlet property="titleImageOffset" destination="vk2-tC-DOE" id="40I-iU-krz"/>
<outlet property="titleTopOffset" destination="dh3-iA-fGg" id="pM9-PQ-ryI"/>
</connections>
<point key="canvasLocation" x="138" y="154"/>
</view>
</objects>
<resources>
<image name="Logo" width="1024" height="1024"/>
</resources>
</document>

View file

@ -0,0 +1,144 @@
class AvailableArea: UIView {
private enum Const {
static let observeKeyPath = "sublayers"
}
var deferNotification: Bool { return true }
private(set) var orientation = UIDeviceOrientation.unknown {
didSet {
scheduleNotification()
}
}
var shouldUpdateAreaFrame: Bool {
if let insets = UIApplication.shared.delegate?.window??.safeAreaInsets {
return insets.left > 0 || insets.right > 0
} else {
return false
}
}
var areaFrame: CGRect {
return alternative(iPhone: {
var frame = self.frame
if self.shouldUpdateAreaFrame {
switch self.orientation {
case .landscapeLeft:
frame.origin.x -= 16
frame.size.width += 60
case .landscapeRight:
frame.origin.x -= 44
frame.size.width += 60
default: break
}
}
return frame
}, iPad: { self.frame })()
}
private var affectingViews = Set<UIView>()
override func didMoveToSuperview() {
super.didMoveToSuperview()
subscribe()
update()
}
deinit {
unsubscribe()
}
private func subscribe() {
guard let ol = superview?.layer else { return }
ol.addObserver(self, forKeyPath: Const.observeKeyPath, options: .new, context: nil)
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
let nc = NotificationCenter.default
nc.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: .main) { _ in
let orientation = UIDevice.current.orientation
guard !orientation.isFlat && orientation != .portraitUpsideDown else { return }
self.orientation = orientation
}
}
private func unsubscribe() {
guard let ol = superview?.layer else { return }
ol.removeObserver(self, forKeyPath: Const.observeKeyPath)
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
override func observeValue(forKeyPath keyPath: String?, of _: Any?, change _: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
if keyPath == Const.observeKeyPath {
DispatchQueue.main.async {
self.update()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
scheduleNotification()
}
private func newAffectingViews(view: UIView) -> Set<UIView> {
var views = Set<UIView>()
if isAreaAffectingView(view) {
views.insert(view)
}
view.subviews.forEach {
views.formUnion(newAffectingViews(view: $0))
}
return views
}
private func update() {
guard let sv = superview else { return }
let newAVs = newAffectingViews(view: sv)
newAVs.subtracting(affectingViews).forEach(addAffectingView)
affectingViews = newAVs
scheduleNotification()
}
func addConstraints(otherView: UIView, directions: MWMAvailableAreaAffectDirections) {
guard !directions.isEmpty else {
LOG(.warning, "Attempt to add empty affecting directions from \(otherView) to \(self)")
return
}
let add = { (sa: NSLayoutConstraint.Attribute, oa: NSLayoutConstraint.Attribute, rel: NSLayoutConstraint.Relation) in
let c = NSLayoutConstraint(item: self, attribute: sa, relatedBy: rel, toItem: otherView, attribute: oa, multiplier: 1, constant: 0)
c.priority = UILayoutPriority.defaultHigh
c.isActive = true
}
[
.top: (.top, .bottom, .greaterThanOrEqual),
.bottom: (.bottom, .top, .lessThanOrEqual),
.left: (.left, .right, .greaterThanOrEqual),
.right: (.right, .left, .lessThanOrEqual),
]
.filter { directions.contains($0.key) }
.map { $0.value }
.forEach(add)
}
@objc
private func scheduleNotification() {
if deferNotification {
let selector = #selector(notifyObserver)
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: selector, object: nil)
perform(selector, with: nil, afterDelay: 0)
} else {
notifyObserver()
}
}
func isAreaAffectingView(_: UIView) -> Bool { return false }
func addAffectingView(_: UIView) {}
@objc func notifyObserver() {}
}
extension MWMAvailableAreaAffectDirections: Hashable {
public var hashValue: Int {
return rawValue
}
}

View file

@ -0,0 +1,7 @@
typedef NS_OPTIONS(NSInteger, MWMAvailableAreaAffectDirections) {
MWMAvailableAreaAffectDirectionsNone = 0,
MWMAvailableAreaAffectDirectionsTop = 1 << 0,
MWMAvailableAreaAffectDirectionsBottom = 1 << 1,
MWMAvailableAreaAffectDirectionsLeft = 1 << 2,
MWMAvailableAreaAffectDirectionsRight = 1 << 3
};

View file

@ -0,0 +1,21 @@
final class NavigationInfoArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.navigationInfoAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.navigationInfoAreaAffectView
let directions = ov.navigationInfoAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMNavigationDashboardManager.updateNavigationInfoAvailableArea(areaFrame)
}
}
extension UIView {
var navigationInfoAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var navigationInfoAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,25 @@
final class PlacePageArea: AvailableArea {
override var areaFrame: CGRect {
return frame
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.placePageAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.placePageAreaAffectView
let directions = ov.placePageAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMPlacePageManagerHelper.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var placePageAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,23 @@
final class SideButtonsArea: AvailableArea {
override var deferNotification: Bool { return false }
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.sideButtonsAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.sideButtonsAreaAffectView
let directions = ov.sideButtonsAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMSideButtons.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var sideButtonsAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var sideButtonsAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,29 @@
final class TabBarArea: AvailableArea {
override var areaFrame: CGRect {
var areaFrame = frame
// Spacing is used only for devices with zero bottom safe area (such as SE).
let additionalBottomSpacing: CGFloat = MapsAppDelegate.theApp().window.safeAreaInsets.bottom.isZero ? -10 : .zero
areaFrame.origin.y += additionalBottomSpacing
return areaFrame
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.tabBarAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.tabBarAreaAffectView
let directions = ov.tabBarAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
BottomTabBarViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var tabBarAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var tabBarAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,21 @@
final class TrackRecordingButtonArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.trackRecordingButtonAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.trackRecordingButtonAreaAffectView
let directions = ov.trackRecordingButtonAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
TrackRecordingButtonViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var trackRecordingButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var trackRecordingButtonAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,21 @@
final class TrafficButtonArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.trafficButtonAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.trafficButtonAreaAffectView
let directions = ov.trafficButtonAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMTrafficButtonViewController.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var trafficButtonAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var trafficButtonAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,24 @@
final class VisibleArea: AvailableArea {
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.visibleAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.visibleAreaAffectView
let directions = ov.visibleAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
if CarPlayService.shared.isCarplayActivated {
return
}
FrameworkHelper.setVisibleViewport(areaFrame, scaleFactor: MapViewController.shared()?.mapView.contentScaleFactor ?? 1.0)
}
}
extension UIView {
@objc var visibleAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var visibleAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,30 @@
final class WidgetsArea: AvailableArea {
override var areaFrame: CGRect {
return alternative(iPhone: {
var frame = super.areaFrame
frame.origin.y -= 16
frame.size.height += 16
return frame
}, iPad: { super.areaFrame })()
}
override func isAreaAffectingView(_ other: UIView) -> Bool {
return !other.widgetsAreaAffectDirections.isEmpty
}
override func addAffectingView(_ other: UIView) {
let ov = other.widgetsAreaAffectView
let directions = ov.widgetsAreaAffectDirections
addConstraints(otherView: ov, directions: directions)
}
override func notifyObserver() {
MWMMapWidgetsHelper.updateAvailableArea(areaFrame)
}
}
extension UIView {
@objc var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections { return [] }
var widgetsAreaAffectView: UIView { return self }
}

View file

@ -0,0 +1,11 @@
#ifndef MWMBottomMenuState_h
#define MWMBottomMenuState_h
typedef NS_ENUM(NSUInteger, MWMBottomMenuState) {
MWMBottomMenuStateHidden,
MWMBottomMenuStateInactive,
MWMBottomMenuStateActive,
MWMBottomMenuStateLayers
};
#endif /* MWMBottomMenuState_h */

View file

@ -0,0 +1,36 @@
@objc class BottomMenuBuilder: NSObject {
@objc static func buildMenu(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) -> UIViewController {
return BottomMenuBuilder.build(mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate,
sections: [.layers, .items])
}
@objc static func buildLayers(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) -> UIViewController {
return BottomMenuBuilder.build(mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate,
sections: [.layers])
}
private static func build(mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate,
sections: [BottomMenuPresenter.Sections]) -> UIViewController {
let viewController = BottomMenuViewController(nibName: nil, bundle: nil)
let interactor = BottomMenuInteractor(viewController: viewController,
mapViewController: mapViewController,
controlsManager: controlsManager,
delegate: delegate)
let presenter = BottomMenuPresenter(view: viewController, interactor: interactor, sections: sections)
interactor.presenter = presenter
viewController.presenter = presenter
return viewController
}
}

View file

@ -0,0 +1,101 @@
protocol BottomMenuInteractorProtocol: AnyObject {
func close()
func addPlace()
func downloadMaps()
func donate()
func openHelp()
func openSettings()
func shareLocation(cell: BottomMenuItemCell)
func toggleTrackRecording()
}
@objc protocol BottomMenuDelegate {
func actionDownloadMaps(_ mode: MWMMapDownloaderMode)
func addPlace()
func didFinishAddingPlace()
}
class BottomMenuInteractor {
weak var presenter: BottomMenuPresenterProtocol?
private weak var viewController: UIViewController?
private weak var mapViewController: MapViewController?
private weak var delegate: BottomMenuDelegate?
private weak var controlsManager: MWMMapViewControlsManager?
private let trackRecorder: TrackRecordingManager = .shared
init(viewController: UIViewController,
mapViewController: MapViewController,
controlsManager: MWMMapViewControlsManager,
delegate: BottomMenuDelegate) {
self.viewController = viewController
self.mapViewController = mapViewController
self.delegate = delegate
self.controlsManager = controlsManager
}
}
extension BottomMenuInteractor: BottomMenuInteractorProtocol {
func close() {
guard let controlsManager = controlsManager else {
fatalError()
}
controlsManager.menuState = controlsManager.menuRestoreState
}
func addPlace() {
delegate?.addPlace()
}
func donate() {
close()
guard var url = SettingsBridge.donateUrl() else { return }
if url == "https://www.comaps.app/donate/" {
url = L("translated_om_site_url") + "donate/"
}
viewController?.openUrl(url, externally: true)
}
func downloadMaps() {
close()
delegate?.actionDownloadMaps(.downloaded)
}
func openHelp() {
close()
mapViewController?.openAbout()
}
func openSettings() {
close()
mapViewController?.openSettings()
}
func shareLocation(cell: BottomMenuItemCell) {
guard let coordinates = LocationManager.lastLocation()?.coordinate else {
viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil)
return
}
guard let viewController = viewController else { return }
let vc = ActivityViewController.share(forMyPosition: coordinates)
vc.present(inParentViewController: viewController, anchorView: cell.anchorView)
}
func toggleTrackRecording() {
close()
let mapViewController = MapViewController.shared()!
switch trackRecorder.recordingState {
case .active:
mapViewController.showTrackRecordingPlacePage()
case .inactive:
trackRecorder.start { result in
switch result {
case .success:
mapViewController.showTrackRecordingPlacePage()
case .failure:
break
}
}
}
}
}

View file

@ -0,0 +1,178 @@
protocol BottomMenuPresenterProtocol: UITableViewDelegate, UITableViewDataSource {
func onClosePressed()
func cellToHighlightIndexPath() -> IndexPath?
func setCellHighlighted(_ highlighted: Bool)
}
class BottomMenuPresenter: NSObject {
enum CellType: Int, CaseIterable {
case addPlace
case recordTrack
case share
case donate
case downloadMaps
case settings
case help
}
enum Sections: Int {
case layers
case items
}
private weak var view: BottomMenuViewProtocol?
private let interactor: BottomMenuInteractorProtocol
private let sections: [Sections]
private var menuCells: [CellType]
private let trackRecorder = TrackRecordingManager.shared
private var cellToHighlight: CellType?
init(view: BottomMenuViewProtocol,
interactor: BottomMenuInteractorProtocol,
sections: [Sections]) {
self.view = view
self.interactor = interactor
self.sections = sections
self.menuCells = []
self.cellToHighlight = Self.getCellToHighlight()
super.init()
}
private static func getCellToHighlight() -> CellType? {
let featureToHighlightData = DeepLinkHandler.shared.getInAppFeatureHighlightData()
guard let featureToHighlightData, featureToHighlightData.urlType == .menu else { return nil }
switch featureToHighlightData.feature {
case .trackRecorder: return .recordTrack
default: return nil
}
}
}
extension BottomMenuPresenter: BottomMenuPresenterProtocol {
func onClosePressed() {
interactor.close()
}
func cellToHighlightIndexPath() -> IndexPath? {
// Highlighting is enabled only for the .items section.
guard let cellToHighlight,
let sectionIndex = sections.firstIndex(of: .items),
let cellIndex = menuCells.firstIndex(of: cellToHighlight) else { return nil }
return IndexPath(row: cellIndex, section: sectionIndex)
}
func setCellHighlighted(_ highlighted: Bool) {
cellToHighlight = nil
}
}
//MARK: -- UITableViewDataSource
extension BottomMenuPresenter {
func numberOfSections(in tableView: UITableView) -> Int {
sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sections[section] {
case .layers:
return 1
case .items:
let leftButtonType = Settings.leftButtonType
menuCells = CellType.allCases.filter { cell in
if cell == .donate {
return false
} else if leftButtonType == .addPlace, cell == .addPlace {
return false
} else if leftButtonType == .recordTrack, cell == .recordTrack {
return false
} else if leftButtonType == .help, cell == .help {
return false
} else if leftButtonType == .settings, cell == .settings {
return false
}
return true
}
return menuCells.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sections[indexPath.section] {
case .layers:
let cell = tableView.dequeueReusableCell(cell: BottomMenuLayersCell.self)!
cell.onClose = { [weak self] in self?.onClosePressed() }
if sections.count > 1 {
cell.addSeparator(.bottom)
}
return cell
case .items:
let cell = tableView.dequeueReusableCell(cell: BottomMenuItemCell.self)!
switch menuCells[indexPath.row] {
case .addPlace:
let enabled = MWMNavigationDashboardManager.shared().state == .hidden && FrameworkHelper.canEditMapAtViewportCenter()
cell.configure(imageName: "plus",
title: L("placepage_add_place_button"),
enabled: enabled)
case .recordTrack:
cell.configure(imageName: "track", title: L("start_track_recording"))
case .downloadMaps:
cell.configure(imageName: "ic_menu_download",
title: L("download_maps"),
badgeCount: MapsAppDelegate.theApp().badgeNumber())
case .donate:
cell.configure(imageName: "ic_menu_donate",
title: L("donate"))
case .help:
cell.configure(imageName: "help",
title: L("help"))
case .settings:
cell.configure(imageName: "gearshape.fill",
title: L("settings"))
case .share:
cell.configure(imageName: "square.and.arrow.up",
title: L("share_my_location"))
}
return cell
}
}
}
//MARK: -- UITableViewDelegate
extension BottomMenuPresenter {
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
return cell.isEnabled ? indexPath : nil
}
return indexPath
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch sections[indexPath.section] {
case .layers:
return;
case .items:
switch menuCells[indexPath.row] {
case .addPlace:
interactor.addPlace()
case .recordTrack:
interactor.toggleTrackRecording()
case .downloadMaps:
interactor.downloadMaps()
case .donate:
interactor.donate()
case .help:
interactor.openHelp()
case .settings:
interactor.openSettings()
case .share:
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
interactor.shareLocation(cell: cell)
}
}
}
}
}

View file

@ -0,0 +1,100 @@
protocol BottomMenuViewProtocol: AnyObject {
var presenter: BottomMenuPresenterProtocol? { get set }
}
class BottomMenuViewController: MWMViewController {
var presenter: BottomMenuPresenterProtocol?
private let transitioningManager = BottomMenuTransitioningManager()
@IBOutlet var tableView: UITableView!
@IBOutlet var heightConstraint: NSLayoutConstraint!
@IBOutlet var bottomConstraint: NSLayoutConstraint!
lazy var chromeView: UIView = {
let view = UIView()
view.setStyle(.presentationBackground)
return view
}()
weak var containerView: UIView! {
didSet {
containerView.insertSubview(chromeView, at: 0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.layer.setCornerRadius(.buttonDefault, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
tableView.sectionFooterHeight = 0
tableView.dataSource = presenter
tableView.delegate = presenter
tableView.registerNib(cell: BottomMenuItemCell.self)
tableView.registerNib(cell: BottomMenuLayersCell.self)
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: nil) { _ in
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let cellToHighlight = presenter?.cellToHighlightIndexPath() {
tableView.cellForRow(at: cellToHighlight)?.highlight()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.setNeedsLayout()
tableView.layoutIfNeeded()
heightConstraint.constant = min(tableView.contentSize.height, view.height)
tableView.isScrollEnabled = tableView.contentSize.height > heightConstraint.constant;
}
@IBAction func onClosePressed(_ sender: Any) {
presenter?.onClosePressed()
}
@IBAction func onPan(_ sender: UIPanGestureRecognizer) {
let yOffset = sender.translation(in: view.superview).y
let yVelocity = sender.velocity(in: view.superview).y
sender.setTranslation(CGPoint.zero, in: view.superview)
bottomConstraint.constant = min(bottomConstraint.constant - yOffset, 0);
let alpha = 1.0 - abs(bottomConstraint.constant / tableView.height)
self.chromeView.alpha = alpha
let state = sender.state
if state == .ended || state == .cancelled {
if yVelocity > 0 || (yVelocity == 0 && alpha < 0.8) {
presenter?.onClosePressed()
} else {
let duration = min(kDefaultAnimationDuration, TimeInterval(self.bottomConstraint.constant / yVelocity))
self.view.layoutIfNeeded()
UIView.animate(withDuration: duration) {
self.chromeView.alpha = 1
self.bottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
}
override var transitioningDelegate: UIViewControllerTransitioningDelegate? {
get { return transitioningManager }
set { }
}
override var modalPresentationStyle: UIModalPresentationStyle {
get { return .custom }
set { }
}
}
extension BottomMenuViewController: BottomMenuViewProtocol {
}

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<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="BottomMenuViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="bottomConstraint" destination="Crm-Ym-Ikk" id="K0d-Ad-Q13"/>
<outlet property="heightConstraint" destination="dYV-fi-iGj" id="chn-o3-rhF"/>
<outlet property="tableView" destination="L4F-0e-1B7" id="dHQ-DU-QPO"/>
<outlet property="view" destination="iN0-l3-epB" id="nOL-DH-swt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DD7-rW-ckP">
<rect key="frame" x="0.0" y="0.0" width="414" height="862"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outletCollection property="gestureRecognizers" destination="sXH-Kv-ZnQ" appends="YES" id="u82-AO-mZ1"/>
</connections>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="L4F-0e-1B7">
<rect key="frame" x="0.0" y="562" width="414" height="300"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="width" constant="350" id="Jyo-UJ-ltJ"/>
<constraint firstAttribute="height" priority="750" constant="300" id="dYV-fi-iGj"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="Jyo-UJ-ltJ"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="Jyo-UJ-ltJ"/>
</mask>
</variation>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M4g-vy-YtE" userLabel="Bottom View">
<rect key="frame" x="0.0" y="862" width="414" height="34"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="L4F-0e-1B7" secondAttribute="bottom" id="Crm-Ym-Ikk"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="top" secondItem="L4F-0e-1B7" secondAttribute="bottom" id="E7M-j3-lrN"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="LOj-yu-5nE"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="trailing" secondItem="L4F-0e-1B7" secondAttribute="trailing" id="PdB-CC-VOI"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Vxc-mf-jVJ"/>
<constraint firstAttribute="trailing" secondItem="DD7-rW-ckP" secondAttribute="trailing" id="WAo-c4-geW"/>
<constraint firstItem="M4g-vy-YtE" firstAttribute="leading" secondItem="L4F-0e-1B7" secondAttribute="leading" id="cjE-84-gfc"/>
<constraint firstItem="DD7-rW-ckP" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="deF-nX-Ae5"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="top" relation="greaterThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="top" id="fDu-HA-dhq"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="g7h-Yh-azG"/>
<constraint firstAttribute="bottom" secondItem="M4g-vy-YtE" secondAttribute="bottom" priority="750" id="hFA-7p-XKe"/>
<constraint firstItem="L4F-0e-1B7" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="t8e-ZM-EdJ"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<variation key="heightClass=compact">
<mask key="constraints">
<exclude reference="g7h-Yh-azG"/>
</mask>
</variation>
<connections>
<outletCollection property="gestureRecognizers" destination="Rdk-jI-mZR" appends="YES" id="bbV-GO-4iF"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="153.34821428571428"/>
</view>
<tapGestureRecognizer cancelsTouchesInView="NO" id="sXH-Kv-ZnQ">
<connections>
<action selector="onClosePressed:" destination="-1" id="nMr-L4-IGY"/>
</connections>
</tapGestureRecognizer>
<panGestureRecognizer delaysTouchesBegan="YES" delaysTouchesEnded="NO" minimumNumberOfTouches="1" id="Rdk-jI-mZR">
<connections>
<action selector="onPan:" destination="-1" id="yGW-xa-dnH"/>
</connections>
</panGestureRecognizer>
</objects>
</document>

View file

@ -0,0 +1,47 @@
import UIKit
class BottomMenuItemCell: UITableViewCell {
@IBOutlet private var label: UILabel!
@IBOutlet private var badgeBackground: UIView!
@IBOutlet private var badgeCountLabel: UILabel!
@IBOutlet private var separator: UIView!
@IBOutlet private var icon: UIImageView!
@IBOutlet private var badgeSpacingConstraint: NSLayoutConstraint!
@IBOutlet private var badgeBackgroundWidthConstraint: NSLayoutConstraint!
var anchorView: UIView {
get {
return icon
}
}
private(set) var isEnabled: Bool = true
func configure(imageName: String, title: String, badgeCount: UInt = .zero, enabled: Bool = true) {
if imageName == "help" {
icon.image = Settings.LeftButtonType.help.image
} else if imageName == "plus" {
icon.image = Settings.LeftButtonType.addPlace.image
} else if imageName == "track" {
icon.image = Settings.LeftButtonType.recordTrack.image
} else if imageName == "ic_menu_download" || imageName == "ic_menu_donate" {
icon.image = UIImage(named: imageName)
} else {
let configuration = UIImage.SymbolConfiguration(pointSize: 22, weight: .semibold)
icon.image = UIImage(systemName: imageName, withConfiguration: configuration)!
}
label.text = title
badgeBackground.isHidden = badgeCount == 0
badgeCountLabel.text = "\(badgeCount)"
if badgeCount == 0 {
badgeSpacingConstraint.constant = 0
badgeBackgroundWidthConstraint.constant = 0
} else {
badgeSpacingConstraint.constant = 8
badgeBackgroundWidthConstraint.constant = 32
}
isEnabled = enabled
icon.setStyleAndApply(isEnabled ? .black : .gray)
label.setFontStyleAndApply(isEnabled ? .blackPrimary : .blackHint)
}
}

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="48" id="gZi-cd-LgO" customClass="BottomMenuItemCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="gZi-cd-LgO" id="rsu-1s-Lsp">
<rect key="frame" x="0.0" y="0.0" width="414" height="48"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_menu_download" translatesAutoresizingMaskIntoConstraints="NO" id="8oJ-8z-qRL">
<rect key="frame" x="16" y="10" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="Hth-JK-nv2"/>
<constraint firstAttribute="height" constant="28" id="zil-Ys-XYB"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMBlack"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Uc-o1-PsF">
<rect key="frame" x="60" y="14" width="298" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="con-tP-3dJ" userLabel="DownloadBadgeBackground">
<rect key="frame" x="366" y="14" width="32" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FT9-8n-RZm" userLabel="DownloadBadgeCount">
<rect key="frame" x="0.0" y="0.0" width="32" height="20"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="FT9-8n-RZm" secondAttribute="trailing" id="Ge3-P2-idS"/>
<constraint firstAttribute="bottom" secondItem="FT9-8n-RZm" secondAttribute="bottom" id="Kgn-y3-dBT"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="top" secondItem="con-tP-3dJ" secondAttribute="top" id="LaN-Vb-w2N"/>
<constraint firstAttribute="height" constant="20" id="Y5v-7h-SGf"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="leading" secondItem="con-tP-3dJ" secondAttribute="leading" id="pE4-Qs-ctY"/>
<constraint firstAttribute="width" constant="32" id="ubK-0L-pDn"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4OJ-wN-dY4" userLabel="Separator">
<rect key="frame" x="60" y="47" width="354" height="1"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.12" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="tDM-AP-ern"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Divider"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="5Uc-o1-PsF" firstAttribute="leading" secondItem="4OJ-wN-dY4" secondAttribute="leading" id="9g1-Uo-zCa"/>
<constraint firstAttribute="bottom" secondItem="8oJ-8z-qRL" secondAttribute="bottom" constant="10" id="KJP-IG-4FK"/>
<constraint firstItem="FT9-8n-RZm" firstAttribute="leading" secondItem="5Uc-o1-PsF" secondAttribute="trailing" constant="8" id="RJA-NS-MXP"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="top" secondItem="rsu-1s-Lsp" secondAttribute="top" constant="10" id="bab-2f-YZY"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="4OJ-wN-dY4" secondAttribute="trailing" id="3Vq-qn-yhm"/>
<constraint firstItem="4OJ-wN-dY4" firstAttribute="leading" secondItem="gZi-cd-LgO" secondAttribute="leading" constant="60" id="7wy-Q8-lja"/>
<constraint firstAttribute="trailing" secondItem="con-tP-3dJ" secondAttribute="trailing" constant="16" id="Bm7-vh-vYz"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="leading" secondItem="gZi-cd-LgO" secondAttribute="leading" constant="16" id="I7K-bz-QOC"/>
<constraint firstAttribute="bottom" secondItem="4OJ-wN-dY4" secondAttribute="bottom" id="SVQ-VP-J4d"/>
<constraint firstItem="con-tP-3dJ" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="UY4-uw-Kda"/>
<constraint firstItem="5Uc-o1-PsF" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="hVt-zE-3iE"/>
<constraint firstItem="8oJ-8z-qRL" firstAttribute="centerY" secondItem="gZi-cd-LgO" secondAttribute="centerY" id="u2b-9I-SLh"/>
</constraints>
<connections>
<outlet property="badgeBackground" destination="con-tP-3dJ" id="vH5-Rd-uqS"/>
<outlet property="badgeBackgroundWidthConstraint" destination="ubK-0L-pDn" id="D3B-sV-eGO"/>
<outlet property="badgeCountLabel" destination="FT9-8n-RZm" id="HLY-2S-do9"/>
<outlet property="badgeSpacingConstraint" destination="RJA-NS-MXP" id="Hpf-eq-1C1"/>
<outlet property="icon" destination="8oJ-8z-qRL" id="qcD-Kv-c5l"/>
<outlet property="label" destination="5Uc-o1-PsF" id="y4l-b6-MCt"/>
<outlet property="separator" destination="4OJ-wN-dY4" id="Kkv-HO-0eK"/>
</connections>
<point key="canvasLocation" x="337.68115942028987" y="527.00892857142856"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_menu_download" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,37 @@
final class BottomMenuLayerButton: VerticallyAlignedButton {
private var badgeView: UIView?
private let badgeSize = CGSize(width: 12, height: 12)
private let badgeOffset = CGPoint(x: -3, y: 3)
var isBadgeHidden: Bool = true{
didSet {
if oldValue != isBadgeHidden {
updateBadge()
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.layer.masksToBounds = true
updateBadge()
}
private func updateBadge() {
if isBadgeHidden {
badgeView?.removeFromSuperview()
badgeView = nil
} else {
if badgeView == nil {
badgeView = UIView()
badgeView?.setStyle(.badge)
addSubview(badgeView!)
}
let imageFrame = imageView.frame
badgeView?.frame = CGRect(x:imageFrame.minX + imageFrame.width - badgeSize.width / 2 + badgeOffset.x,
y:imageFrame.minY - badgeSize.height/2 + badgeOffset.y,
width: badgeSize.width,
height: badgeSize.height)
}
}
}

View file

@ -0,0 +1,107 @@
import UIKit
class BottomMenuLayersCell: UITableViewCell {
@IBOutlet weak var closeButton: CircleImageButton!
@IBOutlet private var subwayButton: BottomMenuLayerButton! {
didSet {
updateSubwayButton()
}
}
@IBOutlet private var isoLinesButton: BottomMenuLayerButton! {
didSet {
updateIsoLinesButton()
}
}
@IBOutlet private var outdoorButton: BottomMenuLayerButton! {
didSet {
updateOutdoorButton()
}
}
var onClose: (()->())?
override func awakeFromNib() {
super.awakeFromNib()
MapOverlayManager.add(self)
closeButton.setImage(UIImage(named: "ic_close"))
setupButtons()
}
private func setupButtons() {
outdoorButton.setupWith(image: UIImage(resource: .btnMenuOutdoors), text: L("button_layer_outdoor"))
isoLinesButton.setupWith(image: UIImage(resource: .btnMenuIsomaps), text: L("button_layer_isolines"))
subwayButton.setupWith(image: UIImage(resource: .btnMenuSubway), text: L("button_layer_subway"))
}
deinit {
MapOverlayManager.remove(self)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
private func updateSubwayButton() {
let enabled = MapOverlayManager.transitEnabled()
subwayButton.setStyleAndApply(styleFor(enabled))
}
private func updateIsoLinesButton() {
let enabled = MapOverlayManager.isoLinesEnabled()
isoLinesButton.setStyleAndApply(styleFor(enabled))
}
private func updateOutdoorButton() {
let enabled = MapOverlayManager.outdoorEnabled()
outdoorButton.setStyleAndApply(styleFor(enabled))
}
@IBAction func onCloseButtonPressed(_ sender: Any) {
onClose?()
}
@IBAction func onSubwayButton(_ sender: Any) {
let enable = !MapOverlayManager.transitEnabled()
MapOverlayManager.setTransitEnabled(enable)
}
@IBAction func onIsoLinesButton(_ sender: Any) {
let enable = !MapOverlayManager.isoLinesEnabled()
MapOverlayManager.setIsoLinesEnabled(enable)
}
@IBAction func onOutdoorButton(_ sender: Any) {
let enable = !MapOverlayManager.outdoorEnabled()
MapOverlayManager.setOutdoorEnabled(enable)
}
}
extension BottomMenuLayersCell: MapOverlayManagerObserver {
func onTransitStateUpdated() {
updateSubwayButton()
}
func onIsoLinesStateUpdated() {
updateIsoLinesButton()
}
func onOutdoorStateUpdated() {
updateOutdoorButton()
}
}
private extension BottomMenuLayersCell {
func styleFor(_ enabled: Bool) -> MapStyleSheet {
enabled ? .mapMenuButtonEnabled : .mapMenuButtonDisabled
}
}
private extension BottomMenuLayerButton {
func setupWith(image: UIImage, text: String) {
self.image = image
spacing = 10
numberOfLines = 2
localizedText = text
}
}

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<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"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="165" id="KGk-i7-Jjw" customClass="BottomMenuLayersCell" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="340" height="165"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="340" height="165"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="W1i-v6-zbz">
<rect key="frame" x="0.0" y="0.0" width="340" height="50"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Map Layers" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vuk-dn-n2c">
<rect key="frame" x="119.5" y="13" width="101.5" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="blackPrimaryText:bold22"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="layers_title"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2xW-dK-D9y" customClass="CircleImageButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="296" y="11" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="BD2-bz-n13"/>
<constraint firstAttribute="width" constant="28" id="Thu-MY-dQm"/>
</constraints>
<connections>
<action selector="onCloseButtonPressed:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="8vd-Pg-Suh"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="00h-1i-skR"/>
<constraint firstItem="2xW-dK-D9y" firstAttribute="leading" secondItem="Vuk-dn-n2c" secondAttribute="trailing" constant="8" id="4PG-Fm-yqS"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="centerY" secondItem="W1i-v6-zbz" secondAttribute="centerY" id="4du-pr-7hv"/>
<constraint firstAttribute="height" constant="45" id="Ez1-s5-1EO"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="centerX" secondItem="W1i-v6-zbz" secondAttribute="centerX" id="XEG-CK-41Y"/>
<constraint firstItem="Vuk-dn-n2c" firstAttribute="leading" secondItem="W1i-v6-zbz" secondAttribute="leading" constant="16" id="kSJ-Wa-nYA"/>
<constraint firstAttribute="trailing" secondItem="2xW-dK-D9y" secondAttribute="trailing" constant="16" id="kae-50-2nG"/>
<constraint firstItem="2xW-dK-D9y" firstAttribute="centerY" secondItem="Vuk-dn-n2c" secondAttribute="centerY" id="wCu-O0-cz8"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="00h-1i-skR"/>
<exclude reference="Ez1-s5-1EO"/>
<exclude reference="XEG-CK-41Y"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="Ez1-s5-1EO"/>
</mask>
</variation>
<variation key="heightClass=regular">
<mask key="constraints">
<include reference="00h-1i-skR"/>
</mask>
</variation>
<variation key="heightClass=regular-widthClass=regular">
<mask key="constraints">
<include reference="XEG-CK-41Y"/>
<exclude reference="kSJ-Wa-nYA"/>
<exclude reference="4PG-Fm-yqS"/>
</mask>
</variation>
</view>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="sRd-zd-xSl">
<rect key="frame" x="16" y="58" width="308" height="64"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g13-pK-Eig" userLabel="Outdoor Button" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="102.5" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onOutdoorButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="UQ2-jj-fPc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="edA-Mo-3Vx" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="102.5" y="0.0" width="103" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onIsoLinesButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="3LS-C2-2Mc"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4US-fZ-cyg" customClass="BottomMenuLayerButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="205.5" y="0.0" width="102.5" height="64"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<action selector="onSubwayButton:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="xxM-kP-gT1"/>
</connections>
</view>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="70" id="d0H-kE-IWx"/>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="nea-IB-ZkL"/>
<constraint firstAttribute="height" constant="64" id="t9j-kf-yze"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="d0H-kE-IWx"/>
<exclude reference="t9j-kf-yze"/>
</mask>
</variation>
<variation key="heightClass=compact">
<mask key="constraints">
<include reference="d0H-kE-IWx"/>
</mask>
</variation>
<variation key="heightClass=regular">
<mask key="constraints">
<include reference="t9j-kf-yze"/>
</mask>
</variation>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="W1i-v6-zbz" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="6Tm-PS-VWZ"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="centerX" secondItem="W1i-v6-zbz" secondAttribute="centerX" id="7AE-Qf-1L3"/>
<constraint firstAttribute="trailing" secondItem="W1i-v6-zbz" secondAttribute="trailing" id="7ka-f7-sqc"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="top" secondItem="W1i-v6-zbz" secondAttribute="bottom" constant="8" id="PTe-W7-QnR"/>
<constraint firstItem="W1i-v6-zbz" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="RLh-Jr-7dm"/>
<constraint firstAttribute="trailing" secondItem="sRd-zd-xSl" secondAttribute="trailing" priority="750" constant="16" id="Z8f-X6-r4N"/>
<constraint firstItem="sRd-zd-xSl" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="750" constant="16" id="hJE-fg-IEX"/>
<constraint firstAttribute="bottom" secondItem="sRd-zd-xSl" secondAttribute="bottom" constant="26" id="iwo-vC-EI9"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="closeButton" destination="2xW-dK-D9y" id="RQI-hb-JpS"/>
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
</connections>
<point key="canvasLocation" x="137.6953125" y="201.953125"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,36 @@
final class BottomMenuPresentationController: UIPresentationController {
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
(presentedViewController as? BottomMenuViewController)?.chromeView.frame = containerView!.bounds
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
guard let presentedViewController = presentedViewController as? BottomMenuViewController,
let coordinator = presentedViewController.transitionCoordinator,
let containerView = containerView else { return }
containerView.addSubview(presentedView!)
presentedViewController.containerView = containerView
presentedViewController.chromeView.frame = containerView.bounds
presentedViewController.chromeView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
presentedViewController.chromeView.alpha = 1
}, completion: nil)
}
override func dismissalTransitionWillBegin() {
super.dismissalTransitionWillBegin()
guard let presentedViewController = presentedViewController as? BottomMenuViewController,
let coordinator = presentedViewController.transitionCoordinator,
let presentedView = presentedView else { return }
coordinator.animate(alongsideTransition: { _ in
presentedViewController.chromeView.alpha = 0
}, completion: { _ in
presentedView.removeFromSuperview()
})
}
}

View file

@ -0,0 +1,35 @@
final class BottomMenuTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
private let isPresentation: Bool
init(isPresentation: Bool) {
self.isPresentation = isPresentation
super.init()
}
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to) else { return }
let animatingVC = isPresentation ? toVC : fromVC
guard let animatingView = animatingVC.view else { return }
let finalFrameForVC = transitionContext.finalFrame(for: animatingVC)
var initialFrameForVC = finalFrameForVC
initialFrameForVC.origin.y += initialFrameForVC.size.height
let initialFrame = isPresentation ? initialFrameForVC : finalFrameForVC
let finalFrame = isPresentation ? finalFrameForVC : initialFrameForVC
animatingView.frame = initialFrame
UIView.animate(withDuration: transitionDuration(using: transitionContext),
animations: { animatingView.frame = finalFrame },
completion: { _ in
transitionContext.completeTransition(true)
})
}
}

View file

@ -0,0 +1,14 @@
final class BottomMenuTransitioningManager: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source _: UIViewController) -> UIPresentationController? {
BottomMenuPresentationController(presentedViewController: presented,
presenting: presenting)
}
func animationController(forPresented _: UIViewController, presenting _: UIViewController, source _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return BottomMenuTransitioning(isPresentation: true)
}
func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return BottomMenuTransitioning(isPresentation: false)
}
}

View file

@ -0,0 +1,14 @@
@objc class BottomTabBarBuilder: NSObject {
@objc static func build(mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) -> BottomTabBarViewController {
let viewController = BottomTabBarViewController(nibName: nil, bundle: nil)
let interactor = BottomTabBarInteractor(viewController: viewController,
mapViewController: mapViewController,
controlsManager: controlsManager)
let presenter = BottomTabBarPresenter(interactor: interactor)
interactor.presenter = presenter
viewController.presenter = presenter
return viewController
}
}

View file

@ -0,0 +1,32 @@
import UIKit
class BottomTabBarButton: MWMButton {
@objc override func applyTheme() {
if styleName.isEmpty {
setStyle(.bottomTabBarButton)
}
for style in StyleManager.shared.getStyle(styleName) where !style.isEmpty && !style.hasExclusion(view: self) {
BottomTabBarButtonRenderer.render(self, style: style)
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}
class BottomTabBarButtonRenderer {
class func render(_ control: BottomTabBarButton, style: Style) {
UIViewRenderer.renderShadow(control, style: style)
UIViewRenderer.renderBorder(control, style: style)
if let coloring = style.coloring {
control.coloring = coloring
}
if let backgroundColor = style.backgroundColor {
control.backgroundColor = backgroundColor
}
}
}

View file

@ -0,0 +1,75 @@
protocol BottomTabBarInteractorProtocol: AnyObject {
func openSearch()
func openLeftButton()
func openBookmarks()
func openMenu()
}
class BottomTabBarInteractor {
weak var presenter: BottomTabBarPresenterProtocol?
private weak var viewController: UIViewController?
private weak var mapViewController: MapViewController?
private weak var controlsManager: MWMMapViewControlsManager?
private let searchManager: SearchOnMapManager
init(viewController: UIViewController, mapViewController: MapViewController, controlsManager: MWMMapViewControlsManager) {
self.viewController = viewController
self.mapViewController = mapViewController
self.controlsManager = controlsManager
self.searchManager = mapViewController.searchManager
}
}
extension BottomTabBarInteractor: BottomTabBarInteractorProtocol {
func openSearch() {
searchManager.isSearching ? searchManager.close() : searchManager.startSearching(isRouting: false)
}
func openLeftButton() {
switch Settings.leftButtonType {
case .addPlace:
if let delegate = controlsManager as? BottomMenuDelegate {
delegate.addPlace()
}
case .settings:
mapViewController?.openSettings()
case .recordTrack:
let mapViewController = MapViewController.shared()!
let trackRecorder: TrackRecordingManager = .shared
switch trackRecorder.recordingState {
case .active:
mapViewController.showTrackRecordingPlacePage()
case .inactive:
trackRecorder.start { result in
switch result {
case .success:
mapViewController.showTrackRecordingPlacePage()
case .failure:
break
}
}
}
default:
mapViewController?.openAbout()
}
}
func openBookmarks() {
mapViewController?.bookmarksCoordinator.open()
}
func openMenu() {
guard let state = controlsManager?.menuState else {
fatalError("ERROR: Failed to retrieve the current MapViewControlsManager's state.")
}
switch state {
case .inactive: controlsManager?.menuState = .active
case .active: controlsManager?.menuState = .inactive
case .hidden:
// When the current controls manager's state is hidden, accidental taps on the menu button during the hiding animation should be skipped.
break;
case .layers: fallthrough
@unknown default: fatalError("ERROR: Unexpected MapViewControlsManager's state: \(state)")
}
}
}

View file

@ -0,0 +1,37 @@
protocol BottomTabBarPresenterProtocol: AnyObject {
func configure()
func onLeftButtonPressed()
func onSearchButtonPressed()
func onBookmarksButtonPressed()
func onMenuButtonPressed()
}
class BottomTabBarPresenter: NSObject {
private let interactor: BottomTabBarInteractorProtocol
init(interactor: BottomTabBarInteractorProtocol) {
self.interactor = interactor
}
}
extension BottomTabBarPresenter: BottomTabBarPresenterProtocol {
func configure() {
}
func onLeftButtonPressed() {
interactor.openLeftButton()
}
func onSearchButtonPressed() {
interactor.openSearch()
}
func onBookmarksButtonPressed() {
interactor.openBookmarks()
}
func onMenuButtonPressed() {
interactor.openMenu()
}
}

View file

@ -0,0 +1,28 @@
let kExtendedTabBarTappableMargin: CGFloat = -15
final class BottomTabBarView: SolidTouchView {
@IBOutlet var mainButtonsView: ExtendedBottomTabBarContainerView!
override var placePageAreaAffectDirections: MWMAvailableAreaAffectDirections {
return alternative(iPhone: [], iPad: [.bottom])
}
override var widgetsAreaAffectDirections: MWMAvailableAreaAffectDirections {
return [.bottom]
}
override var sideButtonsAreaAffectDirections: MWMAvailableAreaAffectDirections {
return [.bottom]
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}
final class ExtendedBottomTabBarContainerView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return bounds.insetBy(dx: kExtendedTabBarTappableMargin, dy: kExtendedTabBarTappableMargin).contains(point)
}
}

View file

@ -0,0 +1,131 @@
class BottomTabBarViewController: UIViewController {
var presenter: BottomTabBarPresenterProtocol!
@IBOutlet var leftButton: MWMButton?
@IBOutlet var searchButton: MWMButton?
@IBOutlet var searchConstraintWithLeftButton: NSLayoutConstraint?
@IBOutlet var searchConstraintWithoutLeftButton: NSLayoutConstraint?
@IBOutlet var bookmarksButton: MWMButton?
@IBOutlet var bookmarksConstraintWithLeftButton: NSLayoutConstraint?
@IBOutlet var bookmarksConstraintWithoutLeftButton: NSLayoutConstraint?
@IBOutlet var moreButton: MWMButton?
@IBOutlet var downloadBadge: UIView?
private var avaliableArea = CGRect.zero
@objc var isHidden: Bool = false {
didSet {
updateFrame(animated: true)
}
}
@objc var isApplicationBadgeHidden: Bool = true {
didSet {
updateBadge()
}
}
var tabBarView: BottomTabBarView {
return view as! BottomTabBarView
}
@objc static var controller: BottomTabBarViewController? {
return MWMMapViewControlsManager.manager()?.tabBarController
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.configure()
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: nil) { _ in
DispatchQueue.main.async {
self.updateLeftButton()
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
leftButton?.imageView?.contentMode = .scaleAspectFit
updateBadge()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
updateLeftButton()
}
static func updateAvailableArea(_ frame: CGRect) {
BottomTabBarViewController.controller?.updateAvailableArea(frame)
}
@IBAction func onSearchButtonPressed(_ sender: Any) {
presenter.onSearchButtonPressed()
}
@IBAction func onLeftButtonPressed(_ sender: Any) {
presenter.onLeftButtonPressed()
}
@IBAction func onBookmarksButtonPressed(_ sender: Any) {
presenter.onBookmarksButtonPressed()
}
@IBAction func onMenuButtonPressed(_ sender: Any) {
presenter.onMenuButtonPressed()
}
private func updateAvailableArea(_ frame:CGRect) {
avaliableArea = frame
updateFrame(animated: false)
self.view.layoutIfNeeded()
}
private func updateFrame(animated: Bool) {
if avaliableArea == .zero {
return
}
let newFrame = CGRect(x: avaliableArea.minX,
y: isHidden ? avaliableArea.minY + avaliableArea.height : avaliableArea.minY,
width: avaliableArea.width,
height: avaliableArea.height)
let alpha:CGFloat = isHidden ? 0 : 1
if animated {
UIView.animate(withDuration: kDefaultAnimationDuration,
delay: 0,
options: [.beginFromCurrentState],
animations: {
self.view.frame = newFrame
self.view.alpha = alpha
}, completion: nil)
} else {
self.view.frame = newFrame
self.view.alpha = alpha
}
}
private func updateLeftButton() {
let leftButtonType = Settings.leftButtonType
if leftButtonType == .hidden {
leftButton?.isHidden = true
if let searchConstraintWithLeftButton, let searchConstraintWithoutLeftButton, let bookmarksConstraintWithLeftButton, let bookmarksConstraintWithoutLeftButton {
NSLayoutConstraint.activate([searchConstraintWithoutLeftButton, bookmarksConstraintWithoutLeftButton])
NSLayoutConstraint.deactivate([searchConstraintWithLeftButton, bookmarksConstraintWithLeftButton])
}
} else {
leftButton?.isHidden = false
leftButton?.setTitle(nil, for: .normal)
leftButton?.setImage(leftButtonType.image, for: .normal)
leftButton?.accessibilityLabel = leftButtonType.description;
if let searchConstraintWithLeftButton, let searchConstraintWithoutLeftButton, let bookmarksConstraintWithLeftButton, let bookmarksConstraintWithoutLeftButton {
NSLayoutConstraint.activate([searchConstraintWithLeftButton, bookmarksConstraintWithLeftButton])
NSLayoutConstraint.deactivate([searchConstraintWithoutLeftButton, bookmarksConstraintWithoutLeftButton])
}
}
}
private func updateBadge() {
downloadBadge?.isHidden = isApplicationBadgeHidden
}
}

View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
<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="BottomTabBarViewController" customModule="CoMaps" customModuleProvider="target">
<connections>
<outlet property="bookmarksButton" destination="dgG-ki-3tB" id="md5-3T-9tb"/>
<outlet property="bookmarksConstraintWithLeftButton" destination="Jc7-nc-elY" id="gW7-8e-E6m"/>
<outlet property="bookmarksConstraintWithoutLeftButton" destination="NRb-vj-MFg" id="C3Z-Ia-D6i"/>
<outlet property="downloadBadge" destination="uDI-ZC-4wx" id="fAf-cy-Ozn"/>
<outlet property="leftButton" destination="dzf-7Z-N6a" id="LMZ-H7-ftQ"/>
<outlet property="moreButton" destination="svD-yi-GrZ" id="kjk-ZW-nZN"/>
<outlet property="searchButton" destination="No0-ld-JX3" id="m5F-UT-j94"/>
<outlet property="searchConstraintWithLeftButton" destination="tDb-w1-ueQ" id="WaI-Xb-1bu"/>
<outlet property="searchConstraintWithoutLeftButton" destination="cQg-jW-uSD" id="cMy-EC-G07"/>
<outlet property="view" destination="zuH-WU-hiP" id="eoa-4I-wKs"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="zuH-WU-hiP" customClass="BottomTabBarView" customModule="CoMaps" customModuleProvider="target" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="373" height="84"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vum-s3-PHx" userLabel="MainButtons" customClass="ExtendedBottomTabBarContainerView" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="373" height="48"/>
<subviews>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dzf-7Z-N6a" userLabel="LeftButton" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="22.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="helpButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dzf-7Z-N6a" secondAttribute="height" id="qNJ-0K-sK0"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<inset key="imageEdgeInsets" minX="9" minY="9" maxX="9" maxY="9"/>
<state key="normal" image="info.circle" catalog="system"/>
<connections>
<action selector="onLeftButtonPressed:" destination="-1" eventType="touchUpInside" id="1gx-P2-sRJ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="249" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="No0-ld-JX3" userLabel="Search" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="116" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="searchButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="No0-ld-JX3" secondAttribute="height" id="2bW-fc-Hsh"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_search"/>
<connections>
<action selector="onSearchButtonPressed:" destination="-1" eventType="touchUpInside" id="0D5-RB-HBQ"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dgG-ki-3tB" userLabel="Bookmarks" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="209" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="bookmarksButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="dgG-ki-3tB" secondAttribute="height" id="o3b-it-lrV"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu_bookmark_list"/>
<connections>
<action selector="onBookmarksButtonPressed:" destination="-1" eventType="touchUpInside" id="9Z1-eg-xth"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleAspectFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="svD-yi-GrZ" userLabel="Menu" customClass="BottomTabBarButton" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="302.5" y="0.0" width="48" height="48"/>
<accessibility key="accessibilityConfiguration" identifier="menuButton"/>
<constraints>
<constraint firstAttribute="width" secondItem="svD-yi-GrZ" secondAttribute="height" id="gmG-3a-Mqe"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="ic_menu"/>
<connections>
<action selector="onMenuButtonPressed:" destination="-1" eventType="touchUpInside" id="rzb-y4-nR1"/>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uDI-ZC-4wx" userLabel="DownloadBadge">
<rect key="frame" x="329.5" y="11" width="10" height="10"/>
<color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="width" constant="10" id="tEP-Xi-qnU"/>
<constraint firstAttribute="height" constant="10" id="wNg-5Z-7AO"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Badge"/>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<accessibility key="accessibilityConfiguration" identifier="MainButtons"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="69A-eu-uLp"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="8nL-zT-Y7b"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="9eR-I7-7at"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Fde-um-JL6"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.25" id="Jc7-nc-elY"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="JjT-sc-hIY"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" priority="900" id="NRb-vj-MFg"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="1.75" id="Q0b-gd-HwS"/>
<constraint firstItem="dgG-ki-3tB" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="Rs8-Hl-CAc"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerX" secondItem="svD-yi-GrZ" secondAttribute="centerX" constant="8" id="XNb-Ba-Hn7"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="Zug-zY-KIX"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.25" priority="900" id="cQg-jW-uSD"/>
<constraint firstItem="svD-yi-GrZ" firstAttribute="centerY" secondItem="vum-s3-PHx" secondAttribute="centerY" id="sja-hO-YY3"/>
<constraint firstItem="No0-ld-JX3" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.75" id="tDb-w1-ueQ"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="centerX" secondItem="vum-s3-PHx" secondAttribute="centerX" multiplier="0.25" id="u3G-gY-98J"/>
<constraint firstItem="dzf-7Z-N6a" firstAttribute="height" secondItem="vum-s3-PHx" secondAttribute="height" id="yTg-8g-H1p"/>
<constraint firstItem="uDI-ZC-4wx" firstAttribute="centerY" secondItem="svD-yi-GrZ" secondAttribute="centerY" constant="-8" id="yq3-ui-IaL"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="cQg-jW-uSD"/>
<exclude reference="NRb-vj-MFg"/>
</mask>
</variation>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="aaw-Hz-zma"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vum-s3-PHx" firstAttribute="top" secondItem="zuH-WU-hiP" secondAttribute="top" id="PQS-ro-25e"/>
<constraint firstItem="vum-s3-PHx" firstAttribute="leading" secondItem="zuH-WU-hiP" secondAttribute="leading" id="kza-JN-Dul"/>
<constraint firstAttribute="trailing" secondItem="vum-s3-PHx" secondAttribute="trailing" id="sM6-P2-rN9"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="mainButtonsView" destination="vum-s3-PHx" id="fBi-DA-orA"/>
</connections>
<point key="canvasLocation" x="72" y="254"/>
</view>
</objects>
<resources>
<image name="ic_menu" width="48" height="48"/>
<image name="ic_menu_bookmark_list" width="48" height="48"/>
<image name="ic_menu_search" width="48" height="48"/>
<image name="info.circle" catalog="system" width="128" height="123"/>
</resources>
</document>

View file

@ -0,0 +1,202 @@
final class CarPlayMapViewController: MWMViewController {
private(set) var mapView: EAGLView?
@IBOutlet var speedInfoView: UIView!
@IBOutlet var speedCamLimitContainer: UIView!
@IBOutlet var speedCamImageView: UIImageView!
@IBOutlet var speedCamLimitLabel: UILabel!
@IBOutlet var currentSpeedView: UIView!
@IBOutlet var currentSpeedLabel: UILabel!
private var currentSpeedMps: Double = 0.0
private var speedLimitMps: Double?
private var speedCamLimitMps: Double?
private var isCameraOnRoute: Bool = false
private var viewPortState: CPViewPortState = .default
private var isSpeedCamBlinking: Bool = false
private var isLeftWheelCar: Bool {
return self.speedInfoView.frame.origin.x > self.view.frame.midX
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if mapView?.drapeEngineCreated == false && !MapsAppDelegate.isTestsEnvironment() {
mapView?.createDrapeEngine()
}
updateVisibleViewPortState(viewPortState)
}
func addMapView(_ mapView: EAGLView, mapButtonSafeAreaLayoutGuide: UILayoutGuide) {
mapView.translatesAutoresizingMaskIntoConstraints = false
removeMapView()
self.mapView = mapView
mapView.frame = view.bounds
view.insertSubview(mapView, at: 0)
mapView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
speedInfoView.trailingAnchor.constraint(equalTo: mapButtonSafeAreaLayoutGuide.trailingAnchor).isActive = true
speedCamLimitContainer.layer.borderWidth = 2.0
}
func removeMapView() {
if let mapView = self.mapView {
mapView.removeFromSuperview()
self.mapView = nil
}
}
func hideSpeedControl() {
if !speedInfoView.isHidden {
speedInfoView.isHidden = true
}
}
func showSpeedControl() {
if speedInfoView.isHidden {
speedInfoView.isHidden = false
}
}
func updateCurrentSpeed(_ speedMps: Double, speedLimitMps: Double?) {
self.currentSpeedMps = speedMps
self.speedLimitMps = speedLimitMps
updateSpeedControl()
}
func updateCameraInfo(isCameraOnRoute: Bool, speedLimitMps: Double?) {
self.isCameraOnRoute = isCameraOnRoute
self.speedCamLimitMps = speedLimitMps
updateSpeedControl()
}
private func BlinkSpeedCamLimit(blink: Bool)
{
if blink {
if !isSpeedCamBlinking {
speedCamLimitLabel.alpha = 0
speedCamImageView.alpha = 1
UIView.animate(withDuration: 0.5,
delay:0.0,
options:[.repeat, .autoreverse, .curveEaseOut],
animations: { self.speedCamImageView.alpha = 0; self.speedCamLimitLabel.alpha = 1 })
isSpeedCamBlinking = true
}
} else {
if (isSpeedCamBlinking) {
speedCamLimitLabel.layer.removeAllAnimations()
speedCamImageView.layer.removeAllAnimations()
isSpeedCamBlinking = false
}
}
}
private func updateSpeedControl() {
let speedMeasure = Measure.init(asSpeed: currentSpeedMps)
currentSpeedLabel.text = speedMeasure.valueAsString
if isCameraOnRoute {
speedCamLimitContainer.layer.borderColor = UIColor.speedLimitRed().cgColor
speedCamImageView.tintColor = UIColor.speedLimitRed()
// self.speedCamLimitMps comes from SpeedCamManager and is based on
// the nearest speed camera info when it is close enough.
// If it's unknown self.speedLimitMps is used, which is based on current road speed limit.
if let speedCamLimitMps = (self.speedCamLimitMps ?? self.speedLimitMps) {
BlinkSpeedCamLimit(blink: true)
let speedCamLimitMeasure = Measure.init(asSpeed: speedCamLimitMps)
speedCamLimitLabel.text = speedCamLimitMeasure.valueAsString
speedCamLimitLabel.textColor = UIColor.speedLimitDarkGray()
currentSpeedLabel.textColor = UIColor.white
if speedCamLimitMps >= currentSpeedMps {
currentSpeedView.backgroundColor = UIColor.speedLimitGreen()
} else {
currentSpeedView.backgroundColor = UIColor.speedLimitRed()
}
} else {
BlinkSpeedCamLimit(blink: false)
speedCamLimitLabel.alpha = 0.0
speedCamImageView.tintColor = UIColor.speedLimitRed()
speedCamImageView.alpha = 1.0
currentSpeedLabel.textColor = UIColor.speedLimitDarkGray()
currentSpeedView.backgroundColor = UIColor.speedLimitWhite()
}
} else { // !isCameraOnRoute
BlinkSpeedCamLimit(blink: false)
currentSpeedLabel.textColor = UIColor.speedLimitDarkGray()
if let speedLimitMps = self.speedLimitMps {
speedCamImageView.alpha = 0.0
let speedLimitMeasure = Measure.init(asSpeed: speedLimitMps)
speedCamLimitLabel.textColor = UIColor.speedLimitDarkGray()
// speedLimitMps == 0 means unlimited speed.
if speedLimitMeasure.value == 0 {
speedCamLimitLabel.text = "🚀" //""
}
else {
speedCamLimitLabel.text = speedLimitMeasure.valueAsString;
}
speedCamLimitLabel.alpha = 1.0
speedCamLimitContainer.layer.borderColor = UIColor.speedLimitRed().cgColor
if currentSpeedMps > speedLimitMps {
currentSpeedLabel.textColor = UIColor.speedLimitRed()
}
} else {
speedCamImageView.tintColor = UIColor.speedLimitLightGray()
speedCamImageView.alpha = 1.0
speedCamLimitLabel.alpha = 0.0
speedCamLimitContainer.layer.borderColor = UIColor.speedLimitLightGray().cgColor
}
currentSpeedView.backgroundColor = UIColor.speedLimitWhite()
}
}
func updateVisibleViewPortState(_ state: CPViewPortState) {
if CarPlayService.shared.isCarplayActivated {
viewPortState = state
switch viewPortState {
case .default:
updateVisibleViewPortToDefaultState()
case .preview:
updateVisibleViewPortToPreviewState()
case .navigation:
updateVisibleViewPortToNavigationState()
}
}
}
private func updateVisibleViewPortToPreviewState() {
updateVisibleViewPort(frame: view.frame.inset(by: view.safeAreaInsets))
}
private func updateVisibleViewPortToNavigationState() {
updateVisibleViewPort(frame: view.frame.inset(by: view.safeAreaInsets))
}
private func updateVisibleViewPortToDefaultState() {
updateVisibleViewPort(frame: view.bounds)
}
private func updateVisibleViewPort(frame: CGRect) {
guard CarPlayService.shared.isCarplayActivated else { return }
FrameworkHelper.setVisibleViewport(frame, scaleFactor: mapView?.contentScaleFactor ?? 1)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// Triggers the map style updating when CarPlay's 'Appearance' setting is changed.
ThemeManager.invalidate()
}
override func applyTheme() {
super.applyTheme()
updateSpeedControl()
}
}

View file

@ -0,0 +1,76 @@
enum ColorPickerType {
case defaultColorPicker(UIColor)
case bookmarkColorPicker(BookmarkColor)
}
final class ColorPicker: NSObject {
static let shared = ColorPicker()
private var onUpdateColorHandler: ((UIColor) -> Void)?
// MARK: - Public
/// Presents a color picker view controller modally from a specified root view controller.
///
/// - Uses native color picker on the iOS 14.0+ for the `defaultColorPicker` type on iPhone and iPad.
/// - For the rest of the iOS versions, `bookmarkColorPicker` type and iPad designed for Mac uses a custom color picker.
///
func present(from rootViewController: UIViewController, pickerType: ColorPickerType, completionHandler: ((UIColor) -> Void)?) {
onUpdateColorHandler = completionHandler
let colorPickerViewController: UIViewController
switch pickerType {
case .defaultColorPicker(let color):
if !ProcessInfo.processInfo.isiOSAppOnMac {
colorPickerViewController = defaultColorPickerViewController(with: color)
} else {
colorPickerViewController = bookmarksColorPickerViewController(with: BookmarkColor.bookmarkColor(from: color) ?? .none)
}
case .bookmarkColorPicker(let bookmarkColor):
colorPickerViewController = bookmarksColorPickerViewController(with: bookmarkColor)
}
rootViewController.present(colorPickerViewController, animated: true)
}
// MARK: - Private
@available(iOS 14.0, *)
private func defaultColorPickerViewController(with selectedColor: UIColor) -> UIViewController {
let colorPickerController = UIColorPickerViewController()
colorPickerController.supportsAlpha = false
colorPickerController.selectedColor = selectedColor
colorPickerController.delegate = self
return colorPickerController
}
private func bookmarksColorPickerViewController(with selectedColor: BookmarkColor) -> UIViewController {
let bookmarksColorViewController = BookmarkColorViewController(bookmarkColor: selectedColor)
bookmarksColorViewController.delegate = self
// The navigation controller is used for getting the navigation item with the title and the close button.
let navigationController = UINavigationController(rootViewController: bookmarksColorViewController)
return navigationController
}
}
// MARK: - BookmarkColorViewControllerDelegate
extension ColorPicker: BookmarkColorViewControllerDelegate {
func bookmarkColorViewController(_ viewController: BookmarkColorViewController, didSelect bookmarkColor: BookmarkColor) {
onUpdateColorHandler?(bookmarkColor.color)
onUpdateColorHandler = nil
viewController.dismiss(animated: true)
}
}
// MARK: - UIColorPickerViewControllerDelegate
extension ColorPicker: UIColorPickerViewControllerDelegate {
@available(iOS 14.0, *)
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
onUpdateColorHandler?(viewController.selectedColor.sRGBColor)
onUpdateColorHandler = nil
viewController.dismiss(animated: true, completion: nil)
}
@available(iOS 14.0, *)
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
onUpdateColorHandler?(viewController.selectedColor.sRGBColor)
}
}

View file

@ -0,0 +1,25 @@
typealias URLsCompletionHandler = ([URL]) -> Void
final class DocumentPicker: NSObject {
static let shared = DocumentPicker()
private var completionHandler: URLsCompletionHandler?
func present(from rootViewController: UIViewController,
fileTypes: [FileType] = [.kml, .kmz, .gpx],
completionHandler: @escaping URLsCompletionHandler) {
self.completionHandler = completionHandler
let documentPickerViewController: UIDocumentPickerViewController
documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: fileTypes.map(\.utType), asCopy: true)
documentPickerViewController.delegate = self
// TODO: Enable multiple selection when the multiple files parsing support will be added to the bookmark_manager.
documentPickerViewController.allowsMultipleSelection = false
rootViewController.present(documentPickerViewController, animated: true)
}
}
// MARK: - UIDocumentPickerDelegate
extension DocumentPicker: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
completionHandler?(urls)
}
}

View file

@ -0,0 +1,22 @@
import UniformTypeIdentifiers
// TODO: (KK) Remove this type-wrapper and use custom UTTypeIdentifier that is registered into the Info.plist after updating to the iOS >= 14.0.
struct FileType {
let fileExtension: String
let typeIdentifier: String
}
extension FileType {
static let kml = FileType(fileExtension: "kml", typeIdentifier: "com.google.earth.kml")
static let kmz = FileType(fileExtension: "kmz", typeIdentifier: "com.google.earth.kmz")
static let gpx = FileType(fileExtension: "gpx", typeIdentifier: "com.topografix.gpx")
}
// MARK: - FileType + UTType
extension FileType {
@available(iOS 14.0, *)
var utType: UTType {
UTType(filenameExtension: fileExtension)!
}
}

View file

@ -0,0 +1,168 @@
class AvailableMapsDataSource {
struct Const {
static let locationArrow = ""
}
private let parentCountryId: String?
private var sections: [String]?
private var sectionsContent: [String: [String]]?
private var nearbySection: [String]?
fileprivate var searching = false
fileprivate lazy var searchDataSource: IDownloaderDataSource = {
SearchMapsDataSource()
}()
init(_ parentCountryId: String? = nil, location: CLLocationCoordinate2D? = nil) {
self.parentCountryId = parentCountryId
let countryIds: [String]
if let parentCountryId = parentCountryId {
countryIds = Storage.shared().allCountries(withParent: parentCountryId)
} else {
countryIds = Storage.shared().allCountries()
}
configSections(countryIds, location: location)
}
private func configSections(_ countryIds: [String], location: CLLocationCoordinate2D?) {
let countries = countryIds.map {
CountryIdAndName(countryId: $0, name: Storage.shared().name(forCountry: $0))
}.sorted {
$0.countryName.compare($1.countryName) == .orderedAscending
}
sections = []
sectionsContent = [:]
if let location = location, let nearbySection = Storage.shared().nearbyAvailableCountries(location) {
sections?.append(Const.locationArrow)
sectionsContent![Const.locationArrow] = nearbySection
}
for country in countries {
let section = parentCountryId == nil ? String(country.countryName.prefix(1)) : L("downloader_available_maps")
if sections!.last != section {
sections!.append(section)
sectionsContent![section] = []
}
var sectionCountries = sectionsContent![section]
sectionCountries?.append(country.countryId)
sectionsContent![section] = sectionCountries
}
}
}
extension AvailableMapsDataSource: IDownloaderDataSource {
var isEmpty: Bool {
searching ? searchDataSource.isEmpty : false
}
var title: String {
guard let parentCountryId = parentCountryId else {
return L("download_maps")
}
return Storage.shared().name(forCountry: parentCountryId)
}
var isRoot: Bool {
parentCountryId == nil
}
var isSearching: Bool {
searching
}
func getParentCountryId() -> String {
if parentCountryId != nil {
return parentCountryId!
}
return Storage.shared().getRootId()
}
func parentAttributes() -> MapNodeAttributes {
guard let parentId = parentCountryId else {
return Storage.shared().attributesForRoot()
}
return Storage.shared().attributes(forCountry: parentId)
}
func numberOfSections() -> Int {
searching ? searchDataSource.numberOfSections() : (sections?.count ?? 0)
}
func numberOfItems(in section: Int) -> Int {
if searching {
return searchDataSource.numberOfItems(in: section)
}
let index = sections![section]
return sectionsContent![index]!.count
}
func item(at indexPath: IndexPath) -> MapNodeAttributes {
if searching {
return searchDataSource.item(at: indexPath)
}
let sectionIndex = sections![indexPath.section]
let sectionItems = sectionsContent![sectionIndex]
let countryId = sectionItems![indexPath.item]
return Storage.shared().attributes(forCountry: countryId)
}
func matchedName(at indexPath: IndexPath) -> String? {
searching ? searchDataSource.matchedName(at: indexPath) : nil
}
func title(for section: Int) -> String {
if searching {
return searchDataSource.title(for: section)
}
let title = sections![section]
if title == Const.locationArrow {
return L("downloader_near_me_subtitle")
}
return title
}
func indexTitles() -> [String]? {
if searching {
return nil
}
if parentCountryId != nil {
return nil
}
return sections
}
func dataSourceFor(_ childId: String) -> IDownloaderDataSource {
searching ? searchDataSource.dataSourceFor(childId) : AvailableMapsDataSource(childId)
}
func reload(_ completion: () -> Void) {
if searching {
searchDataSource.reload(completion)
}
// do nothing.
completion()
}
func search(_ query: String, locale: String, update: @escaping (Bool) -> Void) {
if query.isEmpty {
cancelSearch()
update(true)
return
}
searchDataSource.search(query, locale: locale) { [weak self] (finished) in
if finished {
self?.searching = true
update(finished)
}
}
}
func cancelSearch() {
searching = false
searchDataSource.cancelSearch()
}
}

View file

@ -0,0 +1,5 @@
#import "MWMTableViewCell.h"
@interface MWMMapDownloaderButtonTableViewCell : MWMTableViewCell
@end

View file

@ -0,0 +1,25 @@
#import "MWMMapDownloaderButtonTableViewCell.h"
@implementation MWMMapDownloaderButtonTableViewCell
- (void)awakeFromNib
{
[super awakeFromNib];
[self config];
}
- (void)prepareForReuse
{
[super prepareForReuse];
[self config];
}
- (void)config
{
if ([self respondsToSelector:@selector(setSeparatorInset:)])
[self setSeparatorInset:UIEdgeInsetsZero];
if ([self respondsToSelector:@selector(setLayoutMargins:)])
[self setLayoutMargins:UIEdgeInsetsZero];
}
@end

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="System colors in document resources" minToolsVersion="11.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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="0.0" shouldIndentWhileEditing="NO" reuseIdentifier="MWMMapDownloaderButtonTableViewCell" id="KGk-i7-Jjw" customClass="MWMMapDownloaderButtonTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add Maps Button" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dOW-mN-UY7">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" systemColor="linkColor"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:linkBlueText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="download_maps"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
</tableViewCellContentView>
<inset key="separatorInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="139" y="155"/>
</tableViewCell>
</objects>
<resources>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,3 @@
@interface MWMMapDownloaderCellHeader : UILabel
@end

View file

@ -0,0 +1,23 @@
#import "MWMMapDownloaderCellHeader.h"
#import "SwiftBridge.h"
@implementation MWMMapDownloaderCellHeader
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self setStyleNameAndApply:@"regular12:blackSecondaryText"];
}
return self;
}
- (void)drawTextInRect:(CGRect)rect
{
rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetsMake(0, 16, 0, 0));
rect = UIEdgeInsetsInsetRect(rect, self.safeAreaInsets);
[super drawTextInRect:rect];
}
@end

View file

@ -0,0 +1,5 @@
#import "MWMMapDownloaderTableViewCell.h"
@interface MWMMapDownloaderLargeCountryTableViewCell : MWMMapDownloaderTableViewCell
@end

View file

@ -0,0 +1,31 @@
#import "MWMMapDownloaderLargeCountryTableViewCell.h"
#import "MWMCircularProgress.h"
#import <CoreApi/MWMMapNodeAttributes.h>
@interface MWMMapDownloaderLargeCountryTableViewCell ()
@property (weak, nonatomic) IBOutlet UILabel * mapsCount;
@end
@implementation MWMMapDownloaderLargeCountryTableViewCell
#pragma mark - Config
- (void)config:(MWMMapNodeAttributes *)nodeAttrs searchQuery:(NSString *)searchQuery {
[super config:nodeAttrs searchQuery:searchQuery];
BOOL haveLocalMaps = (nodeAttrs.downloadedMwmCount != 0);
NSString *ofMaps = haveLocalMaps ? [NSString stringWithFormat:L(@"downloader_of"), nodeAttrs.downloadedMwmCount, nodeAttrs.totalMwmCount] : @(nodeAttrs.totalMwmCount).stringValue;
self.mapsCount.text = [NSString stringWithFormat:@"%@: %@", L(@"downloader_status_maps"), ofMaps];
}
- (void)configProgress:(MWMMapNodeAttributes *)nodeAttrs {
[super configProgress:nodeAttrs];
if (nodeAttrs.nodeStatus == MWMMapNodeStatusPartly || nodeAttrs.nodeStatus == MWMMapNodeStatusNotDownloaded) {
MWMCircularProgressStateVec affectedStates = @[@(MWMCircularProgressStateNormal), @(MWMCircularProgressStateSelected)];
[self.progress setImageName:@"ic_folder" forStates:affectedStates];
}
}
@end

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MWMMapDownloaderLargeCountryTableViewCell" rowHeight="62" id="4CW-jw-1JP" customClass="MWMMapDownloaderLargeCountryTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4CW-jw-1JP" id="rFT-0r-5zy">
<rect key="frame" x="0.0" y="0.0" width="320" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3NQ-tT-U9b">
<rect key="frame" x="12" y="13" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="3vs-uR-5WA"/>
<constraint firstAttribute="width" constant="36" id="WtB-IW-uuH"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="People's Republic of China" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cum-4j-JlQ">
<rect key="frame" x="60" y="12" width="167" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.87" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="14 maps" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="851-fC-gEN">
<rect key="frame" x="60" y="36" width="224" height="14"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular12:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="760" text="405 MB" textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Ni-mb-fA5">
<rect key="frame" x="235" y="22.5" width="49" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_right" translatesAutoresizingMaskIntoConstraints="NO" id="7HN-EK-QXO" userLabel="Arrow">
<rect key="frame" x="284" y="17" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="OyB-rL-7rb"/>
<constraint firstAttribute="height" constant="28" id="dNb-uE-TzF"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
</userDefinedRuntimeAttributes>
</imageView>
</subviews>
<constraints>
<constraint firstItem="cum-4j-JlQ" firstAttribute="leading" secondItem="rFT-0r-5zy" secondAttribute="leading" constant="60" id="6W7-tW-rcf"/>
<constraint firstAttribute="trailing" secondItem="9Ni-mb-fA5" secondAttribute="trailing" constant="36" id="C7I-fY-Gfw"/>
<constraint firstItem="851-fC-gEN" firstAttribute="trailing" secondItem="9Ni-mb-fA5" secondAttribute="trailing" id="DJE-zo-NaQ"/>
<constraint firstAttribute="trailing" secondItem="7HN-EK-QXO" secondAttribute="trailing" constant="8" id="EZJ-3g-VZ0"/>
<constraint firstItem="3NQ-tT-U9b" firstAttribute="leading" secondItem="rFT-0r-5zy" secondAttribute="leading" constant="12" id="Km2-Bp-fFb"/>
<constraint firstItem="851-fC-gEN" firstAttribute="leading" secondItem="cum-4j-JlQ" secondAttribute="leading" id="NHY-P5-axf"/>
<constraint firstItem="cum-4j-JlQ" firstAttribute="top" secondItem="rFT-0r-5zy" secondAttribute="top" constant="12" id="SEx-Ax-stg"/>
<constraint firstAttribute="bottom" secondItem="851-fC-gEN" secondAttribute="bottom" constant="12" id="ZwR-py-AZC"/>
<constraint firstItem="851-fC-gEN" firstAttribute="top" secondItem="cum-4j-JlQ" secondAttribute="bottom" constant="4" id="aAE-b0-nwb"/>
<constraint firstItem="9Ni-mb-fA5" firstAttribute="leading" secondItem="cum-4j-JlQ" secondAttribute="trailing" constant="8" id="hV0-8P-jwr"/>
<constraint firstItem="9Ni-mb-fA5" firstAttribute="centerY" secondItem="rFT-0r-5zy" secondAttribute="centerY" id="tB8-k0-qYw"/>
<constraint firstItem="7HN-EK-QXO" firstAttribute="centerY" secondItem="rFT-0r-5zy" secondAttribute="centerY" id="tjc-5f-EBn"/>
<constraint firstItem="3NQ-tT-U9b" firstAttribute="centerY" secondItem="rFT-0r-5zy" secondAttribute="centerY" id="zd3-Ex-VJY"/>
</constraints>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="downloadSize" destination="9Ni-mb-fA5" id="btQ-8t-pIV"/>
<outlet property="mapsCount" destination="851-fC-gEN" id="dY0-zX-KS2"/>
<outlet property="stateWrapper" destination="3NQ-tT-U9b" id="PJw-9U-8b3"/>
<outlet property="title" destination="cum-4j-JlQ" id="f9c-KC-2dR"/>
</connections>
<point key="canvasLocation" x="408.69565217391306" y="269.86607142857139"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_arrow_gray_right" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMMapDownloaderTableViewCell.h"
@interface MWMMapDownloaderPlaceTableViewCell : MWMMapDownloaderTableViewCell
@property (nonatomic) BOOL needDisplayArea;
@end

View file

@ -0,0 +1,42 @@
#import "MWMMapDownloaderPlaceTableViewCell.h"
#import <CoreApi/MWMMapNodeAttributes.h>
@interface MWMMapDownloaderPlaceTableViewCell ()
@property(weak, nonatomic) IBOutlet UILabel *descriptionLabel;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *titleBottomOffset;
@end
@implementation MWMMapDownloaderPlaceTableViewCell
#pragma mark - Config
- (void)config:(MWMMapNodeAttributes *)nodeAttrs searchQuery:(NSString *)searchQuery {
[super config:nodeAttrs searchQuery:searchQuery];
BOOL isDescriptionVisible = NO;
NSDictionary *selectedAreaAttrs = @{NSFontAttributeName : [UIFont bold12]};
NSDictionary *unselectedAreaAttrs = @{NSFontAttributeName : [UIFont regular12]};
self.needDisplayArea = !nodeAttrs.hasParent;
if (self.needDisplayArea && nodeAttrs.topmostParentInfo.count == 1) {
isDescriptionVisible = nodeAttrs.hasParent;
if (isDescriptionVisible) {
self.descriptionLabel.attributedText = [self matchedString:nodeAttrs.topmostParentInfo[0].countryName
selectedAttrs:selectedAreaAttrs
unselectedAttrs:unselectedAreaAttrs];
}
}
else if (nodeAttrs.nodeDescription.length > 0)
{
isDescriptionVisible = YES;
self.descriptionLabel.attributedText = [self matchedString:nodeAttrs.nodeDescription
selectedAttrs:selectedAreaAttrs
unselectedAttrs:unselectedAreaAttrs];
}
self.descriptionLabel.hidden = !isDescriptionVisible;
self.titleBottomOffset.priority =
isDescriptionVisible ? UILayoutPriorityDefaultLow : UILayoutPriorityDefaultHigh;
}
@end

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MWMMapDownloaderPlaceTableViewCell" rowHeight="62" id="1KI-85-wsU" customClass="MWMMapDownloaderPlaceTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1KI-85-wsU" id="mRF-11-OKU">
<rect key="frame" x="0.0" y="0.0" width="320" height="62"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Yb5-r1-Z2X">
<rect key="frame" x="12" y="13" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="OjM-Wz-G8P"/>
<constraint firstAttribute="width" constant="36" id="vYF-tP-Wtw"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="London" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Db-Yq-FlD">
<rect key="frame" x="60" y="12" width="195" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="20" id="fnm-gO-0Fg"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.87" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UK" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fev-4l-MY3">
<rect key="frame" x="60" y="36" width="199" height="14"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular12:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="760" text="45 MB" textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rqh-iy-Sx9">
<rect key="frame" x="263" y="23" width="41" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="3Db-Yq-FlD" firstAttribute="leading" secondItem="mRF-11-OKU" secondAttribute="leading" constant="60" id="2CW-BR-iT1"/>
<constraint firstItem="Yb5-r1-Z2X" firstAttribute="leading" secondItem="mRF-11-OKU" secondAttribute="leading" constant="12" id="GGp-H1-htH"/>
<constraint firstItem="3Db-Yq-FlD" firstAttribute="top" secondItem="mRF-11-OKU" secondAttribute="top" constant="12" id="Jhy-gy-RcP"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="leading" secondItem="3Db-Yq-FlD" secondAttribute="trailing" constant="8" id="NhL-qc-Mcu"/>
<constraint firstAttribute="trailing" secondItem="rqh-iy-Sx9" secondAttribute="trailing" constant="16" id="dil-Rw-64e"/>
<constraint firstItem="fev-4l-MY3" firstAttribute="top" secondItem="3Db-Yq-FlD" secondAttribute="bottom" priority="500" constant="4" id="ePz-Xy-IoM"/>
<constraint firstAttribute="bottom" secondItem="3Db-Yq-FlD" secondAttribute="bottom" priority="250" constant="12" id="ffv-VG-1xY"/>
<constraint firstItem="Yb5-r1-Z2X" firstAttribute="centerY" secondItem="mRF-11-OKU" secondAttribute="centerY" id="g72-Dp-9Ky"/>
<constraint firstItem="fev-4l-MY3" firstAttribute="leading" secondItem="3Db-Yq-FlD" secondAttribute="leading" id="kP7-cA-GeN"/>
<constraint firstAttribute="bottom" secondItem="fev-4l-MY3" secondAttribute="bottom" priority="500" constant="12" id="mJE-YM-1uE"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="centerY" secondItem="mRF-11-OKU" secondAttribute="centerY" id="qZa-yl-a6W"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="leading" secondItem="fev-4l-MY3" secondAttribute="trailing" constant="4" id="rqB-DG-k9N"/>
</constraints>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="descriptionLabel" destination="fev-4l-MY3" id="lyN-5S-iWE"/>
<outlet property="downloadSize" destination="rqh-iy-Sx9" id="g9R-G7-dxQ"/>
<outlet property="stateWrapper" destination="Yb5-r1-Z2X" id="iPW-N5-qJi"/>
<outlet property="title" destination="3Db-Yq-FlD" id="qtt-YF-a0V"/>
<outlet property="titleBottomOffset" destination="ffv-VG-1xY" id="0kL-3a-tKy"/>
</connections>
<point key="canvasLocation" x="408.69565217391306" y="269.86607142857139"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,7 @@
#import "MWMMapDownloaderPlaceTableViewCell.h"
@interface MWMMapDownloaderSubplaceTableViewCell : MWMMapDownloaderPlaceTableViewCell
- (void)setSubplaceText:(NSString *)text;
@end

View file

@ -0,0 +1,17 @@
#import "MWMMapDownloaderSubplaceTableViewCell.h"
@interface MWMMapDownloaderSubplaceTableViewCell ()
@property(weak, nonatomic) IBOutlet UILabel *subPlace;
@end
@implementation MWMMapDownloaderSubplaceTableViewCell
- (void)setSubplaceText:(NSString *)text {
self.subPlace.attributedText = [self matchedString:text
selectedAttrs:@{NSFontAttributeName : [UIFont bold14]}
unselectedAttrs:@{NSFontAttributeName : [UIFont regular14]}];
}
@end

View file

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MWMMapDownloaderSubplaceTableViewCell" rowHeight="82" id="1KI-85-wsU" customClass="MWMMapDownloaderSubplaceTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="82"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1KI-85-wsU" id="mRF-11-OKU">
<rect key="frame" x="0.0" y="0.0" width="320" height="82"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Yb5-r1-Z2X">
<rect key="frame" x="12" y="23" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="OjM-Wz-G8P"/>
<constraint firstAttribute="width" constant="36" id="vYF-tP-Wtw"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="760" text="Mossel Bay" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x7m-Zm-8y6" propertyAccessControl="none">
<rect key="frame" x="60" y="12" width="199" height="16.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="760" text="London" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Db-Yq-FlD">
<rect key="frame" x="60" y="32.5" width="195" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="20" id="CiU-uC-JVj"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.87" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UK" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fev-4l-MY3">
<rect key="frame" x="60" y="56.5" width="199" height="14"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.54000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular12:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="760" text="45 MB" textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rqh-iy-Sx9">
<rect key="frame" x="263" y="33" width="41" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="3Db-Yq-FlD" firstAttribute="leading" secondItem="mRF-11-OKU" secondAttribute="leading" constant="60" id="4Sj-Yh-d4y"/>
<constraint firstItem="3Db-Yq-FlD" firstAttribute="top" secondItem="x7m-Zm-8y6" secondAttribute="bottom" constant="4" id="AFW-TD-ADw"/>
<constraint firstItem="x7m-Zm-8y6" firstAttribute="top" secondItem="mRF-11-OKU" secondAttribute="top" constant="12" id="E4o-jw-VEZ"/>
<constraint firstItem="Yb5-r1-Z2X" firstAttribute="leading" secondItem="mRF-11-OKU" secondAttribute="leading" constant="12" id="GGp-H1-htH"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="leading" secondItem="x7m-Zm-8y6" secondAttribute="trailing" constant="4" id="GsC-fa-5kk"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="leading" secondItem="3Db-Yq-FlD" secondAttribute="trailing" constant="8" id="I7Q-yY-JMS"/>
<constraint firstAttribute="bottom" secondItem="fev-4l-MY3" secondAttribute="bottom" priority="500" constant="12" id="Rnj-5e-Hfy"/>
<constraint firstItem="fev-4l-MY3" firstAttribute="leading" secondItem="3Db-Yq-FlD" secondAttribute="leading" id="Yk6-jH-hKS"/>
<constraint firstAttribute="bottom" secondItem="3Db-Yq-FlD" secondAttribute="bottom" priority="250" constant="12" id="Zic-It-hOL"/>
<constraint firstAttribute="trailing" secondItem="rqh-iy-Sx9" secondAttribute="trailing" constant="16" id="dil-Rw-64e"/>
<constraint firstItem="Yb5-r1-Z2X" firstAttribute="centerY" secondItem="mRF-11-OKU" secondAttribute="centerY" id="g72-Dp-9Ky"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="leading" secondItem="fev-4l-MY3" secondAttribute="trailing" constant="4" id="oqd-UU-h0T"/>
<constraint firstItem="fev-4l-MY3" firstAttribute="top" secondItem="3Db-Yq-FlD" secondAttribute="bottom" constant="4" id="pwP-cL-stJ"/>
<constraint firstItem="x7m-Zm-8y6" firstAttribute="leading" secondItem="3Db-Yq-FlD" secondAttribute="leading" id="pxg-tv-fNn"/>
<constraint firstItem="rqh-iy-Sx9" firstAttribute="centerY" secondItem="mRF-11-OKU" secondAttribute="centerY" id="qZa-yl-a6W"/>
</constraints>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="descriptionLabel" destination="fev-4l-MY3" id="1C2-OF-Vhu"/>
<outlet property="downloadSize" destination="rqh-iy-Sx9" id="q4T-Pv-DrI"/>
<outlet property="stateWrapper" destination="Yb5-r1-Z2X" id="eb8-Ut-gh5"/>
<outlet property="subPlace" destination="x7m-Zm-8y6" id="63W-Bc-FdX"/>
<outlet property="title" destination="3Db-Yq-FlD" id="JdN-yn-xLw"/>
<outlet property="titleBottomOffset" destination="Zic-It-hOL" id="1gW-IS-6Fc"/>
</connections>
<point key="canvasLocation" x="408.69565217391306" y="269.86607142857139"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,32 @@
#import "MWMMapDownloaderMode.h"
#import "MWMTableViewCell.h"
@class MWMCircularProgress;
@class MWMMapNodeAttributes;
@class MWMMapDownloaderTableViewCell;
NS_ASSUME_NONNULL_BEGIN
@protocol MWMMapDownloaderTableViewCellDelegate <NSObject>
- (void)mapDownloaderCellDidPressProgress:(MWMMapDownloaderTableViewCell *)cell;
- (void)mapDownloaderCellDidLongPress:(MWMMapDownloaderTableViewCell *)cell;
@end
@interface MWMMapDownloaderTableViewCell : MWMTableViewCell
@property(nonatomic) MWMCircularProgress *progress;
@property(weak, nonatomic) id<MWMMapDownloaderTableViewCellDelegate> delegate;
@property(nonatomic) MWMMapDownloaderMode mode;
@property(readonly, nonatomic) MWMMapNodeAttributes *nodeAttrs;
- (void)config:(MWMMapNodeAttributes *)nodeAttrs searchQuery:(nullable NSString *)searchQuery;
- (void)configProgress:(MWMMapNodeAttributes *)nodeAttrs;
- (void)setDownloadProgress:(CGFloat)progress;
- (NSAttributedString *)matchedString:(NSString *)str
selectedAttrs:(NSDictionary *)selectedAttrs
unselectedAttrs:(NSDictionary *)unselectedAttrs;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,149 @@
#import "MWMMapDownloaderTableViewCell.h"
#import "MWMCircularProgress.h"
#import "NSString+Categories.h"
#import <CoreApi/MWMCommon.h>
#import <CoreApi/MWMFrameworkHelper.h>
#import <CoreApi/MWMMapNodeAttributes.h>
@interface MWMMapDownloaderTableViewCell () <MWMCircularProgressProtocol>
@property(copy, nonatomic) NSString *searchQuery;
@property(weak, nonatomic) IBOutlet UIView *stateWrapper;
@property(weak, nonatomic) IBOutlet UILabel *title;
@property(weak, nonatomic) IBOutlet UILabel *downloadSize;
@property(strong, nonatomic) MWMMapNodeAttributes *nodeAttrs;
@end
@implementation MWMMapDownloaderTableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
UILongPressGestureRecognizer *lpGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(onLongPress:)];
[self addGestureRecognizer:lpGR];
}
- (void)prepareForReuse {
[super prepareForReuse];
self.nodeAttrs = nil;
}
- (void)onLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan) {
return;
}
[self.delegate mapDownloaderCellDidLongPress:self];
}
#pragma mark - Search matching
- (NSAttributedString *)matchedString:(NSString *)str
selectedAttrs:(NSDictionary *)selectedAttrs
unselectedAttrs:(NSDictionary *)unselectedAttrs {
NSMutableAttributedString *attrTitle = [[NSMutableAttributedString alloc] initWithString:str];
[attrTitle addAttributes:unselectedAttrs range:NSMakeRange(0, str.length)];
if (!self.searchQuery)
return [attrTitle copy];
for (NSValue *range in [str rangesOfString:self.searchQuery])
[attrTitle addAttributes:selectedAttrs range:range.rangeValue];
return [attrTitle copy];
}
#pragma mark - Config
- (void)config:(MWMMapNodeAttributes *)nodeAttrs searchQuery:(NSString *)searchQuery {
self.searchQuery = searchQuery;
self.nodeAttrs = nodeAttrs;
[self configProgress:nodeAttrs];
self.title.attributedText = [self matchedString:nodeAttrs.nodeName
selectedAttrs:@{NSFontAttributeName: [UIFont bold17]}
unselectedAttrs:@{NSFontAttributeName: [UIFont regular17]}];
uint64_t size = 0;
BOOL isModeDownloaded = self.mode == MWMMapDownloaderModeDownloaded;
switch (nodeAttrs.nodeStatus) {
case MWMMapNodeStatusUndefined:
case MWMMapNodeStatusError:
case MWMMapNodeStatusOnDiskOutOfDate:
case MWMMapNodeStatusNotDownloaded:
case MWMMapNodeStatusApplying:
case MWMMapNodeStatusInQueue:
case MWMMapNodeStatusPartly:
size = isModeDownloaded ? nodeAttrs.downloadedSize : nodeAttrs.totalSize;
break;
case MWMMapNodeStatusDownloading:
size = isModeDownloaded ? nodeAttrs.totalUpdateSizeBytes : nodeAttrs.totalSize - nodeAttrs.downloadingSize;
break;
case MWMMapNodeStatusOnDisk:
size = isModeDownloaded ? nodeAttrs.totalSize : 0;
break;
}
self.downloadSize.text = formattedSize(size);
self.downloadSize.hidden = (size == 0);
}
- (void)configProgress:(MWMMapNodeAttributes *)nodeAttrs {
MWMCircularProgress *progress = self.progress;
BOOL isModeDownloaded = self.mode == MWMMapDownloaderModeDownloaded;
MWMButtonColoring coloring = isModeDownloaded ? MWMButtonColoringBlack : MWMButtonColoringBlue;
switch (nodeAttrs.nodeStatus) {
case MWMMapNodeStatusNotDownloaded:
case MWMMapNodeStatusPartly: {
MWMCircularProgressStateVec affectedStates = @[@(MWMCircularProgressStateNormal), @(MWMCircularProgressStateSelected)];
[progress setImageName:@"ic_download" forStates:affectedStates];
[progress setColoring:coloring forStates:affectedStates];
progress.state = MWMCircularProgressStateNormal;
break;
}
case MWMMapNodeStatusDownloading:
progress.progress = kMaxProgress * nodeAttrs.downloadedSize / (isModeDownloaded ? nodeAttrs.totalUpdateSizeBytes : nodeAttrs.totalSize - nodeAttrs.downloadingSize);
break;
case MWMMapNodeStatusApplying:
case MWMMapNodeStatusInQueue:
progress.state = MWMCircularProgressStateSpinner;
break;
case MWMMapNodeStatusUndefined:
case MWMMapNodeStatusError:
progress.state = MWMCircularProgressStateFailed;
break;
case MWMMapNodeStatusOnDisk:
progress.state = MWMCircularProgressStateCompleted;
break;
case MWMMapNodeStatusOnDiskOutOfDate: {
MWMCircularProgressStateVec affectedStates = @[@(MWMCircularProgressStateNormal), @(MWMCircularProgressStateSelected)];
[progress setImageName:@"ic_update" forStates:affectedStates];
[progress setColoring:MWMButtonColoringOther forStates:affectedStates];
progress.state = MWMCircularProgressStateNormal;
break;
}
}
}
- (void)setDownloadProgress:(CGFloat)progress {
self.progress.progress = kMaxProgress * progress;
}
#pragma mark - MWMCircularProgressProtocol
- (void)progressButtonPressed:(nonnull MWMCircularProgress *)progress {
[self.delegate mapDownloaderCellDidPressProgress:self];
}
#pragma mark - Properties
- (MWMCircularProgress *)progress {
if (!_progress) {
_progress = [MWMCircularProgress downloaderProgressForParentView:self.stateWrapper];
_progress.delegate = self;
}
return _progress;
}
@end

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" horizontalCompressionResistancePriority="760" selectionStyle="default" indentationWidth="10" reuseIdentifier="MWMMapDownloaderTableViewCell" rowHeight="52" id="Igh-sI-4cU" customClass="MWMMapDownloaderTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="52"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Igh-sI-4cU" id="hYr-eg-wbg">
<rect key="frame" x="0.0" y="0.0" width="320" height="52"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rTN-aw-5k7">
<rect key="frame" x="12" y="8" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="H0T-d1-25M"/>
<constraint firstAttribute="height" constant="36" id="aA3-Ok-RSD"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="300" verticalHuggingPriority="251" horizontalCompressionResistancePriority="800" text="30 MB" textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lgA-X0-501">
<rect key="frame" x="262.5" y="18" width="41.5" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="260" verticalHuggingPriority="249" horizontalCompressionResistancePriority="752" verticalCompressionResistancePriority="752" text="Algeria" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5kc-K7-7K8">
<rect key="frame" x="60" y="16" width="194.5" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.87" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="lgA-X0-501" firstAttribute="centerY" secondItem="hYr-eg-wbg" secondAttribute="centerY" id="5QG-IM-adn"/>
<constraint firstItem="5kc-K7-7K8" firstAttribute="leading" secondItem="hYr-eg-wbg" secondAttribute="leading" constant="60" id="6Dv-Pw-tTy"/>
<constraint firstAttribute="trailing" secondItem="lgA-X0-501" secondAttribute="trailing" constant="16" id="Af9-0B-Y6q"/>
<constraint firstItem="rTN-aw-5k7" firstAttribute="leading" secondItem="hYr-eg-wbg" secondAttribute="leading" constant="12" id="Jfc-5q-LZI"/>
<constraint firstAttribute="bottom" secondItem="5kc-K7-7K8" secondAttribute="bottom" constant="16" id="ZLY-Tp-hnL"/>
<constraint firstItem="lgA-X0-501" firstAttribute="leading" secondItem="5kc-K7-7K8" secondAttribute="trailing" constant="8" id="Zwe-tB-xNb"/>
<constraint firstItem="5kc-K7-7K8" firstAttribute="top" secondItem="hYr-eg-wbg" secondAttribute="top" constant="16" id="anV-3S-XAR"/>
<constraint firstItem="rTN-aw-5k7" firstAttribute="centerY" secondItem="hYr-eg-wbg" secondAttribute="centerY" id="tND-fx-OhO"/>
</constraints>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="downloadSize" destination="lgA-X0-501" id="Dl4-r1-7lU"/>
<outlet property="stateWrapper" destination="rTN-aw-5k7" id="3jJ-Zp-S0C"/>
<outlet property="title" destination="5kc-K7-7K8" id="SDA-Yd-DD2"/>
</connections>
<point key="canvasLocation" x="413.04347826086962" y="271.875"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,134 @@
import UIKit
protocol DownloadAllViewDelegate: AnyObject {
func onDownloadButtonPressed()
func onRetryButtonPressed()
func onCancelButtonPressed()
func onStateChanged(state: DownloadAllView.State)
}
class DownloadAllView: UIView {
enum State {
case none
case ready
case error
case dowloading
}
enum Style {
case download
case update
}
@IBOutlet private var iconImageView: UIImageView!
@IBOutlet private var title: UILabel!
@IBOutlet private var downloadSizeLabel: UILabel!
@IBOutlet private var stateWrapper: UIView!
@IBOutlet private var downloadButton: UIButton!
@IBOutlet private var titleCenterConstraint: NSLayoutConstraint!
lazy private var progress: MWMCircularProgress = {
let view = MWMCircularProgress.downloaderProgress(forParentView: stateWrapper)
view.delegate = self
return view
}()
var isSizeHidden: Bool = false {
didSet {
if oldValue != isSizeHidden {
updateView()
}
}
}
var style: Style = .download {
didSet {
if oldValue != style {
updateView()
}
}
}
var state: State = .ready {
didSet {
if oldValue != state {
updateView()
delegate?.onStateChanged(state: state)
}
}
}
var downloadSize: UInt64 = 0 {
didSet {
downloadSizeLabel.text = formattedSize(downloadSize)
}
}
var downloadProgress: CGFloat = 0 {
didSet {
self.progress.progress = downloadProgress
}
}
weak var delegate: DownloadAllViewDelegate?
@IBAction func onDownloadButtonPress(_ sender: Any) {
if state == .error {
delegate?.onRetryButtonPressed()
} else {
delegate?.onDownloadButtonPressed()
}
}
private func updateView() {
let readyTitle: String
let downloadingTitle: String
let readyButtonTitle: String
let errorTitle = L("country_status_download_failed")
let errorButtonTitle = L("downloader_retry")
switch style {
case .download:
iconImageView.image = UIImage(named: "ic_download_all")
readyTitle = L("downloader_download_all_button")
downloadingTitle = L("downloader_loading_ios")
readyButtonTitle = L("download_button")
case .update:
iconImageView.image = UIImage(named: "ic_update_all")
readyTitle = L("downloader_update_maps")
downloadingTitle = L("downloader_updating_ios")
readyButtonTitle = L("downloader_update_all_button")
}
titleCenterConstraint.priority = isSizeHidden ? .defaultHigh : .defaultLow
downloadSizeLabel.isHidden = isSizeHidden
switch state {
case .error:
iconImageView.image = UIImage(named: "ic_download_error")
title.text = errorTitle
title.setFontStyleAndApply(.red)
downloadButton.setTitle(errorButtonTitle, for: .normal)
downloadButton.isHidden = false
stateWrapper.isHidden = true
progress.state = .spinner
downloadSizeLabel.isHidden = false
case .ready:
title.text = readyTitle
title.setFontStyleAndApply(.blackPrimary)
downloadButton.setTitle(readyButtonTitle, for: .normal)
downloadButton.isHidden = false
stateWrapper.isHidden = true
progress.state = .spinner
downloadSizeLabel.isHidden = false
case .dowloading:
title.text = downloadingTitle
title.setFontStyleAndApply(.blackPrimary)
downloadButton.isHidden = true
stateWrapper.isHidden = false
progress.state = .spinner
case .none:
self.downloadButton.isHidden = true
self.stateWrapper.isHidden = true
}
}
}
extension DownloadAllView: MWMCircularProgressProtocol {
func progressButtonPressed(_ progress: MWMCircularProgress) {
delegate?.onCancelButtonPressed()
}
}

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<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"/>
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="iN0-l3-epB" customClass="DownloadAllView" customModule="CoMaps" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="64"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="cKg-JT-LQ3">
<rect key="frame" x="16" y="20" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="24" id="IoI-GO-nG3"/>
<constraint firstAttribute="height" constant="24" id="TMm-8V-jMo"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4vQ-dY-JAM">
<rect key="frame" x="58" y="10" width="32" height="20"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="20" id="38P-x2-HCd"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.87" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="760" text="..." textAlignment="right" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mVA-td-yVW">
<rect key="frame" x="58" y="38" width="12" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.66666666669999997" green="0.66666666669999997" blue="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NBt-lB-IvV">
<rect key="frame" x="352" y="17" width="46" height="30"/>
<state key="normal" title="Button"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:linkBlueText"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onDownloadButtonPress:" destination="iN0-l3-epB" eventType="touchUpInside" id="6kx-3L-wc2"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wlz-tD-b2M">
<rect key="frame" x="354" y="14" width="36" height="36"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="36" id="C2G-Y6-VKF"/>
<constraint firstAttribute="width" constant="36" id="Uo3-uS-WpU"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="mVA-td-yVW" firstAttribute="leading" secondItem="cKg-JT-LQ3" secondAttribute="trailing" constant="18" id="1J4-kU-lyG"/>
<constraint firstItem="cKg-JT-LQ3" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="2AL-TB-z3U"/>
<constraint firstItem="wlz-tD-b2M" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="8aq-IR-Ffz"/>
<constraint firstItem="4vQ-dY-JAM" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" priority="500" constant="10" id="AOx-bc-GRy"/>
<constraint firstItem="cKg-JT-LQ3" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="Ehd-uD-6HS"/>
<constraint firstItem="4vQ-dY-JAM" firstAttribute="leading" secondItem="cKg-JT-LQ3" secondAttribute="trailing" constant="18" id="IH6-Jq-o9T"/>
<constraint firstItem="NBt-lB-IvV" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="KUE-7v-eNY"/>
<constraint firstAttribute="trailing" secondItem="NBt-lB-IvV" secondAttribute="trailing" constant="16" id="PgQ-HZ-Ui5"/>
<constraint firstItem="4vQ-dY-JAM" firstAttribute="centerY" secondItem="cKg-JT-LQ3" secondAttribute="centerY" priority="250" id="gWz-3d-LHv"/>
<constraint firstAttribute="bottom" secondItem="mVA-td-yVW" secondAttribute="bottom" constant="10" id="gj3-BP-k3L"/>
<constraint firstAttribute="trailing" secondItem="wlz-tD-b2M" secondAttribute="trailing" constant="24" id="s3R-CP-1zA"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PressBackground"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="downloadButton" destination="NBt-lB-IvV" id="RDS-Sj-rmo"/>
<outlet property="downloadSizeLabel" destination="mVA-td-yVW" id="G7H-Vl-OQQ"/>
<outlet property="iconImageView" destination="cKg-JT-LQ3" id="lvM-rM-apH"/>
<outlet property="stateWrapper" destination="wlz-tD-b2M" id="7XI-dP-yUS"/>
<outlet property="title" destination="4vQ-dY-JAM" id="bZv-x2-pe5"/>
<outlet property="titleCenterConstraint" destination="gWz-3d-LHv" id="6b1-Ah-2ns"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="-162.72321428571428"/>
</view>
</objects>
</document>

View file

@ -0,0 +1,507 @@
import UIKit
@objc(MWMDownloadMapsViewController)
class DownloadMapsViewController: MWMViewController {
// MARK: - Types
private enum NodeAction {
case showOnMap
case download
case update
case cancelDownload
case retryDownload
case delete
}
private enum AllMapsButtonState {
case none
case download(String)
case cancel(String)
}
// MARK: - Outlets
@IBOutlet var tableView: UITableView!
@IBOutlet var noMapsContainer: UIView!
@IBOutlet var downloadAllViewContainer: UIView!
// MARK: Properties
private var searchController = UISearchController(searchResultsController: nil)
var dataSource: IDownloaderDataSource!
@objc var mode: MWMMapDownloaderMode = .downloaded
private var skipCountryEvent = false
private var hasAddMapSection: Bool { dataSource.isRoot && mode == .downloaded }
private let allMapsViewBottomOffsetConstant: CGFloat = 64
lazy var noSerchResultViewController: SearchNoResultsViewController = {
let vc = storyboard!.instantiateViewController(ofType: SearchNoResultsViewController.self)
view.insertSubview(vc.view, aboveSubview: tableView)
vc.view.alignToSuperview()
vc.view.isHidden = true
addChild(vc)
vc.didMove(toParent: self)
return vc
}()
lazy var downloadAllView: DownloadAllView = {
let view = Bundle.main.load(viewClass: DownloadAllView.self)?.first as! DownloadAllView
view.delegate = self
downloadAllViewContainer.addSubview(view)
downloadAllViewContainer.addSeparator()
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
view.topAnchor.constraint(equalTo: downloadAllViewContainer.topAnchor),
view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
])
return view
}()
// MARK: - Methods
override func viewDidLoad() {
super.viewDidLoad()
if dataSource == nil {
switch mode {
case .downloaded:
dataSource = DownloadedMapsDataSource()
case .available:
dataSource = AvailableMapsDataSource(location: LocationManager.lastLocation()?.coordinate)
@unknown default:
fatalError()
}
}
tableView.registerNib(cell: MWMMapDownloaderTableViewCell.self)
tableView.registerNib(cell: MWMMapDownloaderPlaceTableViewCell.self)
tableView.registerNib(cell: MWMMapDownloaderLargeCountryTableViewCell.self)
tableView.registerNib(cell: MWMMapDownloaderSubplaceTableViewCell.self)
tableView.registerNib(cell: MWMMapDownloaderButtonTableViewCell.self)
title = dataSource.title
if mode == .downloaded {
let addMapsButton = button(with: UIImage(systemName: "plus"), action: #selector(onAddMaps))
navigationItem.rightBarButtonItem = addMapsButton
}
noMapsContainer.isHidden = !dataSource.isEmpty || Storage.shared().downloadInProgress()
extendedLayoutIncludesOpaqueBars = true
searchController.searchBar.placeholder = L("downloader_search_field_hint")
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = alternativeSizeClass(iPhone: true, iPad: false)
searchController.searchBar.applyTheme()
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
configButtons()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
dataSource.reload {
reloadData()
noMapsContainer.isHidden = !dataSource.isEmpty || Storage.shared().downloadInProgress()
}
Storage.shared().add(self)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
Storage.shared().remove(self)
}
fileprivate func showChildren(_ nodeAttrs: MapNodeAttributes) {
let vc = storyboard!.instantiateViewController(ofType: DownloadMapsViewController.self)
vc.mode = dataSource.isSearching ? .available : mode
vc.dataSource = dataSource.dataSourceFor(nodeAttrs.countryId)
navigationController?.pushViewController(vc, animated: true)
}
fileprivate func showActions(_ nodeAttrs: MapNodeAttributes, in cell: UITableViewCell) {
let menuTitle = nodeAttrs.nodeName
let multiparent = nodeAttrs.parentInfo.count > 1
let message = dataSource.isRoot || multiparent ? nil : nodeAttrs.parentInfo.first?.countryName
let actionSheet = UIAlertController(title: menuTitle, message: message, preferredStyle: .actionSheet)
actionSheet.popoverPresentationController?.sourceView = cell
actionSheet.popoverPresentationController?.sourceRect = cell.bounds
let actions: [NodeAction]
switch nodeAttrs.nodeStatus {
case .undefined:
actions = []
case .downloading, .applying, .inQueue:
actions = [.cancelDownload]
case .error:
actions = nodeAttrs.downloadedMwmCount > 0 ? [.retryDownload, .delete] : [.retryDownload]
case .onDiskOutOfDate:
actions = [.showOnMap, .update, .delete]
case .onDisk:
actions = [.showOnMap, .delete]
case .notDownloaded:
actions = [.download]
case .partly:
actions = [.download, .delete]
@unknown default:
fatalError()
}
addActions(actions, for: nodeAttrs, to: actionSheet)
actionSheet.addAction(UIAlertAction(title: L("cancel"), style: .cancel))
present(actionSheet, animated: true)
}
private func addActions(_ actions: [NodeAction], for nodeAttrs: MapNodeAttributes, to actionSheet: UIAlertController) {
actions.forEach { [unowned self] in
let action: UIAlertAction
switch $0 {
case .showOnMap:
action = UIAlertAction(title: L("zoom_to_country"), style: .default, handler: { _ in
Storage.shared().showNode(nodeAttrs.countryId)
self.navigationController?.popToRootViewController(animated: true)
})
case .download:
let prefix = nodeAttrs.totalMwmCount == 1 ? L("downloader_download_map") : L("downloader_download_all_button")
action = UIAlertAction(title: "\(prefix) (\(formattedSize(nodeAttrs.totalSize)))",
style: .default,
handler: { _ in
Storage.shared().downloadNode(nodeAttrs.countryId)
})
case .update:
let size = formattedSize(nodeAttrs.totalUpdateSizeBytes)
let title = "\(L("downloader_status_outdated")) \(size)"
action = UIAlertAction(title: title, style: .default, handler: { _ in
Storage.shared().updateNode(nodeAttrs.countryId)
})
case .cancelDownload:
action = UIAlertAction(title: L("cancel_download"), style: .destructive, handler: { _ in
Storage.shared().cancelDownloadNode(nodeAttrs.countryId)
})
case .retryDownload:
action = UIAlertAction(title: L("downloader_retry"), style: .destructive, handler: { _ in
Storage.shared().retryDownloadNode(nodeAttrs.countryId)
})
case .delete:
action = UIAlertAction(title: L("downloader_delete_map"), style: .destructive, handler: { _ in
Storage.shared().deleteNode(nodeAttrs.countryId)
})
}
actionSheet.addAction(action)
}
}
fileprivate func reloadData() {
tableView.reloadData()
configButtons()
}
fileprivate func configButtons() {
downloadAllView.state = .none
downloadAllView.isSizeHidden = false
let parentAttributes = dataSource.parentAttributes()
let error = parentAttributes.nodeStatus == .error || parentAttributes.nodeStatus == .undefined
let downloading = parentAttributes.nodeStatus == .downloading || parentAttributes.nodeStatus == .inQueue || parentAttributes.nodeStatus == .applying
switch mode {
case .available:
if dataSource.isRoot {
break
}
if error {
downloadAllView.state = .error
} else if downloading {
downloadAllView.state = .dowloading
} else if parentAttributes.downloadedMwmCount < parentAttributes.totalMwmCount {
downloadAllView.state = .ready
downloadAllView.style = .download
downloadAllView.downloadSize = parentAttributes.totalSize - parentAttributes.downloadedSize
}
case .downloaded:
let isUpdate = parentAttributes.totalUpdateSizeBytes > 0
let size = isUpdate ? parentAttributes.totalUpdateSizeBytes : parentAttributes.downloadingSize
if error {
downloadAllView.state = dataSource.isRoot ? .none : .error
downloadAllView.downloadSize = parentAttributes.downloadingSize
} else if downloading && dataSource is DownloadedMapsDataSource {
downloadAllView.state = .dowloading
if dataSource.isRoot {
downloadAllView.style = .download
downloadAllView.isSizeHidden = true
}
} else if isUpdate {
downloadAllView.state = .ready
downloadAllView.style = .update
downloadAllView.downloadSize = size
}
@unknown default:
fatalError()
}
}
@objc func onAddMaps() {
let vc = storyboard!.instantiateViewController(ofType: DownloadMapsViewController.self)
if !dataSource.isRoot {
vc.dataSource = AvailableMapsDataSource(dataSource.getParentCountryId())
}
vc.mode = .available
navigationController?.pushViewController(vc, animated: true)
}
}
// MARK: - UITableViewDataSource
extension DownloadMapsViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
dataSource.numberOfSections() + (hasAddMapSection ? 1 : 0)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if hasAddMapSection && section == dataSource.numberOfSections() {
return 1
}
return dataSource.numberOfItems(in: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if hasAddMapSection && indexPath.section == dataSource.numberOfSections() {
let cellType = MWMMapDownloaderButtonTableViewCell.self
let buttonCell = tableView.dequeueReusableCell(cell: cellType, indexPath: indexPath)
return buttonCell
}
let nodeAttrs = dataSource.item(at: indexPath)
let cell: MWMMapDownloaderTableViewCell
if nodeAttrs.hasChildren {
let cellType = MWMMapDownloaderLargeCountryTableViewCell.self
let largeCountryCell = tableView.dequeueReusableCell(cell: cellType, indexPath: indexPath)
cell = largeCountryCell
} else if let matchedName = dataSource.matchedName(at: indexPath), matchedName != nodeAttrs.nodeName {
let cellType = MWMMapDownloaderSubplaceTableViewCell.self
let subplaceCell = tableView.dequeueReusableCell(cell: cellType, indexPath: indexPath)
subplaceCell.setSubplaceText(matchedName)
cell = subplaceCell
} else if !nodeAttrs.hasParent {
let cellType = MWMMapDownloaderTableViewCell.self
let downloaderCell = tableView.dequeueReusableCell(cell: cellType, indexPath: indexPath)
cell = downloaderCell
} else {
let cellType = MWMMapDownloaderPlaceTableViewCell.self
let placeCell = tableView.dequeueReusableCell(cell: cellType, indexPath: indexPath)
cell = placeCell
}
cell.mode = dataSource.isSearching ? .available : mode
cell.config(nodeAttrs, searchQuery: searchController.searchBar.text)
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
dataSource.title(for: section)
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
dataSource.indexTitles()
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
index
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == dataSource.numberOfSections() {
return false
}
let nodeAttrs = dataSource.item(at: indexPath)
switch nodeAttrs.nodeStatus {
case .onDisk, .onDiskOutOfDate, .partly:
return true
default:
return false
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let nodeAttrs = dataSource.item(at: indexPath)
Storage.shared().deleteNode(nodeAttrs.countryId)
}
}
}
// MARK: - UITableViewDelegate
extension DownloadMapsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = MWMMapDownloaderCellHeader()
if section != dataSource.numberOfSections() {
headerView.text = dataSource.title(for: section)
}
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
28
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
section == dataSource.numberOfSections() - 1 ? 68 : 0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.section == dataSource.numberOfSections() {
onAddMaps()
return
}
let nodeAttrs = dataSource.item(at: indexPath)
if nodeAttrs.hasChildren {
showChildren(dataSource.item(at: indexPath))
return
}
showActions(nodeAttrs, in: tableView.cellForRow(at: indexPath)!)
}
}
// MARK: - UIScrollViewDelegate
extension DownloadMapsViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchController.searchBar.resignFirstResponder()
}
}
// MARK: - MWMMapDownloaderTableViewCellDelegate
extension DownloadMapsViewController: MWMMapDownloaderTableViewCellDelegate {
func mapDownloaderCellDidPressProgress(_ cell: MWMMapDownloaderTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let nodeAttrs = dataSource.item(at: indexPath)
switch nodeAttrs.nodeStatus {
case .undefined, .error:
Storage.shared().retryDownloadNode(nodeAttrs.countryId)
case .downloading, .applying, .inQueue:
Storage.shared().cancelDownloadNode(nodeAttrs.countryId)
case .onDiskOutOfDate:
Storage.shared().updateNode(nodeAttrs.countryId)
case .onDisk:
// do nothing
break
case .notDownloaded, .partly:
if nodeAttrs.hasChildren {
showChildren(nodeAttrs)
} else {
Storage.shared().downloadNode(nodeAttrs.countryId)
}
@unknown default:
fatalError()
}
}
func mapDownloaderCellDidLongPress(_ cell: MWMMapDownloaderTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let nodeAttrs = dataSource.item(at: indexPath)
showActions(nodeAttrs, in: cell)
}
}
// MARK: - StorageObserver
extension DownloadMapsViewController: StorageObserver {
func processCountryEvent(_ countryId: String) {
if skipCountryEvent && countryId == dataSource.getParentCountryId() {
return
}
dataSource.reload {
reloadData()
noMapsContainer.isHidden = !dataSource.isEmpty || Storage.shared().downloadInProgress()
}
if countryId == dataSource.getParentCountryId() {
configButtons()
}
for cell in tableView.visibleCells {
guard let downloaderCell = cell as? MWMMapDownloaderTableViewCell else { continue }
if downloaderCell.nodeAttrs.countryId != countryId { continue }
guard let indexPath = tableView.indexPath(for: downloaderCell) else { return }
downloaderCell.config(dataSource.item(at: indexPath), searchQuery: searchController.searchBar.text)
}
}
func processCountry(_ countryId: String, downloadedBytes: UInt64, totalBytes: UInt64) {
for cell in tableView.visibleCells {
guard let downloaderCell = cell as? MWMMapDownloaderTableViewCell else { continue }
if downloaderCell.nodeAttrs.countryId != countryId { continue }
downloaderCell.setDownloadProgress(CGFloat(downloadedBytes) / CGFloat(totalBytes))
}
if countryId == dataSource.getParentCountryId() {
downloadAllView.downloadProgress = CGFloat(downloadedBytes) / CGFloat(totalBytes)
downloadAllView.downloadSize = totalBytes
} else if dataSource.isRoot && dataSource is DownloadedMapsDataSource {
downloadAllView.state = .dowloading
downloadAllView.isSizeHidden = true
}
}
}
// MARK: - UISearchBarDelegate
extension DownloadMapsViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = nil
searchBar.resignFirstResponder()
dataSource.cancelSearch()
reloadData()
noSerchResultViewController.view.isHidden = true
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let locale = searchBar.textInputMode?.primaryLanguage
dataSource.search(searchText, locale: locale ?? "") { [weak self] finished in
guard let self = self else { return }
self.reloadData()
self.noSerchResultViewController.view.isHidden = !self.dataSource.isEmpty
}
}
}
// MARK: - DownloadAllViewDelegate
extension DownloadMapsViewController: DownloadAllViewDelegate {
func onStateChanged(state: DownloadAllView.State) {
if state == .none {
downloadAllViewContainer.isHidden = true
tableView.contentInset = UIEdgeInsets.zero
} else {
downloadAllViewContainer.isHidden = false
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: allMapsViewBottomOffsetConstant, right: 0)
}
}
func onDownloadButtonPressed() {
skipCountryEvent = true
let id = dataSource.getParentCountryId()
if mode == .downloaded {
Storage.shared().updateNode(id)
} else {
Storage.shared().downloadNode(id)
}
skipCountryEvent = false
processCountryEvent(id)
}
func onRetryButtonPressed() {
skipCountryEvent = true
let id = dataSource.getParentCountryId()
Storage.shared().retryDownloadNode(id)
skipCountryEvent = false
processCountryEvent(id)
}
func onCancelButtonPressed() {
skipCountryEvent = true
let id = dataSource.getParentCountryId()
Storage.shared().cancelDownloadNode(id)
skipCountryEvent = false
processCountryEvent(id)
reloadData()
}
}

View file

@ -0,0 +1,139 @@
class DownloadedMapsDataSource {
private let parentCountryId: String?
private var countryIds: [String]
fileprivate var searching = false
fileprivate lazy var searchDataSource: IDownloaderDataSource = {
SearchMapsDataSource()
}()
init(_ parentId: String? = nil) {
self.parentCountryId = parentId
countryIds = DownloadedMapsDataSource.loadData(parentId)
}
private class func loadData(_ parentId: String?) -> [String] {
let countryIds: [String]
if let parentId = parentId {
countryIds = Storage.shared().downloadedCountries(withParent: parentId)
} else {
countryIds = Storage.shared().downloadedCountries()
}
return countryIds.map {
CountryIdAndName(countryId: $0, name: Storage.shared().name(forCountry: $0))
}.sorted {
$0.countryName.compare($1.countryName) == .orderedAscending
}.map {
$0.countryId
}
}
fileprivate func reloadData() {
countryIds = DownloadedMapsDataSource.loadData(parentCountryId)
}
}
extension DownloadedMapsDataSource: IDownloaderDataSource {
var isEmpty: Bool {
return searching ? searchDataSource.isEmpty : countryIds.isEmpty
}
var title: String {
guard let parentCountryId = parentCountryId else {
return L("download_maps")
}
return Storage.shared().name(forCountry: parentCountryId)
}
var isRoot: Bool {
parentCountryId == nil
}
var isSearching: Bool {
searching
}
func getParentCountryId() -> String {
if parentCountryId != nil {
return parentCountryId!
}
return Storage.shared().getRootId()
}
func parentAttributes() -> MapNodeAttributes {
guard let parentId = parentCountryId else {
return Storage.shared().attributesForRoot()
}
return Storage.shared().attributes(forCountry: parentId)
}
func numberOfSections() -> Int {
searching ? searchDataSource.numberOfSections() : 1
}
func numberOfItems(in section: Int) -> Int {
searching ? searchDataSource.numberOfItems(in: section) : countryIds.count
}
func item(at indexPath: IndexPath) -> MapNodeAttributes {
if searching {
return searchDataSource.item(at: indexPath)
}
guard indexPath.section == 0 else { fatalError() }
let countryId = countryIds[indexPath.item]
return Storage.shared().attributes(forCountry: countryId)
}
func matchedName(at indexPath: IndexPath) -> String? {
searching ? searchDataSource.matchedName(at: indexPath) : nil
}
func title(for section: Int) -> String {
if searching {
return searchDataSource.title(for: section)
}
if let parentCountryId = parentCountryId {
let attributes = Storage.shared().attributes(forCountry: parentCountryId)
return Storage.shared().name(forCountry: parentCountryId) + " (\(formattedSize(attributes.downloadedSize)))"
}
let attributes = Storage.shared().attributesForRoot()
return L("downloader_downloaded_subtitle") + " (\(formattedSize(attributes.downloadedSize)))"
}
func indexTitles() -> [String]? {
nil
}
func dataSourceFor(_ childId: String) -> IDownloaderDataSource {
searching ? searchDataSource.dataSourceFor(childId) : DownloadedMapsDataSource(childId)
}
func reload(_ completion: () -> Void) {
if searching {
searchDataSource.reload(completion)
return
}
reloadData()
completion()
}
func search(_ query: String, locale: String, update: @escaping (Bool) -> Void) {
if query.isEmpty {
cancelSearch()
update(true)
return
}
searchDataSource.search(query, locale: locale) { [weak self] (finished) in
if finished {
self?.searching = true
update(finished)
}
}
}
func cancelSearch() {
searching = false
searchDataSource.cancelSearch()
}
}

View file

@ -0,0 +1,18 @@
protocol IDownloaderDataSource {
var isEmpty: Bool { get }
var title: String { get }
var isRoot: Bool { get }
var isSearching: Bool { get }
func getParentCountryId() -> String
func parentAttributes() -> MapNodeAttributes
func numberOfSections() -> Int
func numberOfItems(in section: Int) -> Int
func item(at indexPath: IndexPath) -> MapNodeAttributes
func matchedName(at indexPath: IndexPath) -> String?
func title(for section: Int) -> String
func indexTitles() -> [String]?
func dataSourceFor(_ childId: String) -> IDownloaderDataSource
func reload(_ completion: () -> Void)
func search(_ query: String, locale: String, update: @escaping (_ completed: Bool) -> Void)
func cancelSearch()
}

View file

@ -0,0 +1,4 @@
typedef NS_ENUM(NSUInteger, MWMMapDownloaderMode) {
MWMMapDownloaderModeDownloaded,
MWMMapDownloaderModeAvailable
};

View file

@ -0,0 +1,22 @@
@objc(MWMDownloaderNoResultsEmbedViewController)
final class DownloaderNoResultsEmbed: UINavigationController {
@objc(MWMDownloaderNoResultsScreen)
enum Screen: Int {
case noMaps
case noSearchResults
}
@objc var screen = Screen.noMaps {
didSet {
let controller: MWMViewController
switch screen {
case .noMaps: controller = MWMNoMapsViewController.controller()
case .noSearchResults: controller = SearchNoResultsViewController.controller
}
setViewControllers([controller], animated: false)
}
}
}

View file

@ -0,0 +1,3 @@
@interface MWMNoMapsView : SolidTouchView
@end

View file

@ -0,0 +1,81 @@
#import "MWMNoMapsView.h"
#import "MWMKeyboard.h"
@interface MWMNoMapsView ()<MWMKeyboardObserver>
@property(weak, nonatomic) IBOutlet UIImageView * image;
@property(weak, nonatomic) IBOutlet UILabel * title;
@property(weak, nonatomic) IBOutlet UILabel * text;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * containerWidth;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * containerHeight;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * containerTopOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * containerBottomOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * imageMinHeight;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * imageHeight;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * titleImageOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * titleTopOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * textTopOffset;
@end
@implementation MWMNoMapsView
- (void)awakeFromNib
{
[super awakeFromNib];
if (!IPAD)
{
self.containerWidth.active = NO;
self.containerHeight.active = NO;
}
else
{
self.containerTopOffset.active = NO;
}
[MWMKeyboard addObserver:self];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self configForSize:self.frame.size];
}
- (void)configForSize:(CGSize)size
{
CGSize iPadSize = CGSizeMake(520, 600);
CGSize newSize = IPAD ? iPadSize : size;
CGFloat width = newSize.width;
CGFloat height = newSize.height;
BOOL hideImage = (self.imageHeight.multiplier * height <= self.imageMinHeight.constant);
if (hideImage)
{
self.titleImageOffset.priority = UILayoutPriorityDefaultLow;
self.title.hidden = self.title.minY < self.titleTopOffset.constant;
self.text.hidden = self.text.minY < self.textTopOffset.constant;
}
else
{
self.titleImageOffset.priority = UILayoutPriorityDefaultHigh;
self.title.hidden = NO;
self.text.hidden = NO;
}
self.image.hidden = hideImage;
if (IPAD)
{
self.containerWidth.constant = width;
self.containerHeight.constant = height;
}
}
#pragma mark - MWMKeyboard
- (void)onKeyboardAnimation
{
self.containerBottomOffset.constant = [MWMKeyboard keyboardHeight];
[self.superview layoutIfNeeded];
}
- (void)onKeyboardWillAnimate { [self.superview layoutIfNeeded]; }
@end

View file

@ -0,0 +1,7 @@
#import "MWMViewController.h"
@interface MWMNoMapsViewController : MWMViewController
+ (MWMNoMapsViewController *)controller NS_SWIFT_NAME(controller());
@end

View file

@ -0,0 +1,19 @@
#import "MWMNoMapsViewController.h"
#import "MWMMapDownloaderMode.h"
#import "MWMMapViewControlsManager.h"
#import "SwiftBridge.h"
@implementation MWMNoMapsViewController
+ (MWMNoMapsViewController *)controller
{
auto storyboard = [UIStoryboard instance:MWMStoryboardMain];
return [storyboard instantiateViewControllerWithIdentifier:[self className]];
}
- (IBAction)downloadMaps
{
[[MWMMapViewControlsManager manager] actionDownloadMaps:MWMMapDownloaderModeAvailable];
}
@end

View file

@ -0,0 +1,84 @@
class SearchMapsDataSource {
typealias SearchCallback = (Bool) -> Void
fileprivate var searchResults: [MapSearchResult] = []
fileprivate var searchId = 0
fileprivate var onUpdate: SearchCallback?
}
extension SearchMapsDataSource: IDownloaderDataSource {
var isEmpty: Bool {
searchResults.isEmpty
}
var title: String {
""
}
var isRoot: Bool {
true
}
var isSearching: Bool {
true
}
func numberOfSections() -> Int {
1
}
func getParentCountryId() -> String {
return Storage.shared().getRootId()
}
func parentAttributes() -> MapNodeAttributes {
return Storage.shared().attributesForRoot()
}
func numberOfItems(in section: Int) -> Int {
searchResults.count
}
func item(at indexPath: IndexPath) -> MapNodeAttributes {
Storage.shared().attributes(forCountry: searchResults[indexPath.item].countryId)
}
func matchedName(at indexPath: IndexPath) -> String? {
searchResults[indexPath.item].matchedName
}
func title(for section: Int) -> String {
L("downloader_search_results")
}
func indexTitles() -> [String]? {
nil
}
func dataSourceFor(_ childId: String) -> IDownloaderDataSource {
AvailableMapsDataSource(childId)
}
func reload(_ completion: () -> Void) {
completion()
}
func search(_ query: String, locale: String, update: @escaping SearchCallback) {
searchId += 1
onUpdate = update
FrameworkHelper.search(inDownloader: query, inputLocale: locale) { [weak self, searchId] (results, finished) in
if searchId != self?.searchId {
return
}
self?.searchResults = results
if results.count > 0 || finished {
self?.onUpdate?(finished)
}
}
}
func cancelSearch() {
searchResults = []
onUpdate = nil
}
}

View file

@ -0,0 +1,50 @@
import UIKit
protocol BookmarkColorViewControllerDelegate: AnyObject {
func bookmarkColorViewController(_ viewController: BookmarkColorViewController, didSelect color: BookmarkColor)
}
final class BookmarkColorViewController: MWMTableViewController {
weak var delegate: BookmarkColorViewControllerDelegate?
private let bookmarkColor: BookmarkColor
private let colors: [BookmarkColor] = BookmarkColor.allCases
init(bookmarkColor: BookmarkColor) {
self.bookmarkColor = bookmarkColor
super.init(style: .grouped)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = L("change_color")
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonDidTap))
}
@objc private func cancelButtonDidTap() {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
colors.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueDefaultCell(for: indexPath)
let color = colors[indexPath.row]
let selected = color == bookmarkColor
cell.textLabel?.text = color.title
cell.imageView?.image = color.image(selected)
cell.accessoryType = selected ? .checkmark : .none
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let color = colors[indexPath.row]
delegate?.bookmarkColorViewController(self, didSelect: color)
}
}

View file

@ -0,0 +1,22 @@
import UIKit
protocol BookmarkTitleCellDelegate: AnyObject {
func didFinishEditingTitle(_ title: String)
}
final class BookmarkTitleCell: MWMTableViewCell {
@IBOutlet var textField: UITextField!
weak var delegate: BookmarkTitleCellDelegate?
func configure(name: String, delegate: BookmarkTitleCellDelegate, hint: String) {
textField.text = name
textField.placeholder = hint
self.delegate = delegate
}
}
extension BookmarkTitleCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
delegate?.didFinishEditingTitle(textField.text ?? "")
}
}

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="oz5-CE-QZ8" customClass="BookmarkTitleCell" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="oz5-CE-QZ8" id="v5X-p8-TtT">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="Ykx-ep-qjo">
<rect key="frame" x="20" y="12" width="280" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedPlaceholder" value="placepage_bookmark_name_hint"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="delegate" destination="oz5-CE-QZ8" id="d6l-ER-j3P"/>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Ykx-ep-qjo" secondAttribute="bottom" constant="12" id="cfm-oE-z2I"/>
<constraint firstAttribute="trailing" secondItem="Ykx-ep-qjo" secondAttribute="trailing" constant="20" id="dyn-Ae-apB"/>
<constraint firstItem="Ykx-ep-qjo" firstAttribute="top" secondItem="v5X-p8-TtT" secondAttribute="top" constant="12" id="e37-GE-Eth"/>
<constraint firstItem="Ykx-ep-qjo" firstAttribute="leading" secondItem="v5X-p8-TtT" secondAttribute="leading" constant="20" id="gXL-r9-dr7"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="textField" destination="Ykx-ep-qjo" id="Vsa-Aq-FNB"/>
</connections>
<point key="canvasLocation" x="234" y="339"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,104 @@
import Foundation
extension BookmarkColor {
public static var allCases: [BookmarkColor] = [.red, .pink, .purple, .deepPurple, .blue, .lightBlue, .cyan, .teal, .green,
.lime, .yellow, .orange, .deepOrange, .brown, .gray, .blueGray]
var title: String {
localizedTitleForBookmarkColor(self)
}
var color: UIColor {
uiColorForBookmarkColor(self)
}
func image(_ selected: Bool) -> UIImage {
if selected {
return circleImageForColor(color, frameSize: 22, iconName: "ic_bm_none")
} else {
return circleImageForColor(color, frameSize: 22, diameter: 14)
}
}
func image(_ iconName: String) -> UIImage {
circleImageForColor(color, frameSize: 22, iconName: iconName)
}
static func bookmarkColor(from color: UIColor) -> BookmarkColor? {
allCases.first(where: { uiColorForBookmarkColor($0) == color })
}
}
fileprivate func titleForBookmarkColor(_ color: BookmarkColor) -> String {
switch color {
case .red: return "red"
case .blue: return "blue"
case .purple: return "purple"
case .yellow: return "yellow"
case .pink: return "pink"
case .brown: return "brown"
case .green: return "green"
case .orange: return "orange"
case .deepPurple: return "deep_purple"
case .lightBlue: return "light_blue"
case .cyan: return "cyan"
case .teal: return "teal"
case .lime: return "lime"
case .deepOrange: return "deep_orange"
case .gray: return "gray"
case .blueGray: return "blue_gray"
case .none, .count: return ""
@unknown default:
fatalError()
}
}
fileprivate func localizedTitleForBookmarkColor(_ color: BookmarkColor) -> String {
L(titleForBookmarkColor(color))
}
fileprivate func rgbColor(_ r: CGFloat, _ g: CGFloat, _ b: CGFloat) -> UIColor {
UIColor(red: r / 255, green: g / 255, blue: b / 255, alpha: 0.8)
}
fileprivate func uiColorForBookmarkColor(_ color: BookmarkColor) -> UIColor {
switch color {
case .red: return rgbColor(229, 27, 35);
case .pink: return rgbColor(255, 65, 130);
case .purple: return rgbColor(155, 36, 178);
case .deepPurple: return rgbColor(102, 57, 191);
case .blue: return rgbColor(0, 102, 204);
case .lightBlue: return rgbColor(36, 156, 242);
case .cyan: return rgbColor(20, 190, 205);
case .teal: return rgbColor(0, 165, 140);
case .green: return rgbColor(60, 140, 60);
case .lime: return rgbColor(147, 191, 57);
case .yellow: return rgbColor(255, 200, 0);
case .orange: return rgbColor(255, 150, 0);
case .deepOrange: return rgbColor(240, 100, 50);
case .brown: return rgbColor(128, 70, 51);
case .gray: return rgbColor(115, 115, 115);
case .blueGray: return rgbColor(89, 115, 128);
case .none, .count:
fatalError()
@unknown default:
fatalError()
}
}
func circleImageForColor(_ color: UIColor,
frameSize: CGFloat,
diameter: CGFloat? = nil,
iconName: String? = nil) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: frameSize, height: frameSize))
return renderer.image { context in
let d = diameter ?? frameSize
let rect = CGRect(x: (frameSize - d) / 2, y: (frameSize - d) / 2, width: d, height: d)
context.cgContext.addEllipse(in: rect)
context.cgContext.setFillColor(color.cgColor)
context.cgContext.fillPath()
guard let iconName = iconName, let image = UIImage(named: iconName) else { return }
image.draw(in: rect.insetBy(dx: 3, dy: 3))
}
}

View file

@ -0,0 +1,265 @@
import UIKit
@objc(MWMEditBookmarkController)
final class EditBookmarkViewController: MWMTableViewController {
private enum Sections: Int {
case info
case description
case delete
case count
}
private enum InfoSectionRows: Int {
case title
case color
case bookmarkGroup
case count
}
private var editingCompleted: ((Bool) -> Void)?
private var placePageData: PlacePageData?
private var noteCell: MWMNoteCell?
private var bookmarkTitle: String?
private var bookmarkDescription: String?
private var bookmarkGroupTitle: String?
private var bookmarkId = FrameworkHelper.invalidBookmarkId()
private var bookmarkGroupId = FrameworkHelper.invalidCategoryId()
private var newBookmarkGroupId = FrameworkHelper.invalidCategoryId()
private var bookmarkColor: BookmarkColor!
private let bookmarksManager = BookmarksManager.shared()
override func viewDidLoad() {
super.viewDidLoad()
guard bookmarkId != FrameworkHelper.invalidBookmarkId() || placePageData != nil else {
fatalError("controller should be configured with placePageData or bookmarkId first")
}
title = L("bookmark").capitalized
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save,
target: self,
action: #selector(onSave))
tableView.registerNib(cell: BookmarkTitleCell.self)
tableView.registerNib(cell: MWMButtonCell.self)
tableView.registerNib(cell: MWMNoteCell.self)
addToBookmarksManagerObserverList()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateBookmarkIfNeeded()
}
deinit {
removeFromBookmarksManagerObserverList()
}
func configure(with bookmarkId: MWMMarkID, editCompletion completion: ((Bool) -> Void)?) {
self.bookmarkId = bookmarkId
let bookmark = bookmarksManager.bookmark(withId: bookmarkId)
bookmarkTitle = bookmark.bookmarkName
bookmarkColor = bookmark.bookmarkColor
bookmarkDescription = bookmarksManager.description(forBookmarkId: bookmarkId)
let bookmarkGroup = bookmarksManager.category(forBookmarkId: bookmarkId)
bookmarkGroupId = bookmarkGroup.categoryId
bookmarkGroupTitle = bookmarkGroup.title
editingCompleted = completion
}
@objc(configureWithPlacePageData:)
func configure(with placePageData: PlacePageData) {
guard let bookmarkData = placePageData.bookmarkData else { fatalError("placePageData and bookmarkData can't be nil") }
self.placePageData = placePageData
bookmarkTitle = placePageData.previewData.title
bookmarkDescription = bookmarkData.bookmarkDescription
bookmarkGroupTitle = bookmarkData.bookmarkCategory
bookmarkId = bookmarkData.bookmarkId
bookmarkGroupId = bookmarkData.bookmarkGroupId
bookmarkColor = bookmarkData.color
editingCompleted = nil
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
Sections.count.rawValue
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Sections(rawValue: section) {
case .info:
return InfoSectionRows.count.rawValue
case .description, .delete:
return 1
default:
fatalError()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch Sections(rawValue: indexPath.section) {
case .info:
switch InfoSectionRows(rawValue: indexPath.row) {
case .title:
let cell = tableView.dequeueReusableCell(cell: BookmarkTitleCell.self, indexPath: indexPath)
cell.configure(name: bookmarkTitle ?? "", delegate: self, hint: L("placepage_bookmark_name_hint"))
return cell
case .color:
let cell = tableView.dequeueDefaultCell(for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = bookmarkColor.title
cell.imageView?.image = circleImageForColor(bookmarkColor.color, frameSize: 28, diameter: 22, iconName: "ic_bm_none")
return cell
case .bookmarkGroup:
let cell = tableView.dequeueDefaultCell(for: indexPath)
cell.textLabel?.text = bookmarkGroupTitle
cell.imageView?.image = UIImage(named: "ic_folder")
cell.imageView?.setStyle(.black)
cell.accessoryType = .disclosureIndicator
return cell;
default:
fatalError()
}
case .description:
if let noteCell = noteCell {
return noteCell
} else {
let cell = tableView.dequeueReusableCell(cell: MWMNoteCell.self, indexPath: indexPath)
cell.config(with: self, noteText: bookmarkDescription ?? "", placeholder: L("placepage_personal_notes_hint"))
noteCell = cell
return cell
}
case .delete:
let cell = tableView.dequeueReusableCell(cell: MWMButtonCell.self, indexPath: indexPath)
cell.configure(with: self, title: L("placepage_delete_bookmark_button"), enabled: true)
return cell
default:
fatalError()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch InfoSectionRows(rawValue: indexPath.row) {
case .color:
openColorPicker()
case .bookmarkGroup:
openGroupPicker()
default:
break
}
}
// MARK: - Private
private func updateBookmarkIfNeeded() {
// Skip for the regular place page.
guard bookmarkId != FrameworkHelper.invalidBookmarkId() else { return }
// TODO: Update the bookmark content on the Edit screen instead of closing it when the bookmark gets updated from cloud.
if !bookmarksManager.hasBookmark(bookmarkId) {
goBack()
}
}
private func addToBookmarksManagerObserverList() {
bookmarksManager.add(self)
}
private func removeFromBookmarksManagerObserverList() {
bookmarksManager.remove(self)
}
@objc private func onSave() {
view.endEditing(true)
BookmarksManager.shared().updateBookmark(bookmarkId,
setGroupId: bookmarkGroupId,
title: bookmarkTitle ?? "",
color: bookmarkColor,
description: bookmarkDescription ?? "")
if let placePageData = placePageData {
FrameworkHelper.updatePlacePageData()
placePageData.updateBookmarkStatus()
}
editingCompleted?(true)
goBack()
}
@objc private func openColorPicker() {
ColorPicker.shared.present(from: self, pickerType: .bookmarkColorPicker(bookmarkColor), completionHandler: { [weak self] color in
self?.bookmarkColor = BookmarkColor.bookmarkColor(from: color)
self?.tableView.reloadRows(at: [IndexPath(row: InfoSectionRows.color.rawValue, section: Sections.info.rawValue)], with: .none)
})
}
private func openGroupPicker() {
let groupViewController = SelectBookmarkGroupViewController(groupName: bookmarkGroupTitle ?? "", groupId: bookmarkGroupId)
let navigationController = UINavigationController(rootViewController: groupViewController)
groupViewController.delegate = self
present(navigationController, animated: true, completion: nil)
}
}
extension EditBookmarkViewController: BookmarkTitleCellDelegate {
func didFinishEditingTitle(_ title: String) {
bookmarkTitle = title
}
}
extension EditBookmarkViewController: MWMNoteCellDelegate {
func cell(_ cell: MWMNoteCell, didChangeSizeAndText text: String) {
UIView.setAnimationsEnabled(false)
tableView.refresh()
UIView.setAnimationsEnabled(true)
}
func cell(_ cell: MWMNoteCell, didFinishEditingWithText text: String) {
bookmarkDescription = text
}
}
extension EditBookmarkViewController: MWMButtonCellDelegate {
func cellDidPressButton(_ cell: UITableViewCell) {
BookmarksManager.shared().deleteBookmark(bookmarkId)
if let placePageData = placePageData {
FrameworkHelper.updateAfterDeleteBookmark()
placePageData.updateBookmarkStatus()
}
goBack()
}
}
extension EditBookmarkViewController: SelectBookmarkGroupViewControllerDelegate {
func bookmarkGroupViewController(_ viewController: SelectBookmarkGroupViewController,
didSelect groupTitle: String,
groupId: MWMMarkGroupID) {
viewController.dismiss(animated: true)
bookmarkGroupTitle = groupTitle
bookmarkGroupId = groupId
tableView.reloadRows(at: [IndexPath(row: InfoSectionRows.bookmarkGroup.rawValue, section: Sections.info.rawValue)], with: .none)
}
}
// MARK: - BookmarksObserver
extension EditBookmarkViewController: BookmarksObserver {
func onBookmarksLoadFinished() {
updateBookmarkIfNeeded()
}
func onBookmarksCategoryDeleted(_ groupId: MWMMarkGroupID) {
if bookmarkGroupId == groupId {
goBack()
}
}
}

View file

@ -0,0 +1,236 @@
import UIKit
final class EditTrackViewController: MWMTableViewController {
private enum Sections: Int {
case info
case delete
case count
}
private enum InfoSectionRows: Int {
case title
case color
//case lineWidth // TODO: possible new section & ability - edit track line width
case bookmarkGroup
case count
}
private var editingCompleted: (Bool) -> Void
private var placePageData: PlacePageData?
private let trackId: MWMTrackID
private var trackTitle: String?
private var trackGroupTitle: String?
private var trackGroupId = FrameworkHelper.invalidCategoryId()
private var trackColor: UIColor
private let bookmarksManager = BookmarksManager.shared()
@objc
init(trackId: MWMTrackID, editCompletion completion: @escaping (Bool) -> Void) {
self.trackId = trackId
let track = bookmarksManager.track(withId: trackId)
self.trackTitle = track.trackName
self.trackColor = track.trackColor
let category = bookmarksManager.category(forTrackId: trackId)
self.trackGroupId = category.categoryId
self.trackGroupTitle = category.title
self.editingCompleted = completion
super.init(style: .grouped)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateTrackIfNeeded()
}
deinit {
removeFromBookmarksManagerObserverList()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = L("track_title")
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save,
target: self,
action: #selector(onSave))
tableView.registerNib(cell: BookmarkTitleCell.self)
tableView.registerNib(cell: MWMButtonCell.self)
addToBookmarksManagerObserverList()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
Sections.count.rawValue
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Sections(rawValue: section) {
case .info:
return InfoSectionRows.count.rawValue
case .delete:
return 1
default:
fatalError()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch Sections(rawValue: indexPath.section) {
case .info:
switch InfoSectionRows(rawValue: indexPath.row) {
case .title:
let cell = tableView.dequeueReusableCell(cell: BookmarkTitleCell.self, indexPath: indexPath)
cell.configure(name: trackTitle ?? "", delegate: self, hint: L("placepage_track_name_hint"))
return cell
case .color:
let cell = tableView.dequeueDefaultCell(for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = L("change_color")
cell.imageView?.image = circleImageForColor(trackColor, frameSize: 28, diameter: 22)
return cell
case .bookmarkGroup:
let cell = tableView.dequeueDefaultCell(for: indexPath)
cell.textLabel?.text = trackGroupTitle
cell.imageView?.image = UIImage(named: "ic_folder")
cell.imageView?.setStyle(.black)
cell.accessoryType = .disclosureIndicator
return cell;
default:
fatalError()
}
case .delete:
let cell = tableView.dequeueReusableCell(cell: MWMButtonCell.self, indexPath: indexPath)
cell.configure(with: self, title: L("placepage_delete_track_button"), enabled: true)
return cell
default:
fatalError()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch InfoSectionRows(rawValue: indexPath.row) {
case .color:
openColorPicker()
case .bookmarkGroup:
openGroupPicker()
default:
break
}
}
// MARK: - Private
private func updateTrackIfNeeded() {
// TODO: Update the track content on the Edit screen instead of closing it when the track gets updated from cloud.
if !bookmarksManager.hasTrack(trackId) {
goBack()
}
}
private func addToBookmarksManagerObserverList() {
bookmarksManager.add(self)
}
private func removeFromBookmarksManagerObserverList() {
bookmarksManager.remove(self)
}
@objc private func onSave() {
view.endEditing(true)
BookmarksManager.shared().updateTrack(trackId, setGroupId: trackGroupId, color: trackColor, title: trackTitle ?? "")
editingCompleted(true)
goBack()
}
private func updateColor(_ color: UIColor) {
trackColor = color
tableView.reloadRows(at: [IndexPath(row: InfoSectionRows.color.rawValue, section: Sections.info.rawValue)],
with: .none)
}
@objc private func openColorPicker() {
ColorPicker.shared.present(from: self, pickerType: .defaultColorPicker(trackColor), completionHandler: { [weak self] color in
self?.updateColor(color)
})
}
private func openGroupPicker() {
let groupViewController = SelectBookmarkGroupViewController(groupName: trackGroupTitle ?? "", groupId: trackGroupId)
groupViewController.delegate = self
let navigationController = UINavigationController(rootViewController: groupViewController)
present(navigationController, animated: true, completion: nil)
}
}
extension EditTrackViewController: BookmarkTitleCellDelegate {
func didFinishEditingTitle(_ title: String) {
trackTitle = title
}
}
// MARK: - MWMButtonCellDelegate
extension EditTrackViewController: MWMButtonCellDelegate {
func cellDidPressButton(_ cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else {
fatalError("Invalid cell")
}
switch Sections(rawValue: indexPath.section) {
case .info:
break
case .delete:
bookmarksManager.deleteTrack(trackId)
goBack()
default:
fatalError("Invalid section")
}
}
}
// MARK: - BookmarkColorViewControllerDelegate
extension EditTrackViewController: BookmarkColorViewControllerDelegate {
func bookmarkColorViewController(_ viewController: BookmarkColorViewController, didSelect bookmarkColor: BookmarkColor) {
viewController.dismiss(animated: true)
updateColor(bookmarkColor.color)
}
}
// MARK: - SelectBookmarkGroupViewControllerDelegate
extension EditTrackViewController: SelectBookmarkGroupViewControllerDelegate {
func bookmarkGroupViewController(_ viewController: SelectBookmarkGroupViewController,
didSelect groupTitle: String,
groupId: MWMMarkGroupID) {
viewController.dismiss(animated: true)
trackGroupTitle = groupTitle
trackGroupId = groupId
tableView.reloadRows(at: [IndexPath(row: InfoSectionRows.bookmarkGroup.rawValue, section: Sections.info.rawValue)],
with: .none)
}
}
// MARK: - BookmarksObserver
extension EditTrackViewController: BookmarksObserver {
func onBookmarksLoadFinished() {
updateTrackIfNeeded()
}
func onBookmarksCategoryDeleted(_ groupId: MWMMarkGroupID) {
if trackGroupId == groupId {
goBack()
}
}
}

View file

@ -0,0 +1,95 @@
import UIKit
protocol SelectBookmarkGroupViewControllerDelegate: AnyObject {
func bookmarkGroupViewController(_ viewController: SelectBookmarkGroupViewController,
didSelect groupTitle: String,
groupId: MWMMarkGroupID)
}
final class SelectBookmarkGroupViewController: MWMTableViewController {
private enum Sections: Int {
case addGroup
case groups
case count
}
weak var delegate: SelectBookmarkGroupViewControllerDelegate?
private let groupName: String
private let groupId: MWMMarkGroupID
private let bookmarkGroups = BookmarksManager.shared().sortedUserCategories()
init(groupName: String, groupId: MWMMarkGroupID) {
self.groupName = groupName
self.groupId = groupId
super.init(style: .grouped)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = L("bookmark_sets");
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonDidTap))
}
@objc private func cancelButtonDidTap() {
dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
Sections.count.rawValue
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch Sections(rawValue: section) {
case .addGroup:
return 1
case .groups:
return bookmarkGroups.count
default:
fatalError()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueDefaultCell(for: indexPath)
switch Sections(rawValue: indexPath.section) {
case .addGroup:
cell.textLabel?.text = L("add_new_set")
cell.accessoryType = .disclosureIndicator
case .groups:
let bookmarkGroup = bookmarkGroups[indexPath.row]
cell.textLabel?.text = bookmarkGroup.title
cell.accessoryType = bookmarkGroup.categoryId == groupId ? .checkmark : .none
default:
fatalError()
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch Sections(rawValue: indexPath.section) {
case .addGroup:
createNewGroup()
case .groups:
let selectedGroup = bookmarkGroups[indexPath.row]
delegate?.bookmarkGroupViewController(self, didSelect: selectedGroup.title, groupId: selectedGroup.categoryId)
default:
fatalError()
}
}
private func createNewGroup() {
alertController.presentCreateBookmarkCategoryAlert(withMaxCharacterNum: 60, minCharacterNum: 0) {
[unowned self] name -> Bool in
guard BookmarksManager.shared().checkCategoryName(name) else { return false }
let newGroupId = BookmarksManager.shared().createCategory(withName: name)
self.delegate?.bookmarkGroupViewController(self, didSelect: name, groupId: newGroupId)
return true
}
}
}

View file

@ -0,0 +1,39 @@
#import "CircleView.h"
#include "kml/types.hpp"
namespace ios_bookmark_ui_helper
{
inline UIColor * UIColorForRGB(int red, int green, int blue)
{
return [UIColor colorWithRed:red/255.f green:green/255.f blue:blue/255.f alpha:0.8];
}
inline UIColor * UIColorForBookmarkColor(kml::PredefinedColor color)
{
auto const dpColor = kml::ColorFromPredefinedColor(color);
return UIColorForRGB(dpColor.red, dpColor.green, dpColor.blue);
}
inline UIImage * ImageForBookmark(kml::PredefinedColor color, kml::BookmarkIcon icon)
{
CGFloat const kPinDiameter = 22;
NSString *imageName = [NSString stringWithFormat:@"%@%@", @"ic_bm_", [@(kml::ToString(icon).c_str()) lowercaseString]];
return [CircleView createCircleImageWithDiameter:kPinDiameter
andColor:UIColorForBookmarkColor(color)
andImageName:imageName];
}
inline UIImage * ImageForTrack(float red, float green, float blue)
{
CGFloat const kPinDiameter = 22;
return [CircleView createCircleImageWithDiameter:kPinDiameter
andColor:[UIColor colorWithRed:red
green:green
blue:blue
alpha:1.f]];
}
} // namespace ios_bookmark_ui_helper

View file

@ -0,0 +1,17 @@
#import "MWMTableViewController.h"
#include "indexer/editable_map_object.hpp"
@protocol MWMEditorAdditionalNamesProtocol <NSObject>
- (void)addAdditionalName:(NSInteger)languageIndex;
@end
@interface MWMEditorAdditionalNamesTableViewController : MWMTableViewController
- (void)configWithDelegate:(id<MWMEditorAdditionalNamesProtocol>)delegate
name:(StringUtf8Multilang const &)name
additionalSkipLanguageCodes:(std::vector<NSInteger>)additionalSkipLanguageCodes;
@end

View file

@ -0,0 +1,93 @@
#import "MWMEditorAdditionalNamesTableViewController.h"
#import "MWMTableViewCell.h"
#import <CoreApi/StringUtils.h>
@interface MWMEditorAdditionalNamesTableViewController ()
@property (weak, nonatomic) id<MWMEditorAdditionalNamesProtocol> delegate;
@end
@implementation MWMEditorAdditionalNamesTableViewController
{
StringUtf8Multilang m_name;
std::vector<StringUtf8Multilang::Lang> m_languages;
std::vector<NSInteger> m_additionalSkipLanguageCodes;
}
#pragma mark - UITableViewDataSource
- (void)configWithDelegate:(id<MWMEditorAdditionalNamesProtocol>)delegate
name:(StringUtf8Multilang const &)name
additionalSkipLanguageCodes:(std::vector<NSInteger>)additionalSkipLanguageCodes
{
self.delegate = delegate;
m_name = name;
m_additionalSkipLanguageCodes = additionalSkipLanguageCodes;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = L(@"choose_language");
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
auto const getIndex = [](std::string_view lang) { return StringUtf8Multilang::GetLangIndex(lang); };
StringUtf8Multilang::Languages const & supportedLanguages = StringUtf8Multilang::GetSupportedLanguages();
m_languages.clear();
auto constexpr kDefaultCode = StringUtf8Multilang::kDefaultCode;
for (auto const & language : supportedLanguages)
{
auto const langIndex = getIndex(language.m_code);
if (langIndex != kDefaultCode && m_name.HasString(langIndex))
continue;
auto it = std::find(m_additionalSkipLanguageCodes.begin(), m_additionalSkipLanguageCodes.end(), langIndex);
if (it == m_additionalSkipLanguageCodes.end())
m_languages.push_back(language);
}
std::sort(m_languages.begin(), m_languages.end(),
[&getIndex](StringUtf8Multilang::Lang const & lhs, StringUtf8Multilang::Lang const & rhs) {
// Default name can be changed in advanced mode, but it should be last in list of names.
if (getIndex(lhs.m_code) == kDefaultCode && getIndex(rhs.m_code) != kDefaultCode)
return false;
if (getIndex(lhs.m_code) != kDefaultCode && getIndex(rhs.m_code) == kDefaultCode)
return true;
return std::string(lhs.m_code) < std::string(rhs.m_code);
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MWMTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"ListCellIdentifier"];
NSInteger const index = indexPath.row;
StringUtf8Multilang::Lang const & lang = m_languages[index];
cell.textLabel.text = ToNSString(lang.m_name);
cell.detailTextLabel.text = ToNSString(lang.m_code);
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return m_languages.size();
}
#pragma mark - UITableViewDataSource
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger const index = indexPath.row;
StringUtf8Multilang::Lang const & language = m_languages[index];
[self.delegate addAdditionalName:StringUtf8Multilang::GetLangIndex(language.m_code)];
[self.navigationController popViewControllerAnimated:YES];
}
@end

View file

@ -0,0 +1,3 @@
@objc(MWMEditorAdditionalNamePlaceholderTableViewCell)
final class EditorAdditionalNamePlaceholderTableViewCell: MWMTableViewCell {
}

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="16" id="7Br-fC-W0o" customClass="MWMEditorAdditionalNamePlaceholderTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="16"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="7Br-fC-W0o" id="58c-Uv-d0K">
<rect key="frame" x="0.0" y="0.0" width="320" height="16"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tYH-si-HMY">
<rect key="frame" x="0.0" y="0.0" width="320" height="16"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" priority="750" constant="16" id="A96-CL-Yji"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="tYH-si-HMY" firstAttribute="leading" secondItem="58c-Uv-d0K" secondAttribute="leading" id="GTY-XA-kVU"/>
<constraint firstAttribute="trailing" secondItem="tYH-si-HMY" secondAttribute="trailing" id="VOL-Tl-l3w"/>
<constraint firstAttribute="bottom" secondItem="tYH-si-HMY" secondAttribute="bottom" id="g0D-w9-dGP"/>
<constraint firstItem="tYH-si-HMY" firstAttribute="top" secondItem="58c-Uv-d0K" secondAttribute="top" id="qyd-oG-Ixi"/>
</constraints>
</tableViewCellContentView>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="225" y="282"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,17 @@
#import "MWMTableViewCell.h"
NS_ASSUME_NONNULL_BEGIN
@protocol MWMButtonCellDelegate <NSObject>
- (void)cellDidPressButton:(UITableViewCell *)cell;
@end
@interface MWMButtonCell : MWMTableViewCell
- (void)configureWithDelegate:(id<MWMButtonCellDelegate>)delegate title:(NSString *)title enabled:(BOOL)enabled;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,22 @@
#import "MWMButtonCell.h"
@interface MWMButtonCell ()
@property(nonatomic) IBOutlet UIButton *button;
@property(weak, nonatomic) id<MWMButtonCellDelegate> delegate;
@end
@implementation MWMButtonCell
- (void)configureWithDelegate:(id<MWMButtonCellDelegate>)delegate title:(NSString *)title enabled:(BOOL)enabled {
[self.button setTitle:title forState:UIControlStateNormal];
self.button.enabled = enabled;
self.delegate = delegate;
}
- (IBAction)buttonTap {
[self.delegate cellDidPressButton:self];
}
@end

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<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"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="KGk-i7-Jjw" customClass="MWMButtonCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c7Y-Nr-P4C">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="44" id="Z6u-ES-sTJ"/>
</constraints>
<state key="normal" title="Button">
<color key="titleColor" red="0.95686274510000002" green="0.26274509800000001" blue="0.21176470589999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="BookmarksCategoryDeleteButton"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="buttonTap" destination="KGk-i7-Jjw" eventType="touchUpInside" id="kS2-aj-wfq"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="c7Y-Nr-P4C" secondAttribute="trailing" id="AVP-GR-W7u"/>
<constraint firstAttribute="bottom" secondItem="c7Y-Nr-P4C" secondAttribute="bottom" id="F3N-Zb-tnQ"/>
<constraint firstItem="c7Y-Nr-P4C" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="JQx-63-Ane"/>
<constraint firstItem="c7Y-Nr-P4C" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="fTj-1o-vWJ"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="button" destination="c7Y-Nr-P4C" id="UDx-n5-6fr"/>
</connections>
<point key="canvasLocation" x="139" y="155"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,8 @@
#import "MWMEditorCommon.h"
#import "MWMTableViewCell.h"
@interface MWMEditorAddAdditionalNameTableViewCell : MWMTableViewCell
- (void)configWithDelegate:(id<MWMEditorAdditionalName>)delegate;
@end

View file

@ -0,0 +1,13 @@
#import "MWMEditorAddAdditionalNameTableViewCell.h"
@interface MWMEditorAddAdditionalNameTableViewCell ()
@property(weak, nonatomic) id<MWMEditorAdditionalName> delegate;
@end
@implementation MWMEditorAddAdditionalNameTableViewCell
- (void)configWithDelegate:(id<MWMEditorAdditionalName>)delegate { self.delegate = delegate; }
- (IBAction)addLanguageTap { [self.delegate editAdditionalNameLanguage:NSNotFound]; }
@end

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="81H-Jz-Sl2" customClass="MWMEditorAddAdditionalNameTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="81H-Jz-Sl2" id="EHS-hP-aIE">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ua5-4V-1PH" customClass="MWMButton">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<constraints>
<constraint firstAttribute="height" priority="750" constant="44" id="oVm-ep-LLL"/>
</constraints>
<inset key="contentEdgeInsets" minX="12" minY="0.0" maxX="0.0" maxY="0.0"/>
<inset key="titleEdgeInsets" minX="14" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="add_language" image="plus.circle.fill" catalog="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="FlatNormalTransButtonBig"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="add_language"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="addLanguageTap" destination="81H-Jz-Sl2" eventType="touchUpInside" id="Lw7-kU-hIK"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Ua5-4V-1PH" secondAttribute="bottom" id="Nmp-Cj-ffg"/>
<constraint firstItem="Ua5-4V-1PH" firstAttribute="leading" secondItem="EHS-hP-aIE" secondAttribute="leading" id="XP5-8s-Vka"/>
<constraint firstItem="Ua5-4V-1PH" firstAttribute="top" secondItem="EHS-hP-aIE" secondAttribute="top" id="YAU-Xb-h9Y"/>
<constraint firstAttribute="trailing" secondItem="Ua5-4V-1PH" secondAttribute="trailing" id="f6J-6o-AjX"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<point key="canvasLocation" x="342" y="257"/>
</tableViewCell>
</objects>
<resources>
<image name="plus.circle.fill" catalog="system" width="128" height="123"/>
</resources>
</document>

View file

@ -0,0 +1,16 @@
#import "MWMEditorCommon.h"
#import "MWMTableViewCell.h"
@interface MWMEditorAdditionalNameTableViewCell : MWMTableViewCell
@property(nonatomic, readonly) NSInteger code;
- (void)configWithDelegate:(id<MWMEditorAdditionalName>)delegate
langCode:(NSInteger)langCode
langName:(NSString *)langName
name:(NSString *)name
errorMessage:(NSString *)errorMessage
isValid:(BOOL)isValid
keyboardType:(UIKeyboardType)keyboardType;
@end

View file

@ -0,0 +1,103 @@
#import "MWMEditorAdditionalNameTableViewCell.h"
#import "SwiftBridge.h"
static CGFloat const kErrorLabelHeight = 16;
@interface MWMEditorAdditionalNameTableViewCell ()
@property(weak, nonatomic) IBOutlet UIStackView * stackView;
@property(weak, nonatomic) IBOutlet UILabel * languageLabel;
@property(weak, nonatomic) IBOutlet UITextField * textField;
@property(weak, nonatomic) IBOutlet UILabel * errorLabel;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * errorLabelHeight;
@property(nonatomic, readwrite) NSInteger code;
@property(weak, nonatomic) id<MWMEditorAdditionalName> delegate;
@property(nonatomic) BOOL isValid;
@end
@implementation MWMEditorAdditionalNameTableViewCell
- (void)configWithDelegate:(id<MWMEditorAdditionalName>)delegate
langCode:(NSInteger)langCode
langName:(NSString *)langName
name:(NSString *)name
errorMessage:(NSString *)errorMessage
isValid:(BOOL)isValid
keyboardType:(UIKeyboardType)keyboardType
{
self.delegate = delegate;
self.code = langCode;
self.languageLabel.text = langName;
self.errorLabel.text = errorMessage;
self.isValid = isValid;
self.textField.text = name;
self.textField.keyboardType = keyboardType;
self.textField.backgroundColor = [UIColor clearColor];
[self processValidation];
}
- (void)processValidation
{
if (self.isValid)
{
self.errorLabelHeight.constant = 0;
[self.contentView setStyleNameAndApply: @"Background"];
}
else
{
self.errorLabelHeight.constant = kErrorLabelHeight;
[self.contentView setStyleNameAndApply: @"ErrorBackground"];
}
[self layoutIfNeeded];
}
- (void)changeInvalidCellState
{
if (self.isValid)
return;
self.isValid = YES;
[self processValidation];
[self.delegate tryToChangeInvalidStateForCell:self];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
[self changeInvalidCellState];
return YES;
}
- (BOOL)textFieldShouldClear:(UITextField *)textField
{
[self changeInvalidCellState];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self.delegate cell:self changedText:textField.text];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// Allow to tap on the whole cell to start editing.
UIView * view = [super hitTest:point withEvent:event];
if (view == self.stackView)
return self.textField;
return view;
}
@end

View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="hfo-cP-AGX" customClass="MWMEditorAdditionalNameTableViewCell">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hfo-cP-AGX" id="JQH-ks-NoC">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillProportionally" alignment="top" spacing="3" translatesAutoresizingMaskIntoConstraints="NO" id="eca-XW-QZR">
<rect key="frame" x="16" y="16" width="288" height="12"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="zh-classical" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="efb-nS-cjm">
<rect key="frame" x="0.0" y="0.0" width="77" height="5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalCompressionResistancePriority="499" contentHorizontalAlignment="left" contentVerticalAlignment="center" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="9BY-PA-dlA">
<rect key="frame" x="0.0" y="8" width="5" height="4"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedPlaceholder" value="editor_edit_place_name_hint"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="delegate" destination="hfo-cP-AGX" id="jkD-0x-Ods"/>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="9BY-PA-dlA" firstAttribute="width" secondItem="eca-XW-QZR" secondAttribute="width" id="OSr-03-hPe"/>
</constraints>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="252" translatesAutoresizingMaskIntoConstraints="NO" id="SZa-Bj-se1">
<rect key="frame" x="60" y="32" width="252" height="0.0"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" priority="999" id="CPF-uE-pzx"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.95686274510000002" green="0.26274509800000001" blue="0.21176470589999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular12:redText"/>
</userDefinedRuntimeAttributes>
</label>
</subviews>
<constraints>
<constraint firstItem="SZa-Bj-se1" firstAttribute="leading" secondItem="JQH-ks-NoC" secondAttribute="leading" constant="60" id="BcS-gb-9vl"/>
<constraint firstAttribute="trailing" secondItem="SZa-Bj-se1" secondAttribute="trailing" constant="8" id="EBU-G3-tgp"/>
<constraint firstItem="eca-XW-QZR" firstAttribute="top" secondItem="JQH-ks-NoC" secondAttribute="top" constant="16" id="FJ5-Zj-wLR"/>
<constraint firstAttribute="trailing" secondItem="eca-XW-QZR" secondAttribute="trailing" constant="16" id="NZO-C9-ZCS"/>
<constraint firstItem="SZa-Bj-se1" firstAttribute="top" secondItem="eca-XW-QZR" secondAttribute="bottom" constant="4" id="T9Z-G5-LVc"/>
<constraint firstAttribute="bottom" secondItem="SZa-Bj-se1" secondAttribute="bottom" constant="12" id="TdX-jx-CpD"/>
<constraint firstItem="eca-XW-QZR" firstAttribute="leading" secondItem="JQH-ks-NoC" secondAttribute="leading" constant="16" id="dbb-bG-8uT"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="errorLabel" destination="SZa-Bj-se1" id="hqN-vX-0Hw"/>
<outlet property="errorLabelHeight" destination="CPF-uE-pzx" id="X4g-6X-W0e"/>
<outlet property="languageLabel" destination="efb-nS-cjm" id="13M-9D-Qbf"/>
<outlet property="stackView" destination="eca-XW-QZR" id="RPt-fc-aoR"/>
<outlet property="textField" destination="9BY-PA-dlA" id="SCv-JL-5TL"/>
</connections>
<point key="canvasLocation" x="340.57971014492756" y="256.47321428571428"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,11 @@
#import "MWMTableViewCell.h"
@protocol MWMEditorCellProtocol;
@interface MWMEditorCategoryCell : MWMTableViewCell
- (void)configureWithDelegate:(id<MWMEditorCellProtocol>)delegate
detailTitle:(NSString *)detail
isCreating:(BOOL)isCreating;
@end

View file

@ -0,0 +1,44 @@
#import "MWMEditorCategoryCell.h"
#import "MWMEditorCommon.h"
#import "SwiftBridge.h"
CGFloat const kDetailShortRightSpace = 16;
@interface MWMEditorCategoryCell ()
@property(weak, nonatomic) IBOutlet UIImageView * accessoryIcon;
@property(weak, nonatomic) IBOutlet UILabel * detail;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * detailRightSpace;
@property(weak, nonatomic) id<MWMEditorCellProtocol> delegate;
@end
@implementation MWMEditorCategoryCell
- (void)configureWithDelegate:(id<MWMEditorCellProtocol>)delegate
detailTitle:(NSString *)detail
isCreating:(BOOL)isCreating
{
self.delegate = delegate;
self.detail.text = detail;
self.accessoryIcon.hidden = !isCreating;
if (isCreating)
{
self.selectedBackgroundView = [[UIView alloc] init];
self.selectedBackgroundView.styleName = @"PressBackground";
}
else
{
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.detailRightSpace.constant = kDetailShortRightSpace;
}
}
- (IBAction)cellTap
{
if (self.accessoryIcon.hidden)
return;
[self.delegate cellDidPressButton:self];
}
@end

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="rsc-tP-qGq" customClass="MWMEditorCategoryCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="rsc-tP-qGq" id="mWc-I7-4kZ">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_right" translatesAutoresizingMaskIntoConstraints="NO" id="uA3-ru-Ja1">
<rect key="frame" x="286" y="8" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="CW0-pF-saL"/>
<constraint firstAttribute="height" constant="28" id="P6z-Sg-ZyD"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jfx-Kc-WOc">
<rect key="frame" x="16" y="12" width="226" height="20"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="BaE-rI-CN2"/>
<constraint firstAttribute="height" constant="20" id="oLW-um-5dA"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="editor_edit_place_category_title"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="501" verticalHuggingPriority="251" text="Label" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qrd-hP-nIJ">
<rect key="frame" x="250" y="11.5" width="42" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="hsO-dV-WsY"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="icU-WX-U52">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<connections>
<action selector="cellTap" destination="rsc-tP-qGq" eventType="touchUpInside" id="dru-SF-L7z"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Jfx-Kc-WOc" firstAttribute="leading" secondItem="mWc-I7-4kZ" secondAttribute="leading" constant="16" id="5lg-6C-NQt"/>
<constraint firstItem="qrd-hP-nIJ" firstAttribute="leading" secondItem="Jfx-Kc-WOc" secondAttribute="trailing" constant="8" id="ETn-mX-ysj"/>
<constraint firstItem="Jfx-Kc-WOc" firstAttribute="centerY" secondItem="mWc-I7-4kZ" secondAttribute="centerY" id="P6u-UL-5f7"/>
<constraint firstAttribute="trailing" secondItem="qrd-hP-nIJ" secondAttribute="trailing" constant="28" id="Rwj-G9-EoG"/>
<constraint firstAttribute="trailing" secondItem="uA3-ru-Ja1" secondAttribute="trailing" constant="6" id="byM-mn-aQu"/>
<constraint firstItem="uA3-ru-Ja1" firstAttribute="centerY" secondItem="mWc-I7-4kZ" secondAttribute="centerY" id="sVc-Kt-8ZX"/>
<constraint firstItem="qrd-hP-nIJ" firstAttribute="centerY" secondItem="mWc-I7-4kZ" secondAttribute="centerY" id="wSH-Lz-TEL"/>
</constraints>
</tableViewCellContentView>
<constraints>
<constraint firstItem="icU-WX-U52" firstAttribute="centerX" secondItem="rsc-tP-qGq" secondAttribute="centerX" id="F9k-Ej-LFc"/>
<constraint firstItem="icU-WX-U52" firstAttribute="width" secondItem="rsc-tP-qGq" secondAttribute="width" id="ZUi-Rh-jlL"/>
<constraint firstItem="icU-WX-U52" firstAttribute="height" secondItem="rsc-tP-qGq" secondAttribute="height" id="dy8-tF-aiq"/>
<constraint firstItem="icU-WX-U52" firstAttribute="centerY" secondItem="rsc-tP-qGq" secondAttribute="centerY" id="uky-CX-2ye"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="accessoryIcon" destination="uA3-ru-Ja1" id="esf-Xi-gLG"/>
<outlet property="detail" destination="qrd-hP-nIJ" id="IOE-vz-HaI"/>
<outlet property="detailRightSpace" destination="Rwj-G9-EoG" id="H8f-mf-vOJ"/>
</connections>
<point key="canvasLocation" x="501.44927536231887" y="199.55357142857142"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_arrow_gray_right" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,12 @@
#import "MWMEditorCommon.h"
#import "MWMTableViewCell.h"
#include "indexer/yes_no_unknown.hpp"
@interface MWMEditorSegmentedTableViewCell : MWMTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
value:(YesNoUnknown)value;
@end

View file

@ -0,0 +1,82 @@
#import "MWMEditorSegmentedTableViewCell.hpp"
#import "SwiftBridge.h"
@interface MWMEditorSegmentedTableViewCell ()
@property(weak, nonatomic) IBOutlet UIImageView * icon;
@property(weak, nonatomic) IBOutlet UILabel * label;
@property(weak, nonatomic) IBOutlet UISegmentedControl * segmentedControl;
@property(weak, nonatomic) id<MWMEditorCellProtocol> delegate;
@end
@implementation MWMEditorSegmentedTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
value:(YesNoUnknown)value
{
self.delegate = delegate;
self.icon.image = icon;
self.icon.styleName = @"MWMBlack";
self.label.text = text;
[self.segmentedControl setTitle:NSLocalizedString(@"no", nil) forSegmentAtIndex:0];
[self.segmentedControl setTitle:NSLocalizedString(@"yes", nil) forSegmentAtIndex:2];
switch(value)
{
case Yes:
self.segmentedControl.selectedSegmentIndex = 2;
break;
case No:
self.segmentedControl.selectedSegmentIndex = 0;
break;
case Unknown:
self.segmentedControl.selectedSegmentIndex = 1;
break;
}
[self setTextColorWithSegmentedValue:value];
}
- (void)setTextColorWithSegmentedValue:(YesNoUnknown)value
{
switch (value)
{
case Yes:
case No:
self.label.textColor = [UIColor blackPrimaryText];
break;
case Unknown:
self.label.textColor = [UIColor blackHintText];
break;
}
}
- (IBAction)valueChanged
{
YesNoUnknown value;
switch (self.segmentedControl.selectedSegmentIndex)
{
case 0:
value = No;
break;
case 1:
value = Unknown;
break;
case 2:
value = Yes;
break;
default:
value = Unknown;
NSAssert(false, @"Unexpected YesNoUnknown value %ld", static_cast<long>(self.segmentedControl.selectedSegmentIndex));
}
[self.delegate cell:self changeSegmented:value];
[self setTextColorWithSegmentedValue:value];
}
@end

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<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"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="qme-9J-aMf" customClass="MWMEditorSegmentedTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="qme-9J-aMf" id="fpo-VO-awT">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="j3u-fF-pG6">
<rect key="frame" x="16" y="8" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="28" id="Ce3-kg-QsX"/>
<constraint firstAttribute="width" constant="28" id="MT3-eQ-SOL"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMBlack"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="187" translatesAutoresizingMaskIntoConstraints="NO" id="QFf-wR-pPw">
<rect key="frame" x="60" y="0.0" width="133" height="44"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="44" id="XYk-fv-D9R"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="Rp8-YJ-AzV">
<rect key="frame" x="201" y="6.5" width="103" height="32"/>
<segments>
<segment title="No"/>
<segment title="-"/>
<segment title="Yes"/>
</segments>
<connections>
<action selector="valueChanged" destination="qme-9J-aMf" eventType="valueChanged" id="jR4-p8-Ldo"/>
</connections>
</segmentedControl>
</subviews>
<constraints>
<constraint firstItem="QFf-wR-pPw" firstAttribute="top" secondItem="fpo-VO-awT" secondAttribute="top" id="3mH-Ua-EUl"/>
<constraint firstItem="j3u-fF-pG6" firstAttribute="centerY" secondItem="fpo-VO-awT" secondAttribute="centerY" id="4xr-PJ-Dl2"/>
<constraint firstItem="j3u-fF-pG6" firstAttribute="leading" secondItem="fpo-VO-awT" secondAttribute="leading" constant="16" id="TFM-QG-P2e"/>
<constraint firstAttribute="bottom" secondItem="QFf-wR-pPw" secondAttribute="bottom" id="YAg-Wx-DLo"/>
<constraint firstItem="Rp8-YJ-AzV" firstAttribute="centerY" secondItem="fpo-VO-awT" secondAttribute="centerY" id="aTy-ab-vvF"/>
<constraint firstItem="Rp8-YJ-AzV" firstAttribute="leading" secondItem="QFf-wR-pPw" secondAttribute="trailing" constant="8" id="ge8-tr-S4S"/>
<constraint firstItem="QFf-wR-pPw" firstAttribute="leading" secondItem="fpo-VO-awT" secondAttribute="leading" constant="60" id="mNR-IV-cek"/>
<constraint firstAttribute="trailing" secondItem="Rp8-YJ-AzV" secondAttribute="trailing" constant="16" id="z6B-PV-rXg"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="icon" destination="j3u-fF-pG6" id="nIz-cY-MCW"/>
<outlet property="label" destination="QFf-wR-pPw" id="1MO-Vf-bt6"/>
<outlet property="segmentedControl" destination="Rp8-YJ-AzV" id="C9T-O3-dMM"/>
</connections>
<point key="canvasLocation" x="137.59999999999999" y="154.72263868065968"/>
</tableViewCell>
</objects>
</document>

View file

@ -0,0 +1,11 @@
#import "MWMEditorCommon.h"
#import "MWMTableViewCell.h"
@interface MWMEditorSelectTableViewCell : MWMTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
placeholder:(NSString *)placeholder;
@end

View file

@ -0,0 +1,46 @@
#import "MWMEditorSelectTableViewCell.h"
#import <CoreApi/MWMCommon.h>
#import "SwiftBridge.h"
@interface MWMEditorSelectTableViewCell ()
@property(weak, nonatomic) IBOutlet UIImageView * icon;
@property(weak, nonatomic) IBOutlet UILabel * label;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * labelLeadingOffset;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint * labelTrailingOffset;
@property(weak, nonatomic) IBOutlet UIImageView * grayArrow;
@property(weak, nonatomic) id<MWMEditorCellProtocol> delegate;
@end
@implementation MWMEditorSelectTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
placeholder:(NSString *)placeholder
{
self.delegate = delegate;
self.icon.hidden = NO;
self.icon.image = icon;
self.icon.styleName = @"MWMBlack";
if (text && text.length != 0)
{
self.label.text = text;
[self.label setStyleNameAndApply: @"blackPrimaryText"];
}
else
{
self.label.text = placeholder;
[self.label setStyleNameAndApply: @"blackHintText"];
}
self.label.preferredMaxLayoutWidth =
self.width - self.labelLeadingOffset.constant - self.labelTrailingOffset.constant;
if (isInterfaceRightToLeft())
self.grayArrow.transform = CGAffineTransformMakeScale(-1, 1);
}
- (IBAction)selectAction { [self.delegate cellDidPressButton:self]; }
@end

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="xiI-ev-aU2" customClass="MWMEditorSelectTableViewCell" propertyAccessControl="none">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xiI-ev-aU2" id="V1p-K9-KlE">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="xur-tJ-HA9">
<rect key="frame" x="16" y="8" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="Oak-eT-K2q"/>
<constraint firstAttribute="height" constant="28" id="s7g-CH-05Z"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMBlack"/>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="220" translatesAutoresizingMaskIntoConstraints="NO" id="MBe-6r-IsA">
<rect key="frame" x="60" y="8" width="220" height="28"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="28" id="7cv-Wq-vWC"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular17:blackPrimaryText"/>
</userDefinedRuntimeAttributes>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_arrow_gray_right" translatesAutoresizingMaskIntoConstraints="NO" id="jLw-GA-z3y">
<rect key="frame" x="286" y="8" width="28" height="28"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="Ki5-4h-NRK"/>
<constraint firstAttribute="height" constant="28" id="PG8-LW-72y"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
</userDefinedRuntimeAttributes>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nq9-KC-Ujh">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<connections>
<action selector="selectAction" destination="xiI-ev-aU2" eventType="touchUpInside" id="xXk-7q-LQQ"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="jLw-GA-z3y" secondAttribute="trailing" constant="6" id="3Bg-20-t7E"/>
<constraint firstAttribute="trailing" secondItem="nq9-KC-Ujh" secondAttribute="trailing" id="5gd-06-ZOC"/>
<constraint firstItem="MBe-6r-IsA" firstAttribute="top" secondItem="V1p-K9-KlE" secondAttribute="top" constant="8" id="6FB-in-VCz"/>
<constraint firstAttribute="bottom" secondItem="MBe-6r-IsA" secondAttribute="bottom" constant="8" id="CG0-ar-vx9"/>
<constraint firstItem="xur-tJ-HA9" firstAttribute="leading" secondItem="V1p-K9-KlE" secondAttribute="leading" constant="16" id="DlR-yh-2JK"/>
<constraint firstItem="MBe-6r-IsA" firstAttribute="leading" secondItem="V1p-K9-KlE" secondAttribute="leading" constant="60" id="RGA-XL-gLq"/>
<constraint firstItem="xur-tJ-HA9" firstAttribute="centerY" secondItem="V1p-K9-KlE" secondAttribute="centerY" id="agx-Kf-DpK"/>
<constraint firstAttribute="bottom" secondItem="nq9-KC-Ujh" secondAttribute="bottom" id="fK8-AO-JKa"/>
<constraint firstItem="nq9-KC-Ujh" firstAttribute="top" secondItem="V1p-K9-KlE" secondAttribute="top" id="ibI-hy-19F"/>
<constraint firstItem="jLw-GA-z3y" firstAttribute="centerY" secondItem="V1p-K9-KlE" secondAttribute="centerY" id="nY0-7m-bcZ"/>
<constraint firstItem="nq9-KC-Ujh" firstAttribute="leading" secondItem="V1p-K9-KlE" secondAttribute="leading" id="tyN-aq-pC1"/>
<constraint firstAttribute="trailing" secondItem="MBe-6r-IsA" secondAttribute="trailing" constant="40" id="yG7-RB-5Pa"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<inset key="separatorInset" minX="60" minY="0.0" maxX="0.0" maxY="0.0"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="Background"/>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="grayArrow" destination="jLw-GA-z3y" id="5NK-K5-4IO"/>
<outlet property="icon" destination="xur-tJ-HA9" id="ObZ-fc-tbA"/>
<outlet property="label" destination="MBe-6r-IsA" id="mvf-Q3-vMz"/>
<outlet property="labelLeadingOffset" destination="RGA-XL-gLq" id="Tb4-Ew-iDe"/>
<outlet property="labelTrailingOffset" destination="yG7-RB-5Pa" id="NqK-3D-zMQ"/>
</connections>
<point key="canvasLocation" x="139" y="155"/>
</tableViewCell>
</objects>
<resources>
<image name="ic_arrow_gray_right" width="28" height="28"/>
</resources>
</document>

View file

@ -0,0 +1,11 @@
#import "MWMEditorCommon.h"
#import "MWMTableViewCell.h"
@interface MWMEditorSwitchTableViewCell : MWMTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
on:(BOOL)on;
@end

View file

@ -0,0 +1,41 @@
#import "MWMEditorSwitchTableViewCell.h"
#import "SwiftBridge.h"
@interface MWMEditorSwitchTableViewCell ()
@property(weak, nonatomic) IBOutlet UIImageView * icon;
@property(weak, nonatomic) IBOutlet UILabel * label;
@property(weak, nonatomic) IBOutlet UISwitch * switchControl;
@property(weak, nonatomic) id<MWMEditorCellProtocol> delegate;
@end
@implementation MWMEditorSwitchTableViewCell
- (void)configWithDelegate:(id<MWMEditorCellProtocol>)delegate
icon:(UIImage *)icon
text:(NSString *)text
on:(BOOL)on
{
self.delegate = delegate;
self.icon.image = icon;
self.icon.styleName = @"MWMBlack";
self.label.text = text;
self.switchControl.on = on;
[self setTextColorWithSwitchValue:on];
}
- (void)setTextColorWithSwitchValue:(BOOL)value
{
self.label.textColor = value ? [UIColor blackPrimaryText] : [UIColor blackHintText];
}
- (IBAction)valueChanged
{
BOOL const value = self.switchControl.on;
[self.delegate cell:self changeSwitch:value];
[self setTextColorWithSwitchValue:value];
}
@end

Some files were not shown because too many files have changed in this diff Show more