Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
|
|
@ -0,0 +1,17 @@
|
|||
final class BMCActionsCell: MWMTableViewCell {
|
||||
@IBOutlet private weak var actionImage: UIImageView!
|
||||
|
||||
@IBOutlet private weak var actionTitle: UILabel!
|
||||
|
||||
private var model: BMCAction! {
|
||||
didSet {
|
||||
actionImage.image = model.image
|
||||
actionTitle.text = model.title
|
||||
}
|
||||
}
|
||||
|
||||
func config(model: BMCAction) -> UITableViewCell {
|
||||
self.model = model
|
||||
return self
|
||||
}
|
||||
}
|
||||
71
iphone/Maps/Bookmarks/Categories/Actions/BMCActionsCell.xib
Normal file
71
iphone/Maps/Bookmarks/Categories/Actions/BMCActionsCell.xib
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?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" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<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="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="BMCActionsCell" customModule="CoMaps" customModuleProvider="target">
|
||||
<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>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YDi-5J-vFD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic24PxAddCopy" translatesAutoresizingMaskIntoConstraints="NO" id="paw-km-zXg">
|
||||
<rect key="frame" x="16" y="10" width="24" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="S7u-WM-dEL"/>
|
||||
<constraint firstAttribute="width" constant="24" id="WgB-k6-NoZ"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMBlue"/>
|
||||
</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="2jJ-Pu-pjy">
|
||||
<rect key="frame" x="56" y="11.5" width="248" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="paw-km-zXg" firstAttribute="centerY" secondItem="YDi-5J-vFD" secondAttribute="centerY" id="Oe0-2d-YX4"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2jJ-Pu-pjy" secondAttribute="trailing" constant="16" id="ZGg-bD-dnT"/>
|
||||
<constraint firstItem="2jJ-Pu-pjy" firstAttribute="centerY" secondItem="YDi-5J-vFD" secondAttribute="centerY" id="h3L-zB-b66"/>
|
||||
<constraint firstItem="paw-km-zXg" firstAttribute="leading" secondItem="YDi-5J-vFD" secondAttribute="leading" constant="16" id="hTc-Pj-9Kf"/>
|
||||
<constraint firstItem="2jJ-Pu-pjy" firstAttribute="leading" secondItem="paw-km-zXg" secondAttribute="trailing" constant="16" id="maj-Zr-I2l"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="YDi-5J-vFD" secondAttribute="trailing" id="0yF-ib-wdg"/>
|
||||
<constraint firstItem="YDi-5J-vFD" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="7B2-Af-3Ko"/>
|
||||
<constraint firstAttribute="bottom" secondItem="YDi-5J-vFD" secondAttribute="bottom" id="HQk-FG-Enc"/>
|
||||
<constraint firstItem="YDi-5J-vFD" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="oCB-5w-Deh"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="actionImage" destination="paw-km-zXg" id="CfG-Wg-jon"/>
|
||||
<outlet property="actionTitle" destination="2jJ-Pu-pjy" id="YFu-jA-Abl"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="139" y="155"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="ic24PxAddCopy" width="24" height="24"/>
|
||||
</resources>
|
||||
</document>
|
||||
67
iphone/Maps/Bookmarks/Categories/BMCActionsCreateCell.xib
Normal file
67
iphone/Maps/Bookmarks/Categories/BMCActionsCreateCell.xib
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<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="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="BMCActionsCreateCell" customModule="CoMaps" customModuleProvider="target">
|
||||
<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="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YDi-5J-vFD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic24PxAddCopy" translatesAutoresizingMaskIntoConstraints="NO" id="paw-km-zXg">
|
||||
<rect key="frame" x="16" y="10" width="24" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="S7u-WM-dEL"/>
|
||||
<constraint firstAttribute="width" constant="24" id="WgB-k6-NoZ"/>
|
||||
</constraints>
|
||||
</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="2jJ-Pu-pjy">
|
||||
<rect key="frame" x="56" y="11.5" width="248" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="paw-km-zXg" firstAttribute="centerY" secondItem="YDi-5J-vFD" secondAttribute="centerY" id="Oe0-2d-YX4"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2jJ-Pu-pjy" secondAttribute="trailing" constant="16" id="ZGg-bD-dnT"/>
|
||||
<constraint firstItem="2jJ-Pu-pjy" firstAttribute="centerY" secondItem="YDi-5J-vFD" secondAttribute="centerY" id="h3L-zB-b66"/>
|
||||
<constraint firstItem="paw-km-zXg" firstAttribute="leading" secondItem="YDi-5J-vFD" secondAttribute="leading" constant="16" id="hTc-Pj-9Kf"/>
|
||||
<constraint firstItem="2jJ-Pu-pjy" firstAttribute="leading" secondItem="paw-km-zXg" secondAttribute="trailing" constant="16" id="maj-Zr-I2l"/>
|
||||
<constraint firstAttribute="height" constant="44" id="rhS-LZ-kol"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="YDi-5J-vFD" secondAttribute="trailing" id="0yF-ib-wdg"/>
|
||||
<constraint firstItem="YDi-5J-vFD" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="7B2-Af-3Ko"/>
|
||||
<constraint firstAttribute="bottom" secondItem="YDi-5J-vFD" secondAttribute="bottom" id="HQk-FG-Enc"/>
|
||||
<constraint firstItem="YDi-5J-vFD" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="oCB-5w-Deh"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="actionImage" destination="paw-km-zXg" id="CfG-Wg-jon"/>
|
||||
<outlet property="actionTitle" destination="2jJ-Pu-pjy" id="YFu-jA-Abl"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="ic24PxAddCopy" width="24" height="24"/>
|
||||
</resources>
|
||||
</document>
|
||||
47
iphone/Maps/Bookmarks/Categories/BMCModels.swift
Normal file
47
iphone/Maps/Bookmarks/Categories/BMCModels.swift
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
enum BMCSection {
|
||||
case categories
|
||||
case actions
|
||||
case recentlyDeleted
|
||||
case notifications
|
||||
}
|
||||
|
||||
protocol BMCModel {}
|
||||
|
||||
enum BMCAction: BMCModel {
|
||||
case create
|
||||
case exportAll
|
||||
case `import`
|
||||
case recentlyDeleted(Int)
|
||||
}
|
||||
|
||||
extension BMCAction {
|
||||
var title: String {
|
||||
switch self {
|
||||
case .create:
|
||||
return L("bookmarks_create_new_group")
|
||||
case .exportAll:
|
||||
return L("bookmarks_export")
|
||||
case .import:
|
||||
return L("bookmarks_import")
|
||||
case .recentlyDeleted(let count):
|
||||
return L("bookmarks_recently_deleted") + " (\(count))"
|
||||
}
|
||||
}
|
||||
|
||||
var image: UIImage {
|
||||
switch self {
|
||||
case .create:
|
||||
return UIImage(named: "ic24PxAddCopy")!
|
||||
case .exportAll:
|
||||
return UIImage(named: "ic24PxShare")!
|
||||
case .import:
|
||||
return UIImage(named: "ic24PxImport")!
|
||||
case .recentlyDeleted:
|
||||
return UIImage(named: "ic_route_manager_trash_open")!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum BMCNotification: BMCModel {
|
||||
case load
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
final class BMCEmtyDescriptionCell: UITableViewCell {
|
||||
}
|
||||
333
iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift
Normal file
333
iphone/Maps/Bookmarks/Categories/BMCView/BMCViewController.swift
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
final class BMCViewController: MWMViewController {
|
||||
private var viewModel: BMCDefaultViewModel! {
|
||||
didSet {
|
||||
viewModel.view = self
|
||||
tableView.dataSource = self
|
||||
}
|
||||
}
|
||||
|
||||
private weak var coordinator: BookmarksCoordinator?
|
||||
|
||||
@IBOutlet private weak var tableView: UITableView! {
|
||||
didSet {
|
||||
let cells = [
|
||||
BMCCategoryCell.self,
|
||||
BMCActionsCell.self,
|
||||
BMCNotificationsCell.self,
|
||||
]
|
||||
tableView.registerNibs(cells)
|
||||
tableView.registerNibForHeaderFooterView(BMCCategoriesHeader.self)
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet private var actionsHeader: UIView!
|
||||
@IBOutlet private var notificationsHeader: BMCNotificationsHeader!
|
||||
|
||||
init(coordinator: BookmarksCoordinator?) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.coordinator = coordinator
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.setStyle(.pressBackground)
|
||||
viewModel = BMCDefaultViewModel()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
viewModel.addToObserverList()
|
||||
viewModel.reloadData()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
viewModel.removeFromObserverList()
|
||||
}
|
||||
|
||||
private func createNewCategory() {
|
||||
alertController.presentCreateBookmarkCategoryAlert(withMaxCharacterNum: viewModel.maxCategoryNameLength,
|
||||
minCharacterNum: viewModel.minCategoryNameLength)
|
||||
{ [weak viewModel] (name: String!) -> Bool in
|
||||
guard let model = viewModel else { return false }
|
||||
if model.checkCategory(name: name) {
|
||||
model.addCategory(name: name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func shareCategoryFile(at index: Int, fileType: KmlFileType, anchor: UIView) {
|
||||
UIApplication.shared.showLoadingOverlay()
|
||||
viewModel.shareCategoryFile(at: index, fileType: fileType, handler: sharingResultHandler(anchorView: anchor))
|
||||
}
|
||||
|
||||
private func shareAllCategories(anchor: UIView?) {
|
||||
UIApplication.shared.showLoadingOverlay()
|
||||
viewModel.shareAllCategories(handler: sharingResultHandler(anchorView: anchor))
|
||||
}
|
||||
|
||||
private func sharingResultHandler(anchorView: UIView?) -> SharingResultCompletionHandler {
|
||||
{ [weak self] status, url in
|
||||
UIApplication.shared.hideLoadingOverlay {
|
||||
guard let self else { return }
|
||||
switch status {
|
||||
case .success:
|
||||
let shareController = ActivityViewController.share(for: url, message: L("share_bookmarks_email_body"))
|
||||
{ [weak self] _, _, _, _ in
|
||||
self?.viewModel?.finishShareCategory()
|
||||
}
|
||||
shareController.present(inParentViewController: self, anchorView: anchorView)
|
||||
case .emptyCategory:
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_error_title_share_empty"),
|
||||
text: L("bookmarks_error_message_share_empty"))
|
||||
case .fileError, .archiveError:
|
||||
MWMAlertViewController.activeAlert().presentInfoAlert(L("dialog_routing_system_error"),
|
||||
text: L("bookmarks_error_message_share_general"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func showImportDialog() {
|
||||
DocumentPicker.shared.present(from: self) { [viewModel] urls in
|
||||
viewModel?.importCategories(from: urls)
|
||||
}
|
||||
}
|
||||
|
||||
private func openCategorySettings(category: BookmarkGroup) {
|
||||
let settingsController = CategorySettingsViewController(bookmarkGroup: BookmarksManager.shared().category(withId: category.categoryId))
|
||||
settingsController.delegate = self
|
||||
|
||||
MapViewController.shared()?.navigationController?.pushViewController(settingsController, animated: true)
|
||||
}
|
||||
|
||||
private func openCategory(category: BookmarkGroup) {
|
||||
let bmViewController = BookmarksListBuilder.build(markGroupId: category.categoryId,
|
||||
bookmarksCoordinator: coordinator,
|
||||
delegate: self)
|
||||
MapViewController.shared()?.navigationController?.pushViewController(bmViewController, animated: true)
|
||||
}
|
||||
|
||||
private func setCategoryVisible(_ visible: Bool, at index: Int) {
|
||||
let category = viewModel.category(at: index)
|
||||
BookmarksManager.shared().setCategory(category.categoryId, isVisible: visible)
|
||||
if let categoriesHeader = tableView.headerView(forSection: viewModel.sectionIndex(section: .categories)) as? BMCCategoriesHeader {
|
||||
categoriesHeader.isShowAll = viewModel.areAllCategoriesHidden()
|
||||
}
|
||||
}
|
||||
|
||||
private func editCategory(at index: Int, anchor: UIView) {
|
||||
let category = viewModel.category(at: index)
|
||||
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
if let ppc = actionSheet.popoverPresentationController {
|
||||
ppc.sourceView = anchor
|
||||
ppc.sourceRect = anchor.bounds
|
||||
}
|
||||
|
||||
let settings = L("edit")
|
||||
actionSheet.addAction(UIAlertAction(title: settings, style: .default, handler: { _ in
|
||||
self.openCategorySettings(category: category)
|
||||
}))
|
||||
let showHide = L(category.isVisible ? "hide_from_map" : "zoom_to_country")
|
||||
actionSheet.addAction(UIAlertAction(title: showHide, style: .default, handler: { _ in
|
||||
self.setCategoryVisible(!category.isVisible, at: index)
|
||||
let sectionIndex = self.viewModel.sectionIndex(section: .categories)
|
||||
self.tableView.reloadRows(at: [IndexPath(row: index, section: sectionIndex)], with: .none)
|
||||
}))
|
||||
actionSheet.addAction(UIAlertAction(title: L("export_file"), style: .default, handler: { _ in
|
||||
self.shareCategoryFile(at: index, fileType: .text, anchor: anchor)
|
||||
}))
|
||||
actionSheet.addAction(UIAlertAction(title: L("export_file_gpx"), style: .default, handler: { _ in
|
||||
self.shareCategoryFile(at: index, fileType: .gpx, anchor: anchor)
|
||||
}))
|
||||
let delete = L("delete_list")
|
||||
let deleteAction = UIAlertAction(title: delete, style: .destructive, handler: { [viewModel] _ in
|
||||
viewModel!.deleteCategory(at: index)
|
||||
})
|
||||
deleteAction.isEnabled = (viewModel.canDeleteCategory())
|
||||
actionSheet.addAction(deleteAction)
|
||||
let cancel = L("cancel")
|
||||
actionSheet.addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil))
|
||||
|
||||
present(actionSheet, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func openRecentlyDeleted() {
|
||||
let recentlyDeletedController = RecentlyDeletedCategoriesViewController(viewModel: RecentlyDeletedCategoriesViewModel(bookmarksManager: BookmarksManager.shared()))
|
||||
MapViewController.shared()?.navigationController?.pushViewController(recentlyDeletedController, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: BMCView {
|
||||
func update(sections: [BMCSection]) {
|
||||
if sections.isEmpty {
|
||||
tableView.reloadData()
|
||||
} else {
|
||||
let indexes = IndexSet(sections.map { viewModel.sectionIndex(section: $0) })
|
||||
tableView.update { tableView.reloadSections(indexes, with: .automatic) }
|
||||
}
|
||||
}
|
||||
|
||||
func insert(at indexPaths: [IndexPath]) {
|
||||
tableView.insertRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
func delete(at indexPaths: [IndexPath]) {
|
||||
tableView.deleteRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
func conversionFinished(success: Bool) {
|
||||
MWMAlertViewController.activeAlert().closeAlert {
|
||||
if !success {
|
||||
MWMAlertViewController.activeAlert().presentBookmarkConversionErrorAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: UITableViewDataSource {
|
||||
func numberOfSections(in _: UITableView) -> Int {
|
||||
return viewModel.numberOfSections()
|
||||
}
|
||||
|
||||
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch viewModel.sectionType(section: section) {
|
||||
case .categories: fallthrough
|
||||
case .actions, .recentlyDeleted: fallthrough
|
||||
case .notifications: return viewModel.numberOfRows(section: section)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
func dequeCell<Cell>(_ cell: Cell.Type) -> Cell where Cell: UITableViewCell {
|
||||
return tableView.dequeueReusableCell(cell: cell, indexPath: indexPath)
|
||||
}
|
||||
|
||||
switch viewModel.sectionType(section: indexPath.section) {
|
||||
case .categories:
|
||||
return dequeCell(BMCCategoryCell.self).config(category: viewModel.category(at: indexPath.row),
|
||||
delegate: self)
|
||||
case .actions:
|
||||
return dequeCell(BMCActionsCell.self).config(model: viewModel.action(at: indexPath.row))
|
||||
case .recentlyDeleted:
|
||||
return dequeCell(BMCActionsCell.self).config(model: viewModel.recentlyDeletedCategories())
|
||||
case .notifications:
|
||||
return dequeCell(BMCNotificationsCell.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
if viewModel.sectionType(section: indexPath.section) != .categories {
|
||||
return false
|
||||
}
|
||||
|
||||
return viewModel.canDeleteCategory()
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView,
|
||||
commit editingStyle: UITableViewCell.EditingStyle,
|
||||
forRowAt indexPath: IndexPath) {
|
||||
guard editingStyle == .delete,
|
||||
viewModel.sectionType(section: indexPath.section) == .categories else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.deleteCategory(at: indexPath.row)
|
||||
}
|
||||
|
||||
func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
switch viewModel.sectionType(section: section) {
|
||||
case .notifications: fallthrough
|
||||
case .categories: return 48
|
||||
case .actions, .recentlyDeleted: return 24
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
switch viewModel.sectionType(section: section) {
|
||||
case .categories:
|
||||
let categoriesHeader = tableView.dequeueReusableHeaderFooterView(BMCCategoriesHeader.self)
|
||||
categoriesHeader.isShowAll = viewModel.areAllCategoriesHidden()
|
||||
categoriesHeader.title = L("bookmark_lists")
|
||||
categoriesHeader.delegate = self
|
||||
return categoriesHeader
|
||||
case .actions, .recentlyDeleted: return actionsHeader
|
||||
case .notifications: return notificationsHeader
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
switch viewModel.sectionType(section: indexPath.section) {
|
||||
case .categories:
|
||||
openCategory(category: viewModel.category(at: indexPath.row))
|
||||
case .actions:
|
||||
switch viewModel.action(at: indexPath.row) {
|
||||
case .create: createNewCategory()
|
||||
case .exportAll: shareAllCategories(anchor: tableView.cellForRow(at: indexPath))
|
||||
case .import: showImportDialog()
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
case .recentlyDeleted: openRecentlyDeleted()
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: BMCCategoryCellDelegate {
|
||||
func cell(_ cell: BMCCategoryCell, didCheck visible: Bool) {
|
||||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
setCategoryVisible(visible, at: indexPath.row)
|
||||
}
|
||||
|
||||
func cell(_ cell: BMCCategoryCell, didPress moreButton: UIButton) {
|
||||
guard let indexPath = tableView.indexPath(for: cell) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
editCategory(at: indexPath.row, anchor: moreButton)
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: BMCCategoriesHeaderDelegate {
|
||||
func visibilityAction(_ categoriesHeader: BMCCategoriesHeader) {
|
||||
viewModel.updateAllCategoriesVisibility(isShowAll: categoriesHeader.isShowAll)
|
||||
categoriesHeader.isShowAll = viewModel.areAllCategoriesHidden()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: CategorySettingsViewControllerDelegate {
|
||||
func categorySettingsController(_ viewController: CategorySettingsViewController,
|
||||
didEndEditing categoryId: MWMMarkGroupID) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func categorySettingsController(_ viewController: CategorySettingsViewController,
|
||||
didDelete categoryId: MWMMarkGroupID) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCViewController: BookmarksListDelegate {
|
||||
func bookmarksListDidDeleteGroup() {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" 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="17703"/>
|
||||
<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="BMCViewController" customModule="CoMaps" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="actionsHeader" destination="DhR-7O-ccQ" id="PPD-Ov-b69"/>
|
||||
<outlet property="notificationsHeader" destination="G0o-Op-zPp" id="PUK-3H-1q3"/>
|
||||
<outlet property="tableView" destination="2ia-hi-UhQ" id="qJG-eV-PoF"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" canCancelContentTouches="NO" style="grouped" separatorStyle="default" allowsSelectionDuringEditing="YES" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="1" estimatedSectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="2ia-hi-UhQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="TableView:PressBackground"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="-1" id="P7h-mr-lVO"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="2ia-hi-UhQ" secondAttribute="bottom" id="3LS-kF-koO"/>
|
||||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="2ia-hi-UhQ" secondAttribute="trailing" id="Xvt-xX-QwP"/>
|
||||
<constraint firstItem="2ia-hi-UhQ" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="fEc-XL-6gs"/>
|
||||
<constraint firstItem="2ia-hi-UhQ" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="zOH-o1-384"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="10" y="54"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" id="DhR-7O-ccQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="Iuf-Mb-Pmk"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="483" y="-50"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" id="G0o-Op-zPp" customClass="BMCNotificationsHeader" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WYb-xC-cJR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BbP-rE-XH6">
|
||||
<rect key="frame" x="16" y="13.5" width="343" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium14:blackSecondaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="BbP-rE-XH6" secondAttribute="trailing" constant="16" id="0y9-8D-etT"/>
|
||||
<constraint firstItem="BbP-rE-XH6" firstAttribute="centerY" secondItem="WYb-xC-cJR" secondAttribute="centerY" id="E2o-Zj-Sfl"/>
|
||||
<constraint firstItem="BbP-rE-XH6" firstAttribute="leading" secondItem="WYb-xC-cJR" secondAttribute="leading" constant="16" id="NL9-Mg-ULl"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Gks-qZ-sxu"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="WYb-xC-cJR" firstAttribute="top" secondItem="G0o-Op-zPp" secondAttribute="top" id="HYA-0D-zWS"/>
|
||||
<constraint firstItem="WYb-xC-cJR" firstAttribute="leading" secondItem="Gks-qZ-sxu" secondAttribute="leading" id="KA1-YB-hBF"/>
|
||||
<constraint firstItem="WYb-xC-cJR" firstAttribute="trailing" secondItem="Gks-qZ-sxu" secondAttribute="trailing" id="hne-ZG-GAn"/>
|
||||
<constraint firstAttribute="bottom" secondItem="WYb-xC-cJR" secondAttribute="bottom" id="vwe-2c-cjW"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<connections>
|
||||
<outlet property="label" destination="BbP-rE-XH6" id="UTL-17-qdv"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="483" y="35"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="groupTableViewBackgroundColor">
|
||||
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
protocol BMCView: AnyObject {
|
||||
func update(sections: [BMCSection])
|
||||
func delete(at indexPaths: [IndexPath])
|
||||
func insert(at indexPaths: [IndexPath])
|
||||
func conversionFinished(success: Bool)
|
||||
}
|
||||
|
||||
final class BMCDefaultViewModel: NSObject {
|
||||
private let manager = BookmarksManager.shared()
|
||||
|
||||
weak var view: BMCView?
|
||||
|
||||
private var sections: [BMCSection] = []
|
||||
private var categories: [BookmarkGroup] = []
|
||||
private var actions: [BMCAction] = []
|
||||
private var notifications: [BMCNotification] = []
|
||||
|
||||
private(set) var isPendingPermission = false
|
||||
private var isAuthenticated = false
|
||||
private var filesPrepared = false
|
||||
|
||||
let minCategoryNameLength: UInt = 0
|
||||
let maxCategoryNameLength: UInt = 60
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
reloadData()
|
||||
}
|
||||
|
||||
private func getCategories() -> [BookmarkGroup] {
|
||||
manager.sortedUserCategories()
|
||||
}
|
||||
|
||||
private func getActions() -> [BMCAction] {
|
||||
var actions: [BMCAction] = [.create]
|
||||
actions.append(.import)
|
||||
if !manager.areAllCategoriesEmpty() {
|
||||
actions.append(.exportAll)
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
private func getNotifications() -> [BMCNotification] {
|
||||
[.load]
|
||||
}
|
||||
|
||||
func reloadData() {
|
||||
sections.removeAll()
|
||||
|
||||
if manager.areBookmarksLoaded() {
|
||||
sections.append(.categories)
|
||||
categories = getCategories()
|
||||
|
||||
sections.append(.actions)
|
||||
actions = getActions()
|
||||
|
||||
if manager.recentlyDeletedCategoriesCount() != .zero {
|
||||
sections.append(.recentlyDeleted)
|
||||
}
|
||||
} else {
|
||||
sections.append(.notifications)
|
||||
notifications = getNotifications()
|
||||
}
|
||||
|
||||
view?.update(sections: [])
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCDefaultViewModel {
|
||||
func numberOfSections() -> Int {
|
||||
sections.count
|
||||
}
|
||||
|
||||
func sectionType(section: Int) -> BMCSection {
|
||||
sections[section]
|
||||
}
|
||||
|
||||
func sectionIndex(section: BMCSection) -> Int {
|
||||
sections.firstIndex(of: section)!
|
||||
}
|
||||
|
||||
func numberOfRows(section: Int) -> Int {
|
||||
numberOfRows(section: sectionType(section: section))
|
||||
}
|
||||
|
||||
func numberOfRows(section: BMCSection) -> Int {
|
||||
switch section {
|
||||
case .categories: return categories.count
|
||||
case .actions: return actions.count
|
||||
case .recentlyDeleted: return 1
|
||||
case .notifications: return notifications.count
|
||||
}
|
||||
}
|
||||
|
||||
func category(at index: Int) -> BookmarkGroup {
|
||||
categories[index]
|
||||
}
|
||||
|
||||
func canDeleteCategory() -> Bool {
|
||||
categories.count > 1
|
||||
}
|
||||
|
||||
func action(at index: Int) -> BMCAction {
|
||||
actions[index]
|
||||
}
|
||||
|
||||
func recentlyDeletedCategories() -> BMCAction {
|
||||
.recentlyDeleted(Int(manager.recentlyDeletedCategoriesCount()))
|
||||
}
|
||||
|
||||
func notification(at index: Int) -> BMCNotification {
|
||||
notifications[index]
|
||||
}
|
||||
|
||||
func areAllCategoriesHidden() -> Bool {
|
||||
var result = true
|
||||
categories.forEach { if $0.isVisible { result = false } }
|
||||
return result
|
||||
}
|
||||
|
||||
func updateAllCategoriesVisibility(isShowAll: Bool) {
|
||||
manager.setUserCategoriesVisible(isShowAll)
|
||||
}
|
||||
|
||||
func addCategory(name: String) {
|
||||
guard let section = sections.firstIndex(of: .categories) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
categories.insert(manager.category(withId: manager.createCategory(withName: name)), at: 0)
|
||||
view?.insert(at: [IndexPath(row: 0, section: section)])
|
||||
}
|
||||
|
||||
func deleteCategory(at index: Int) {
|
||||
guard let section = sections.firstIndex(of: .categories) else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let category = categories[index]
|
||||
categories.remove(at: index)
|
||||
view?.delete(at: [IndexPath(row: index, section: section)])
|
||||
manager.deleteCategory(category.categoryId)
|
||||
}
|
||||
|
||||
func checkCategory(name: String) -> Bool {
|
||||
manager.checkCategoryName(name)
|
||||
}
|
||||
|
||||
func shareCategoryFile(at index: Int, fileType: KmlFileType, handler: @escaping SharingResultCompletionHandler) {
|
||||
let category = categories[index]
|
||||
manager.shareCategory(category.categoryId, fileType: fileType, completion: handler)
|
||||
}
|
||||
|
||||
func shareAllCategories(handler: @escaping SharingResultCompletionHandler) {
|
||||
manager.shareAllCategories(completion: handler)
|
||||
}
|
||||
|
||||
func importCategories(from urls: [URL]) {
|
||||
// TODO: Refactor this call when the multiple files parsing support will be added to the bookmark_manager.
|
||||
urls.forEach(manager.loadBookmarkFile(_:))
|
||||
}
|
||||
|
||||
func finishShareCategory() {
|
||||
manager.finishSharing()
|
||||
}
|
||||
|
||||
func addToObserverList() {
|
||||
manager.add(self)
|
||||
}
|
||||
|
||||
func removeFromObserverList() {
|
||||
manager.remove(self)
|
||||
}
|
||||
|
||||
func setNotificationsEnabled(_ enabled: Bool) {
|
||||
manager.setNotificationsEnabled(enabled)
|
||||
}
|
||||
|
||||
func areNotificationsEnabled() -> Bool {
|
||||
manager.areNotificationsEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
extension BMCDefaultViewModel: BookmarksObserver {
|
||||
func onBookmarksLoadFinished() {
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func onBookmarksCategoryDeleted(_ groupId: MWMMarkGroupID) {
|
||||
reloadData()
|
||||
}
|
||||
|
||||
func onBookmarkDeleted(_: MWMMarkID) {
|
||||
reloadData()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
struct BMCDefaultViewModel: BMCViewModel {
|
||||
private var sections: [BMCSection] = [.permissions, .categoriesList, .creation]
|
||||
|
||||
func numberOfSections() -> Int {
|
||||
return sections.count
|
||||
}
|
||||
|
||||
func sectionType(section: Int) -> BMCSection {
|
||||
return sections[section]
|
||||
}
|
||||
|
||||
func sectionIndex(section: BMCSection) -> Int {
|
||||
return sections.index(of: section)!
|
||||
}
|
||||
|
||||
func numberOfRows(section _: Int) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func item(indexPath: IndexPath) -> BMCModel {
|
||||
switch sectionType(section: indexPath.section) {
|
||||
case .permissions: return BMCPermission.signup
|
||||
case .categoriesList: return BMCCategory()
|
||||
case .creation: return BMCAction.create
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<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="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="BMCActionCell" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="113"/>
|
||||
<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="112.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="y7C-Jk-OIm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="112.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="h0a-ss-bey">
|
||||
<rect key="frame" x="16" y="16" width="288" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Zb4-aL-Ldq">
|
||||
<rect key="frame" x="16" y="52.5" width="288" height="40"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="Dik-ca-lyT"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button"/>
|
||||
<connections>
|
||||
<action selector="buttonAction" destination="KGk-i7-Jjw" eventType="touchUpInside" id="vVV-Mk-uw3"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="h0a-ss-bey" secondAttribute="trailing" constant="16" id="FFb-bX-gJB"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Zb4-aL-Ldq" secondAttribute="bottom" constant="20" id="WkL-YA-31h"/>
|
||||
<constraint firstItem="Zb4-aL-Ldq" firstAttribute="leading" secondItem="y7C-Jk-OIm" secondAttribute="leading" constant="16" id="am3-bb-og1"/>
|
||||
<constraint firstItem="h0a-ss-bey" firstAttribute="leading" secondItem="y7C-Jk-OIm" secondAttribute="leading" constant="16" id="eDv-Er-8Pb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Zb4-aL-Ldq" secondAttribute="trailing" constant="16" id="eUB-Wu-h3R"/>
|
||||
<constraint firstItem="h0a-ss-bey" firstAttribute="top" secondItem="y7C-Jk-OIm" secondAttribute="top" constant="16" id="gvx-dV-zx1"/>
|
||||
<constraint firstItem="Zb4-aL-Ldq" firstAttribute="top" secondItem="h0a-ss-bey" secondAttribute="bottom" constant="16" id="sNk-mv-NuJ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="y7C-Jk-OIm" secondAttribute="trailing" id="A6B-j1-qyA"/>
|
||||
<constraint firstItem="y7C-Jk-OIm" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="Raz-jA-KQq"/>
|
||||
<constraint firstAttribute="bottom" secondItem="y7C-Jk-OIm" secondAttribute="bottom" id="Tvc-br-Rug"/>
|
||||
<constraint firstItem="y7C-Jk-OIm" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="yaO-ZP-BVr"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="button" destination="Zb4-aL-Ldq" id="Sgz-Oh-anC"/>
|
||||
<outlet property="label" destination="h0a-ss-bey" id="LbI-bT-OKg"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
protocol BMCCategoriesHeaderDelegate: AnyObject {
|
||||
func visibilityAction(_ categoriesHeader: BMCCategoriesHeader)
|
||||
}
|
||||
|
||||
final class BMCCategoriesHeader: UITableViewHeaderFooterView {
|
||||
@IBOutlet private weak var label: UILabel!
|
||||
@IBOutlet private weak var button: UIButton!
|
||||
|
||||
var isShowAll = false {
|
||||
didSet {
|
||||
let title = L(isShowAll ? "bookmark_lists_show_all" : "bookmark_lists_hide_all")
|
||||
UIView.performWithoutAnimation {
|
||||
button.setTitle(title, for: .normal)
|
||||
button.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var title: String? {
|
||||
didSet {
|
||||
title = title?.uppercased()
|
||||
label.text = title
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: BMCCategoriesHeaderDelegate?
|
||||
|
||||
@IBAction private func buttonAction() {
|
||||
delegate?.visibilityAction(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?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" useSafeAreas="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="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"/>
|
||||
<view contentMode="scaleToFill" id="hpL-he-7N6" customClass="BMCCategoriesHeader" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qNi-Yh-Bat">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="48"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PGC-cy-M2K">
|
||||
<rect key="frame" x="16" y="13.5" width="42" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="medium14:blackSecondaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3hn-pU-MI1">
|
||||
<rect key="frame" x="302" y="0.0" width="73" height="48"/>
|
||||
<inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="14" maxY="0.0"/>
|
||||
<state key="normal" title="Show All"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="linkBlueText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="buttonAction" destination="hpL-he-7N6" eventType="touchUpInside" id="mQo-HU-GKM"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="PGC-cy-M2K" firstAttribute="leading" secondItem="qNi-Yh-Bat" secondAttribute="leading" constant="16" id="2OC-uL-Mgr"/>
|
||||
<constraint firstItem="PGC-cy-M2K" firstAttribute="centerY" secondItem="qNi-Yh-Bat" secondAttribute="centerY" id="44v-Uo-bj7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="3hn-pU-MI1" secondAttribute="trailing" id="HNq-6x-QDD"/>
|
||||
<constraint firstAttribute="bottom" secondItem="3hn-pU-MI1" secondAttribute="bottom" id="VCh-vb-yPn"/>
|
||||
<constraint firstItem="3hn-pU-MI1" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="PGC-cy-M2K" secondAttribute="trailing" constant="4" id="hGm-gr-ABM"/>
|
||||
<constraint firstItem="3hn-pU-MI1" firstAttribute="top" secondItem="qNi-Yh-Bat" secondAttribute="top" id="thZ-ah-uC2"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="aw1-gI-QsW" firstAttribute="trailing" secondItem="qNi-Yh-Bat" secondAttribute="trailing" id="4t4-Hg-Xce"/>
|
||||
<constraint firstItem="aw1-gI-QsW" firstAttribute="bottom" secondItem="qNi-Yh-Bat" secondAttribute="bottom" id="Ptg-FC-sav"/>
|
||||
<constraint firstItem="qNi-Yh-Bat" firstAttribute="leading" secondItem="aw1-gI-QsW" secondAttribute="leading" id="XuM-Ot-YqM"/>
|
||||
<constraint firstItem="qNi-Yh-Bat" firstAttribute="top" secondItem="aw1-gI-QsW" secondAttribute="top" id="cp0-ul-dGE"/>
|
||||
</constraints>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="aw1-gI-QsW"/>
|
||||
<connections>
|
||||
<outlet property="button" destination="3hn-pU-MI1" id="aAd-Ov-dIM"/>
|
||||
<outlet property="label" destination="PGC-cy-M2K" id="D2m-ae-X57"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="483" y="-148"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
protocol BMCCategoryCellDelegate: AnyObject {
|
||||
func cell(_ cell: BMCCategoryCell, didCheck visible: Bool)
|
||||
func cell(_ cell: BMCCategoryCell, didPress moreButton: UIButton)
|
||||
}
|
||||
|
||||
final class BMCCategoryCell: MWMTableViewCell {
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var subtitleLabel: UILabel!
|
||||
|
||||
@IBOutlet private weak var moreButton: UIButton! {
|
||||
didSet {
|
||||
moreButton.setImage(#imageLiteral(resourceName: "ic24PxMore"), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet weak var visibleCheckmark: Checkmark!
|
||||
|
||||
private var category: BookmarkGroup? {
|
||||
didSet {
|
||||
categoryUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
private weak var delegate: BMCCategoryCellDelegate?
|
||||
|
||||
func config(category: BookmarkGroup, delegate: BMCCategoryCellDelegate) -> UITableViewCell {
|
||||
self.category = category
|
||||
self.delegate = delegate
|
||||
return self
|
||||
}
|
||||
|
||||
@IBAction func onVisibleChanged(_ sender: Checkmark) {
|
||||
delegate?.cell(self, didCheck: sender.isChecked)
|
||||
}
|
||||
|
||||
@IBAction private func moreAction() {
|
||||
delegate?.cell(self, didPress: moreButton)
|
||||
}
|
||||
|
||||
func categoryUpdated() {
|
||||
guard let category = category else { return }
|
||||
titleLabel.text = category.title
|
||||
subtitleLabel.text = category.placesCountTitle()
|
||||
visibleCheckmark.isChecked = category.isVisible
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
|
||||
<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="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="BMCCategoryCell" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="60"/>
|
||||
<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="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wcq-KH-q74" customClass="Checkmark" customModule="CoMaps" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="56" height="60.5"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="56" id="iRO-vl-eYM"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="image" keyPath="offImage" value="ic_eye_off"/>
|
||||
<userDefinedRuntimeAttribute type="image" keyPath="onImage" value="ic_eye_on"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="onVisibleChanged:" destination="KGk-i7-Jjw" eventType="valueChanged" id="fV8-pr-hNc"/>
|
||||
</connections>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="My Places" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jut-eq-wia">
|
||||
<rect key="frame" x="56" y="10" width="204" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackPrimaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Public • 12 Bookmarks" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jBd-Tj-RiW">
|
||||
<rect key="frame" x="56" y="35" width="204" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5781785102739726" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular14:blackSecondaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2gi-fk-gR6">
|
||||
<rect key="frame" x="264" y="0.0" width="56" height="60"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="56" id="ot8-q5-ynR"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="MWMGray"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="moreAction" destination="KGk-i7-Jjw" eventType="touchUpInside" id="zmP-yn-CEM"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="jBd-Tj-RiW" firstAttribute="leading" secondItem="wcq-KH-q74" secondAttribute="trailing" id="BBj-bB-xCt"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2gi-fk-gR6" secondAttribute="trailing" id="Ddb-qq-tEN"/>
|
||||
<constraint firstAttribute="bottom" secondItem="jBd-Tj-RiW" secondAttribute="bottom" constant="10" id="ITj-Sq-UKz"/>
|
||||
<constraint firstAttribute="bottom" secondItem="wcq-KH-q74" secondAttribute="bottom" constant="-0.5" id="LnB-MK-oB3"/>
|
||||
<constraint firstItem="jut-eq-wia" firstAttribute="leading" secondItem="wcq-KH-q74" secondAttribute="trailing" id="RBv-Jh-88n"/>
|
||||
<constraint firstItem="jBd-Tj-RiW" firstAttribute="top" secondItem="jut-eq-wia" secondAttribute="bottom" constant="4" id="TT5-VN-D7j"/>
|
||||
<constraint firstItem="2gi-fk-gR6" firstAttribute="leading" secondItem="jut-eq-wia" secondAttribute="trailing" constant="4" id="Xx2-s7-Jt0"/>
|
||||
<constraint firstItem="2gi-fk-gR6" firstAttribute="leading" secondItem="jBd-Tj-RiW" secondAttribute="trailing" constant="4" id="b4E-PQ-Lca"/>
|
||||
<constraint firstItem="wcq-KH-q74" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="fYG-79-pgs"/>
|
||||
<constraint firstItem="wcq-KH-q74" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="lui-lx-nl7"/>
|
||||
<constraint firstAttribute="bottom" secondItem="2gi-fk-gR6" secondAttribute="bottom" id="maE-v3-UlZ"/>
|
||||
<constraint firstItem="2gi-fk-gR6" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="nbD-ba-3tr"/>
|
||||
<constraint firstItem="jut-eq-wia" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="omd-cs-RXb"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="moreButton" destination="2gi-fk-gR6" id="t3r-XO-zDV"/>
|
||||
<outlet property="subtitleLabel" destination="jBd-Tj-RiW" id="D3j-45-I9U"/>
|
||||
<outlet property="titleLabel" destination="jut-eq-wia" id="pHy-5L-bhq"/>
|
||||
<outlet property="visibleCheckmark" destination="wcq-KH-q74" id="X4U-VF-MLB"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="52.799999999999997" y="47.676161919040482"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="ic_eye_off" width="24" height="24"/>
|
||||
<image name="ic_eye_on" width="24" height="24"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
@objc protocol CategorySettingsViewControllerDelegate: AnyObject {
|
||||
func categorySettingsController(_ viewController: CategorySettingsViewController,
|
||||
didEndEditing categoryId: MWMMarkGroupID)
|
||||
func categorySettingsController(_ viewController: CategorySettingsViewController,
|
||||
didDelete categoryId: MWMMarkGroupID)
|
||||
}
|
||||
|
||||
final class CategorySettingsViewController: MWMTableViewController {
|
||||
private enum Sections: Int {
|
||||
case info
|
||||
case description
|
||||
case delete
|
||||
case count
|
||||
}
|
||||
|
||||
private enum InfoSectionRows: Int {
|
||||
case title
|
||||
}
|
||||
|
||||
private let bookmarkGroup: BookmarkGroup
|
||||
private var noteCell: MWMNoteCell?
|
||||
private var changesMade = false
|
||||
private var newName: String?
|
||||
private var newAnnotation: String?
|
||||
|
||||
@objc weak var delegate: CategorySettingsViewControllerDelegate?
|
||||
|
||||
@objc init(bookmarkGroup: BookmarkGroup) {
|
||||
self.bookmarkGroup = bookmarkGroup
|
||||
super.init(style: .grouped)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = L("edit")
|
||||
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)
|
||||
}
|
||||
|
||||
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 1
|
||||
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: bookmarkGroup.title, delegate: self, hint: L("bookmarks_error_message_empty_list_name"))
|
||||
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: bookmarkGroup.detailedAnnotation,
|
||||
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("delete_list"),
|
||||
enabled: BookmarksManager.shared().userCategoriesCount() > 1)
|
||||
return cell
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func onSave() {
|
||||
view.endEditing(true)
|
||||
if let newName = newName, !newName.isEmpty {
|
||||
BookmarksManager.shared().setCategory(bookmarkGroup.categoryId, name: newName)
|
||||
changesMade = true
|
||||
}
|
||||
|
||||
if let newAnnotation = newAnnotation {
|
||||
BookmarksManager.shared().setCategory(bookmarkGroup.categoryId, description: newAnnotation)
|
||||
changesMade = true
|
||||
}
|
||||
|
||||
delegate?.categorySettingsController(self, didEndEditing: bookmarkGroup.categoryId)
|
||||
}
|
||||
}
|
||||
|
||||
extension CategorySettingsViewController: BookmarkTitleCellDelegate {
|
||||
func didFinishEditingTitle(_ title: String) {
|
||||
newName = title
|
||||
}
|
||||
}
|
||||
|
||||
extension CategorySettingsViewController: MWMNoteCellDelegate {
|
||||
func cell(_ cell: MWMNoteCell, didChangeSizeAndText text: String) {
|
||||
UIView.setAnimationsEnabled(false)
|
||||
tableView.refresh()
|
||||
UIView.setAnimationsEnabled(true)
|
||||
tableView.scrollToRow(at: IndexPath(item: 0, section: Sections.description.rawValue),
|
||||
at: .bottom,
|
||||
animated: true)
|
||||
}
|
||||
|
||||
func cell(_ cell: MWMNoteCell, didFinishEditingWithText text: String) {
|
||||
newAnnotation = text
|
||||
}
|
||||
}
|
||||
|
||||
extension CategorySettingsViewController: MWMButtonCellDelegate {
|
||||
func cellDidPressButton(_ cell: UITableViewCell) {
|
||||
BookmarksManager.shared().deleteCategory(bookmarkGroup.categoryId)
|
||||
delegate?.categorySettingsController(self, didDelete: bookmarkGroup.categoryId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
final class BMCNotificationsCell: MWMTableViewCell {
|
||||
@IBOutlet private weak var spinner: UIView! {
|
||||
didSet {
|
||||
circularProgress = MWMCircularProgress(parentView: spinner)
|
||||
circularProgress.state = .spinner
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet private weak var label: UILabel! {
|
||||
didSet {
|
||||
label.text = L("load_kmz_title")
|
||||
}
|
||||
}
|
||||
|
||||
private var circularProgress: MWMCircularProgress!
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?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="retina4_7" 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"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="KGk-i7-Jjw" customClass="BMCNotificationsCell" customModule="CoMaps" customModuleProvider="target" propertyAccessControl="none">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="48"/>
|
||||
<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="48"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="23z-Ov-GrT">
|
||||
<rect key="frame" x="16" y="12" width="24" height="24"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="24" id="Duf-kw-YwR"/>
|
||||
<constraint firstAttribute="height" constant="24" id="o5g-od-uLZ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ydm-M6-i2D">
|
||||
<rect key="frame" x="56" y="13.5" width="233" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="regular16:blackSecondaryText"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="ydm-M6-i2D" firstAttribute="leading" secondItem="23z-Ov-GrT" secondAttribute="trailing" constant="16" id="IFP-zv-ycJ"/>
|
||||
<constraint firstItem="ydm-M6-i2D" firstAttribute="centerY" secondItem="23z-Ov-GrT" secondAttribute="centerY" id="XQ3-Ic-lXU"/>
|
||||
<constraint firstItem="23z-Ov-GrT" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="bIW-Rw-EGD"/>
|
||||
<constraint firstItem="23z-Ov-GrT" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="12" id="er5-i4-cs1"/>
|
||||
<constraint firstAttribute="bottom" secondItem="23z-Ov-GrT" secondAttribute="bottom" constant="12" id="gkY-we-imN"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="ydm-M6-i2D" secondAttribute="trailing" constant="16" id="nAH-MD-erS"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
|
||||
<connections>
|
||||
<outlet property="label" destination="ydm-M6-i2D" id="FJz-8r-GV0"/>
|
||||
<outlet property="spinner" destination="23z-Ov-GrT" id="9Me-CJ-fX6"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="139" y="154"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
final class BMCNotificationsHeader: UIView {
|
||||
@IBOutlet private weak var label: UILabel! {
|
||||
didSet {
|
||||
label.text = L("bookmark_lists").uppercased()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
final class RecentlyDeletedCategoriesViewController: MWMViewController {
|
||||
|
||||
private enum LocalizedStrings {
|
||||
static let clear = L("clear")
|
||||
static let delete = L("delete")
|
||||
static let deleteAll = L("delete_all")
|
||||
static let recover = L("recover")
|
||||
static let recoverAll = L("recover_all")
|
||||
static let recentlyDeleted = L("bookmarks_recently_deleted")
|
||||
static let searchInTheList = L("search_in_the_list")
|
||||
}
|
||||
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
|
||||
private lazy var clearButton = UIBarButtonItem(title: LocalizedStrings.clear, style: .done, target: self, action: #selector(clearButtonDidTap))
|
||||
private lazy var recoverButton = UIBarButtonItem(title: LocalizedStrings.recover, style: .done, target: self, action: #selector(recoverButtonDidTap))
|
||||
private lazy var deleteButton = UIBarButtonItem(title: LocalizedStrings.delete, style: .done, target: self, action: #selector(deleteButtonDidTap))
|
||||
private let searchController = UISearchController(searchResultsController: nil)
|
||||
private let viewModel: RecentlyDeletedCategoriesViewModel
|
||||
|
||||
init(viewModel: RecentlyDeletedCategoriesViewModel = RecentlyDeletedCategoriesViewModel(bookmarksManager: BookmarksManager.shared())) {
|
||||
self.viewModel = viewModel
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
viewModel.stateDidChange = { [weak self] state in
|
||||
self?.updateState(state)
|
||||
}
|
||||
viewModel.filteredDataSourceDidChange = { [weak self] dataSource in
|
||||
guard let self else { return }
|
||||
if dataSource.isEmpty {
|
||||
self.tableView.reloadData()
|
||||
} else {
|
||||
let indexes = IndexSet(integersIn: 0...dataSource.count - 1)
|
||||
self.tableView.update { self.tableView.reloadSections(indexes, with: .automatic) }
|
||||
}
|
||||
}
|
||||
viewModel.onCategoriesIsEmpty = { [weak self] in
|
||||
self?.goBack()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupView()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
navigationController?.setToolbarHidden(true, animated: true)
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
extendedLayoutIncludesOpaqueBars = true
|
||||
setupNavigationBar()
|
||||
setupToolBar()
|
||||
setupSearchBar()
|
||||
setupTableView()
|
||||
layout()
|
||||
updateState(viewModel.state)
|
||||
}
|
||||
|
||||
private func setupNavigationBar() {
|
||||
title = LocalizedStrings.recentlyDeleted
|
||||
}
|
||||
|
||||
private func setupToolBar() {
|
||||
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
toolbarItems = [flexibleSpace, recoverButton, flexibleSpace, deleteButton, flexibleSpace]
|
||||
navigationController?.isToolbarHidden = false
|
||||
}
|
||||
|
||||
private func setupSearchBar() {
|
||||
searchController.searchBar.placeholder = LocalizedStrings.searchInTheList
|
||||
searchController.obscuresBackgroundDuringPresentation = false
|
||||
searchController.hidesNavigationBarDuringPresentation = false
|
||||
searchController.searchBar.delegate = self
|
||||
searchController.searchBar.applyTheme()
|
||||
navigationItem.searchController = searchController
|
||||
navigationItem.hidesSearchBarWhenScrolling = true
|
||||
}
|
||||
|
||||
private func setupTableView() {
|
||||
tableView.setStyles([.tableView, .pressBackground])
|
||||
tableView.allowsMultipleSelectionDuringEditing = true
|
||||
tableView.register(cell: RecentlyDeletedTableViewCell.self)
|
||||
tableView.setEditing(true, animated: false)
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
}
|
||||
|
||||
private func layout() {
|
||||
view.addSubview(tableView)
|
||||
NSLayoutConstraint.activate([
|
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func updateState(_ state: RecentlyDeletedCategoriesViewModel.State) {
|
||||
switch state {
|
||||
case .searching:
|
||||
navigationController?.setToolbarHidden(true, animated: false)
|
||||
searchController.searchBar.isUserInteractionEnabled = true
|
||||
case .nothingSelected:
|
||||
navigationController?.setToolbarHidden(false, animated: false)
|
||||
recoverButton.title = LocalizedStrings.recoverAll
|
||||
deleteButton.title = LocalizedStrings.deleteAll
|
||||
searchController.searchBar.isUserInteractionEnabled = true
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
tableView.indexPathsForSelectedRows?.forEach { tableView.deselectRow(at: $0, animated: true)}
|
||||
case .someSelected:
|
||||
navigationController?.setToolbarHidden(false, animated: false)
|
||||
recoverButton.title = LocalizedStrings.recover
|
||||
deleteButton.title = LocalizedStrings.delete
|
||||
searchController.searchBar.isUserInteractionEnabled = false
|
||||
navigationItem.rightBarButtonItem = clearButton
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
@objc private func clearButtonDidTap() {
|
||||
viewModel.cancelSelecting()
|
||||
}
|
||||
|
||||
@objc private func recoverButtonDidTap() {
|
||||
viewModel.recoverSelectedCategories()
|
||||
}
|
||||
|
||||
@objc private func deleteButtonDidTap() {
|
||||
viewModel.deleteSelectedCategories()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
extension RecentlyDeletedCategoriesViewController: UITableViewDataSource {
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
viewModel.filteredDataSource.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
viewModel.filteredDataSource[section].content.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(cell: RecentlyDeletedTableViewCell.self, indexPath: indexPath)
|
||||
let category = viewModel.filteredDataSource[indexPath.section].content[indexPath.row]
|
||||
cell.configureWith(RecentlyDeletedTableViewCell.ViewModel(category))
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
extension RecentlyDeletedCategoriesViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
guard tableView.isEditing else {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
return
|
||||
}
|
||||
viewModel.selectCategory(at: indexPath)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
||||
guard tableView.isEditing else { return }
|
||||
guard let selectedIndexPaths = tableView.indexPathsForSelectedRows, !selectedIndexPaths.isEmpty else {
|
||||
viewModel.deselectAllCategories()
|
||||
return
|
||||
}
|
||||
viewModel.deselectCategory(at: indexPath)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
let deleteAction = UIContextualAction(style: .destructive, title: LocalizedStrings.delete) { [weak self] (_, _, completion) in
|
||||
self?.viewModel.deleteCategory(at: indexPath)
|
||||
completion(true)
|
||||
}
|
||||
let recoverAction = UIContextualAction(style: .normal, title: LocalizedStrings.recover) { [weak self] (_, _, completion) in
|
||||
self?.viewModel.recoverCategory(at: indexPath)
|
||||
completion(true)
|
||||
}
|
||||
return UISwipeActionsConfiguration(actions: [deleteAction, recoverAction])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UISearchBarDelegate
|
||||
extension RecentlyDeletedCategoriesViewController: UISearchBarDelegate {
|
||||
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
|
||||
searchBar.setShowsCancelButton(true, animated: true)
|
||||
viewModel.startSearching()
|
||||
}
|
||||
|
||||
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
|
||||
searchBar.setShowsCancelButton(false, animated: true)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.text = nil
|
||||
searchBar.resignFirstResponder()
|
||||
viewModel.cancelSearching()
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
viewModel.search(searchText)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
final class RecentlyDeletedCategoriesViewModel: NSObject {
|
||||
|
||||
typealias BookmarksManager = RecentlyDeletedCategoriesManager & BookmarksObservable
|
||||
|
||||
enum Section: CaseIterable {
|
||||
struct Model: Equatable {
|
||||
var content: [RecentlyDeletedCategory]
|
||||
}
|
||||
|
||||
case main
|
||||
}
|
||||
|
||||
enum State {
|
||||
case searching
|
||||
case nothingSelected
|
||||
case someSelected
|
||||
}
|
||||
|
||||
private var recentlyDeletedCategoriesManager: BookmarksManager
|
||||
private var dataSource: [Section.Model] = [] {
|
||||
didSet {
|
||||
if dataSource.isEmpty {
|
||||
onCategoriesIsEmpty?()
|
||||
}
|
||||
}
|
||||
}
|
||||
private(set) var state: State = .nothingSelected
|
||||
private(set) var filteredDataSource: [Section.Model] = []
|
||||
private(set) var selectedIndexPaths: [IndexPath] = []
|
||||
private(set) var searchText = String()
|
||||
|
||||
var stateDidChange: ((State) -> Void)?
|
||||
var filteredDataSourceDidChange: (([Section.Model]) -> Void)?
|
||||
var onCategoriesIsEmpty: (() -> Void)?
|
||||
|
||||
init(bookmarksManager: BookmarksManager) {
|
||||
self.recentlyDeletedCategoriesManager = bookmarksManager
|
||||
super.init()
|
||||
subscribeOnBookmarksManagerNotifications()
|
||||
fetchRecentlyDeletedCategories()
|
||||
}
|
||||
|
||||
deinit {
|
||||
unsubscribeFromBookmarksManagerNotifications()
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
private func subscribeOnBookmarksManagerNotifications() {
|
||||
recentlyDeletedCategoriesManager.add(self)
|
||||
}
|
||||
|
||||
private func unsubscribeFromBookmarksManagerNotifications() {
|
||||
recentlyDeletedCategoriesManager.remove(self)
|
||||
}
|
||||
|
||||
private func updateState(to newState: State) {
|
||||
guard state != newState else { return }
|
||||
state = newState
|
||||
stateDidChange?(state)
|
||||
}
|
||||
|
||||
private func updateFilteredDataSource(_ dataSource: [Section.Model]) {
|
||||
filteredDataSource = dataSource.filtered(using: searchText)
|
||||
filteredDataSourceDidChange?(filteredDataSource)
|
||||
}
|
||||
|
||||
private func updateSelectionAtIndexPath(_ indexPath: IndexPath, isSelected: Bool) {
|
||||
if isSelected {
|
||||
updateState(to: .someSelected)
|
||||
} else {
|
||||
let allDeselected = dataSource.allSatisfy { $0.content.isEmpty }
|
||||
updateState(to: allDeselected ? .nothingSelected : .someSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeCategories(at indexPaths: [IndexPath], completion: ([URL]) -> Void) {
|
||||
var fileToRemoveURLs: [URL]
|
||||
if indexPaths.isEmpty {
|
||||
// Remove all without selection.
|
||||
fileToRemoveURLs = dataSource.flatMap { $0.content.map { $0.fileURL } }
|
||||
dataSource.removeAll()
|
||||
} else {
|
||||
fileToRemoveURLs = [URL]()
|
||||
indexPaths.forEach { [weak self] indexPath in
|
||||
guard let self else { return }
|
||||
let fileToRemoveURL = self.filteredDataSource[indexPath.section].content[indexPath.row].fileURL
|
||||
self.dataSource[indexPath.section].content.removeAll { $0.fileURL == fileToRemoveURL }
|
||||
fileToRemoveURLs.append(fileToRemoveURL)
|
||||
}
|
||||
}
|
||||
updateFilteredDataSource(dataSource)
|
||||
updateState(to: .nothingSelected)
|
||||
completion(fileToRemoveURLs)
|
||||
}
|
||||
|
||||
private func removeSelectedCategories(completion: ([URL]) -> Void) {
|
||||
let removeAll = selectedIndexPaths.isEmpty || selectedIndexPaths.count == dataSource.flatMap({ $0.content }).count
|
||||
removeCategories(at: removeAll ? [] : selectedIndexPaths, completion: completion)
|
||||
selectedIndexPaths.removeAll()
|
||||
updateState(to: .nothingSelected)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
extension RecentlyDeletedCategoriesViewModel {
|
||||
func fetchRecentlyDeletedCategories() {
|
||||
let categories = recentlyDeletedCategoriesManager.getRecentlyDeletedCategories()
|
||||
guard !categories.isEmpty else { return }
|
||||
dataSource = [Section.Model(content: categories)]
|
||||
updateFilteredDataSource(dataSource)
|
||||
}
|
||||
|
||||
func deleteCategory(at indexPath: IndexPath) {
|
||||
removeCategories(at: [indexPath]) { recentlyDeletedCategoriesManager.deleteRecentlyDeletedCategory(at: $0) }
|
||||
}
|
||||
|
||||
func deleteSelectedCategories() {
|
||||
removeSelectedCategories { recentlyDeletedCategoriesManager.deleteRecentlyDeletedCategory(at: $0) }
|
||||
}
|
||||
|
||||
func recoverCategory(at indexPath: IndexPath) {
|
||||
removeCategories(at: [indexPath]) { recentlyDeletedCategoriesManager.recoverRecentlyDeletedCategories(at: $0) }
|
||||
}
|
||||
|
||||
func recoverSelectedCategories() {
|
||||
removeSelectedCategories { recentlyDeletedCategoriesManager.recoverRecentlyDeletedCategories(at: $0) }
|
||||
}
|
||||
|
||||
func startSelecting() {
|
||||
updateState(to: .nothingSelected)
|
||||
}
|
||||
|
||||
func selectCategory(at indexPath: IndexPath) {
|
||||
selectedIndexPaths.append(indexPath)
|
||||
updateState(to: .someSelected)
|
||||
}
|
||||
|
||||
func deselectCategory(at indexPath: IndexPath) {
|
||||
selectedIndexPaths.removeAll { $0 == indexPath }
|
||||
if selectedIndexPaths.isEmpty {
|
||||
updateState(to: state == .searching ? .searching : .nothingSelected)
|
||||
}
|
||||
}
|
||||
|
||||
func selectAllCategories() {
|
||||
selectedIndexPaths = dataSource.enumerated().flatMap { sectionIndex, section in
|
||||
section.content.indices.map { IndexPath(row: $0, section: sectionIndex) }
|
||||
}
|
||||
updateState(to: .someSelected)
|
||||
}
|
||||
|
||||
func deselectAllCategories() {
|
||||
selectedIndexPaths.removeAll()
|
||||
updateState(to: state == .searching ? .searching : .nothingSelected)
|
||||
}
|
||||
|
||||
func cancelSelecting() {
|
||||
selectedIndexPaths.removeAll()
|
||||
updateState(to: .nothingSelected)
|
||||
}
|
||||
|
||||
func startSearching() {
|
||||
updateState(to: .searching)
|
||||
}
|
||||
|
||||
func cancelSearching() {
|
||||
searchText.removeAll()
|
||||
selectedIndexPaths.removeAll()
|
||||
updateFilteredDataSource(dataSource)
|
||||
updateState(to: .nothingSelected)
|
||||
}
|
||||
|
||||
func search(_ searchText: String) {
|
||||
updateState(to: .searching)
|
||||
guard !searchText.isEmpty else {
|
||||
cancelSearching()
|
||||
return
|
||||
}
|
||||
self.searchText = searchText
|
||||
updateFilteredDataSource(dataSource)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BookmarksObserver
|
||||
|
||||
extension RecentlyDeletedCategoriesViewModel: BookmarksObserver {
|
||||
func onBookmarksLoadFinished() {
|
||||
fetchRecentlyDeletedCategories()
|
||||
}
|
||||
|
||||
func onRecentlyDeletedBookmarksCategoriesChanged() {
|
||||
fetchRecentlyDeletedCategories()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private extension Array where Element == RecentlyDeletedCategoriesViewModel.Section.Model {
|
||||
func filtered(using searchText: String) -> [Element] {
|
||||
let filteredArray = map { section in
|
||||
let filteredContent = section.content.filter {
|
||||
guard !searchText.isEmpty else { return true }
|
||||
return $0.title.localizedCaseInsensitiveContains(searchText)
|
||||
}
|
||||
return RecentlyDeletedCategoriesViewModel.Section.Model(content: filteredContent)
|
||||
}
|
||||
return filteredArray
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
final class RecentlyDeletedTableViewCell: UITableViewCell {
|
||||
|
||||
struct ViewModel: Equatable, Hashable {
|
||||
let fileName: String
|
||||
let fileURL: URL
|
||||
let deletionDate: Date
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureWith(_ viewModel: ViewModel) {
|
||||
textLabel?.text = viewModel.fileName
|
||||
detailTextLabel?.text = DateTimeFormatter.dateString(from: viewModel.deletionDate,
|
||||
dateStyle: .medium,
|
||||
timeStyle: .medium)
|
||||
}
|
||||
}
|
||||
|
||||
extension RecentlyDeletedTableViewCell.ViewModel {
|
||||
init(_ category: RecentlyDeletedCategory) {
|
||||
self.fileName = category.title
|
||||
self.fileURL = category.fileURL
|
||||
self.deletionDate = category.deletionDate
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue