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,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
}
}