From d3ed067476b80da9eecc8da270f933762d419bbd Mon Sep 17 00:00:00 2001 From: Dmitry Svetlichny Date: Tue, 18 Feb 2025 15:00:32 +0200 Subject: [PATCH 1/3] Add TracksChangeAppearanceViewController --- OsmAnd.xcodeproj/project.pbxproj | 4 + ...TracksChangeAppearanceViewController.swift | 125 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift diff --git a/OsmAnd.xcodeproj/project.pbxproj b/OsmAnd.xcodeproj/project.pbxproj index cfa0b6da1e..2d5c3b1075 100644 --- a/OsmAnd.xcodeproj/project.pbxproj +++ b/OsmAnd.xcodeproj/project.pbxproj @@ -1361,6 +1361,7 @@ C573544F2AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = C573544D2AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.mm */; }; C57354502AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C573544E2AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.xib */; }; C5735AF62AFE9CB500638715 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C5735AF42AFE9CB500638715 /* InfoPlist.strings */; }; + C575798C2D64A0A30016957D /* TracksChangeAppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */; }; C57E26F32D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57E26F12D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift */; }; C57E26F42D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C57E26F22D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib */; }; C5879A49299FB96300F53249 /* uk_tts.js in Resources */ = {isa = PBXBuildFile; fileRef = C5879A48299FB96300F53249 /* uk_tts.js */; }; @@ -4991,6 +4992,7 @@ C5735B452AFE9D8700638715 /* uz-Cyrl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "uz-Cyrl"; path = "Resources/Localizations/uz-Cyrl.lproj/InfoPlist.strings"; sourceTree = ""; }; C5735B462AFE9D8800638715 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = Resources/Localizations/vi.lproj/InfoPlist.strings; sourceTree = ""; }; C5735B472AFE9D8900638715 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = Resources/Localizations/cy.lproj/InfoPlist.strings; sourceTree = ""; }; + C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksChangeAppearanceViewController.swift; sourceTree = ""; }; C57E26F12D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachRoadsBannerCell.swift; sourceTree = ""; }; C57E26F22D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AttachRoadsBannerCell.xib; sourceTree = ""; }; C5879A48299FB96300F53249 /* uk_tts.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = uk_tts.js; path = ../resources/voice/uk/uk_tts.js; sourceTree = ""; }; @@ -11973,6 +11975,7 @@ C50E32832CA57DDB00EEC41F /* TracksFilterDetailsViewController.swift */, C5A887E32CAD7B8E00DA2979 /* TracksSearchFilter.swift */, C57245D82CDF3B70004F1F19 /* TracksSortModeHelper.swift */, + C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */, ); path = MyPlaces; sourceTree = ""; @@ -17140,6 +17143,7 @@ DA5A845026C563A900F274C7 /* OAPOILayer.mm in Sources */, DA2EC47B29FBB90A00ECEB37 /* WidgetType.swift in Sources */, 4631377929AE6D8A000B0258 /* OACarPlayHistoryListController.mm in Sources */, + C575798C2D64A0A30016957D /* TracksChangeAppearanceViewController.swift in Sources */, DA5A834026C563A800F274C7 /* OAFolderCardCollectionViewCell.m in Sources */, DA5A849226C563A900F274C7 /* OAMeasurementEditingContext.mm in Sources */, DA5A856226C563A900F274C7 /* OAWaypointHelper.mm in Sources */, diff --git a/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift b/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift new file mode 100644 index 0000000000..6b1d343e27 --- /dev/null +++ b/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift @@ -0,0 +1,125 @@ +// +// TracksChangeAppearanceViewController.swift +// OsmAnd Maps +// +// Created by Dmitry Svetlichny on 18.02.2025. +// Copyright © 2025 OsmAnd. All rights reserved. +// + +import UIKit + +final class TracksChangeAppearanceViewController: OABaseNavbarViewController { + private static let buttonTitleKey = "buttonTitleKey" + private static let directionArrowsRowKey = "directionArrowsRowKey" + private static let startFinishIconsRowKey = "startFinishIconsRowKey" + private static let coloringRowKey = "coloringRowKey" + private static let coloringDescRowKey = "coloringDescRowKey" + private static let widthRowKey = "widthRowKey" + private static let widthDescrRow = "widthDescrRow" + private static let splitIntervalRow = "splitIntervalRow" + private static let splitIntervalDescrRow = "splitIntervalDescrRow" + + private var tracks: [TrackItem] + + init(tracks: [TrackItem]) { + self.tracks = tracks + super.init(nibName: "OABaseNavbarViewController", bundle: nil) + initTableData() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func registerCells() { + addCell(OASimpleTableViewCell.reuseIdentifier) + addCell(OAButtonTableViewCell.reuseIdentifier) + } + + override func getTitle() -> String? { + localizedString("change_appearance") + } + + override func getLeftNavbarButtonTitle() -> String? { + localizedString("shared_string_cancel") + } + + override func getRightNavbarButtons() -> [UIBarButtonItem] { + return [createRightNavbarButton(localizedString("shared_string_done"), iconName: nil, action: #selector(onRightNavbarButtonPressed), menu: nil)] + } + + override func isNavbarSeparatorVisible() -> Bool { + false + } + + override func generateData() { + tableData.clearAllData() + + let directionSection = tableData.createNewSection() + let directionArrowsRow = directionSection.createNewRow() + directionArrowsRow.cellType = OAButtonTableViewCell.reuseIdentifier + directionArrowsRow.key = Self.directionArrowsRowKey + directionArrowsRow.title = localizedString("gpx_direction_arrows") + directionArrowsRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) + let startFinishIconsRow = directionSection.createNewRow() + startFinishIconsRow.cellType = OAButtonTableViewCell.reuseIdentifier + startFinishIconsRow.key = Self.startFinishIconsRowKey + startFinishIconsRow.title = localizedString("track_show_start_finish_icons") + startFinishIconsRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) + + let coloringSection = tableData.createNewSection() + let coloringRow = coloringSection.createNewRow() + coloringRow.cellType = OAButtonTableViewCell.reuseIdentifier + coloringRow.key = Self.coloringRowKey + coloringRow.title = localizedString("shared_string_coloring") + coloringRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) + let coloringDescrRow = coloringSection.createNewRow() + coloringDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier + coloringDescrRow.key = Self.coloringDescRowKey + coloringDescrRow.title = localizedString("each_favourite_point_own_icon") + + let widthSection = tableData.createNewSection() + let widthRow = widthSection.createNewRow() + widthRow.cellType = OAButtonTableViewCell.reuseIdentifier + widthRow.key = Self.widthRowKey + widthRow.title = localizedString("routing_attr_width_name") + widthRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) + let widthDescrRow = widthSection.createNewRow() + widthDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier + widthDescrRow.key = Self.widthDescrRow + widthDescrRow.title = localizedString("unchanged_parameter_summary") + + let splitIntervalSection = tableData.createNewSection() + let splitIntervalRow = splitIntervalSection.createNewRow() + splitIntervalRow.cellType = OAButtonTableViewCell.reuseIdentifier + splitIntervalRow.key = Self.splitIntervalRow + splitIntervalRow.title = localizedString("gpx_split_interval") + splitIntervalRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) + let splitIntervalDescrRow = splitIntervalSection.createNewRow() + splitIntervalDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier + splitIntervalDescrRow.key = Self.splitIntervalDescrRow + splitIntervalDescrRow.title = localizedString("unchanged_parameter_summary") + } + + override func getRow(_ indexPath: IndexPath?) -> UITableViewCell? { + guard let indexPath else { return nil } + let item = tableData.item(for: indexPath) + if item.cellType == OAButtonTableViewCell.reuseIdentifier { + let cell = tableView.dequeueReusableCell(withIdentifier: OAButtonTableViewCell.reuseIdentifier) as! OAButtonTableViewCell + cell.selectionStyle = .none + cell.leftIconVisibility(false) + cell.descriptionVisibility(false) + cell.titleLabel.text = item.title + return cell + } else if item.cellType == OASimpleTableViewCell.reuseIdentifier { + let cell = tableView.dequeueReusableCell(withIdentifier: OASimpleTableViewCell.reuseIdentifier) as! OASimpleTableViewCell + cell.selectionStyle = .none + cell.leftIconVisibility(false) + cell.titleVisibility(false) + cell.descriptionLabel.text = item.title + return cell + } + + return nil + } +} From 1395b8ad8dd43aa8bd70ea2faa223d8b840b6b8c Mon Sep 17 00:00:00 2001 From: Dmitry Svetlichny Date: Thu, 20 Feb 2025 09:29:11 +0200 Subject: [PATCH 2/3] add func onNavbarChangeAppearanceButtonClicked --- .../MyPlaces/TracksViewController.swift | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/Controllers/MyPlaces/TracksViewController.swift b/Sources/Controllers/MyPlaces/TracksViewController.swift index 7f54bb8dd0..39dacf76eb 100644 --- a/Sources/Controllers/MyPlaces/TracksViewController.swift +++ b/Sources/Controllers/MyPlaces/TracksViewController.swift @@ -556,13 +556,17 @@ final class TracksViewController: OACompoundViewController, UITableViewDelegate, let uploadToOsmAction = UIAction(title: localizedString("upload_to_osm_short"), image: UIImage.icCustomUploadToOpenstreetmapOutlined) { [weak self] _ in self?.onNavbarUploadToOsmButtonClicked() } + let changeAppearanceAction = UIAction(title: localizedString("change_appearance"), image: UIImage.icCustomAppearanceOutlined) { [weak self] _ in + self?.onNavbarChangeAppearanceButtonClicked() + } let deleteAction = UIAction(title: localizedString("shared_string_delete"), image: UIImage.icCustomTrashOutlined, attributes: .destructive) { [weak self] _ in self?.onNavbarDeleteButtonClicked() } let mapTrackOptionsActions = UIMenu(title: "", options: .displayInline, children: [showOnMapAction, exportAction, uploadToOsmAction]) + let changeAppearanceItemsActions = UIMenu(title: "", options: .displayInline, children: [changeAppearanceAction]) let deleteItemsActions = UIMenu(title: "", options: .displayInline, children: [deleteAction]) - menuActions.append(contentsOf: [mapTrackOptionsActions, deleteItemsActions]) + menuActions.append(contentsOf: [mapTrackOptionsActions, changeAppearanceItemsActions, deleteItemsActions]) } let menu = UIMenu(title: "", image: nil, children: menuActions) @@ -960,6 +964,27 @@ final class TracksViewController: OACompoundViewController, UITableViewDelegate, } } + @objc private func onNavbarChangeAppearanceButtonClicked() { + let allTracks: [GpxDataItem] = selectedTracks + selectedFolders.flatMap { folderName in + if let folder = currentFolder.getSubFolders().first(where: { $0.getDirName(includingSubdirs: false) == folderName }) { + return folder.getFlattenedTrackItems().compactMap { $0.dataItem } + } else if let smartFolder = smartFolderHelper.getSmartFolder(name: folderName) { + return smartFolder.getTrackItems().compactMap { $0.dataItem } + } + return [] + } + + let trackItems = Set(allTracks.toTrackItems()) + guard !trackItems.isEmpty else { return } + let vc = TracksChangeAppearanceViewController(tracks: trackItems) + let navigationController = UINavigationController(rootViewController: vc) + navigationController.modalPresentationStyle = .custom + present(navigationController, animated: true) { [weak self] in + guard let self else { return } + self.onNavbarCancelButtonClicked() + } + } + @objc private func onNavbarDeleteButtonClicked() { if hasSelectedItems() { let tracksInSelectedFolders = selectedFolders.reduce(0) { (result, folderName) -> Int in From 84a7f9ef646028d21c8263a5352faf87d5617d4d Mon Sep 17 00:00:00 2001 From: Dmitry Svetlichny Date: Thu, 20 Feb 2025 17:38:48 +0200 Subject: [PATCH 3/3] Add Logics for Change appearance --- OsmAnd.xcodeproj/project.pbxproj | 8 + .../en.lproj/Localizable.strings | 4 +- .../Controllers/MyPlaces/AppearanceData.swift | 75 ++++++++ .../MyPlaces/ChangeTracksAppearanceTask.swift | 112 ++++++++++++ ...TracksChangeAppearanceViewController.swift | 166 +++++++++++++++++- 5 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 Sources/Controllers/MyPlaces/AppearanceData.swift create mode 100644 Sources/Controllers/MyPlaces/ChangeTracksAppearanceTask.swift diff --git a/OsmAnd.xcodeproj/project.pbxproj b/OsmAnd.xcodeproj/project.pbxproj index 2d5c3b1075..4f33d94614 100644 --- a/OsmAnd.xcodeproj/project.pbxproj +++ b/OsmAnd.xcodeproj/project.pbxproj @@ -1362,6 +1362,8 @@ C57354502AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C573544E2AAB5144008B3214 /* OAMapSettingsTerrainParametersViewController.xib */; }; C5735AF62AFE9CB500638715 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C5735AF42AFE9CB500638715 /* InfoPlist.strings */; }; C575798C2D64A0A30016957D /* TracksChangeAppearanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */; }; + C57693172D66159200E4F772 /* AppearanceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57693162D66159200E4F772 /* AppearanceData.swift */; }; + C57693192D67148400E4F772 /* ChangeTracksAppearanceTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57693182D67148400E4F772 /* ChangeTracksAppearanceTask.swift */; }; C57E26F32D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C57E26F12D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift */; }; C57E26F42D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C57E26F22D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib */; }; C5879A49299FB96300F53249 /* uk_tts.js in Resources */ = {isa = PBXBuildFile; fileRef = C5879A48299FB96300F53249 /* uk_tts.js */; }; @@ -4993,6 +4995,8 @@ C5735B462AFE9D8800638715 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = Resources/Localizations/vi.lproj/InfoPlist.strings; sourceTree = ""; }; C5735B472AFE9D8900638715 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = Resources/Localizations/cy.lproj/InfoPlist.strings; sourceTree = ""; }; C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksChangeAppearanceViewController.swift; sourceTree = ""; }; + C57693162D66159200E4F772 /* AppearanceData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceData.swift; sourceTree = ""; }; + C57693182D67148400E4F772 /* ChangeTracksAppearanceTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeTracksAppearanceTask.swift; sourceTree = ""; }; C57E26F12D3D663F00A8A3D5 /* AttachRoadsBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachRoadsBannerCell.swift; sourceTree = ""; }; C57E26F22D3D663F00A8A3D5 /* AttachRoadsBannerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AttachRoadsBannerCell.xib; sourceTree = ""; }; C5879A48299FB96300F53249 /* uk_tts.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = uk_tts.js; path = ../resources/voice/uk/uk_tts.js; sourceTree = ""; }; @@ -11976,6 +11980,8 @@ C5A887E32CAD7B8E00DA2979 /* TracksSearchFilter.swift */, C57245D82CDF3B70004F1F19 /* TracksSortModeHelper.swift */, C575798B2D64A0A30016957D /* TracksChangeAppearanceViewController.swift */, + C57693162D66159200E4F772 /* AppearanceData.swift */, + C57693182D67148400E4F772 /* ChangeTracksAppearanceTask.swift */, ); path = MyPlaces; sourceTree = ""; @@ -16593,6 +16599,7 @@ DA842ECD2A0B998E0030866E /* WidgetInfoCreator.swift in Sources */, 460E9AC12B71111000411854 /* CloudTrashItemMenuViewController.swift in Sources */, 323F04C82C6577340058DD78 /* ShowHideWhitewaterSportsAction.swift in Sources */, + C57693172D66159200E4F772 /* AppearanceData.swift in Sources */, 32388EE226E6AF770021F829 /* OAQuickSearchResultTableViewCell.m in Sources */, DA5F77B628742E85000A2BFF /* OABackupStatus.m in Sources */, DAABE4EE2A1657AF00569F71 /* Color+INT.swift in Sources */, @@ -16972,6 +16979,7 @@ DA5A845226C563A900F274C7 /* OAMapLayers.mm in Sources */, DA4ABEEB2876DA5D00B996EF /* OASettingsCategoryItems.m in Sources */, FA4195592AAB55E6007CBBAB /* ShadowTransporentView.swift in Sources */, + C57693192D67148400E4F772 /* ChangeTracksAppearanceTask.swift in Sources */, DA5A81DE26C563A700F274C7 /* OAStreet.mm in Sources */, 325CFBEB2B5052E000090DF2 /* TracksViewController.swift in Sources */, 32DE2BE42B6D13730025F2B9 /* OATwoButtonsTableViewCell.swift in Sources */, diff --git a/Resources/Localizations/en.lproj/Localizable.strings b/Resources/Localizations/en.lproj/Localizable.strings index 9cb5768fce..553ec29254 100644 --- a/Resources/Localizations/en.lproj/Localizable.strings +++ b/Resources/Localizations/en.lproj/Localizable.strings @@ -2363,9 +2363,11 @@ "shared_string_open" = "Open"; "time_closed" = "Closed"; "time_will_open" = "Will open in"; - +"shared_string_unchanged" = "Unchanged"; "osm_live_active" = "Active"; "menu_all_trips" = "All Tracks"; +"each_favourite_point_own_icon" = "Each favourite point would keep its own icon."; +"unchanged_parameter_summary" = "Each track will retain its own parameters."; "public_transport_ped_route_title" = "The route on foot is approximately %@, and may be faster than public transport"; "calc_pedestrian_route" = "Calculate pedestrian route"; diff --git a/Sources/Controllers/MyPlaces/AppearanceData.swift b/Sources/Controllers/MyPlaces/AppearanceData.swift new file mode 100644 index 0000000000..2af4443170 --- /dev/null +++ b/Sources/Controllers/MyPlaces/AppearanceData.swift @@ -0,0 +1,75 @@ +// +// AppearanceData.swift +// OsmAnd Maps +// +// Created by Dmitry Svetlichny on 19.02.2025. +// Copyright © 2025 OsmAnd. All rights reserved. +// + +import UIKit + +protocol AppearanceChangedDelegate: AnyObject { + func onAppearanceChanged() +} + +struct AppearancePair { + let shouldReset: Bool + let value: Any? +} + +final class AppearanceData: NSObject { + private var map: [GpxParameter: AppearancePair] = [:] + weak var delegate: AppearanceChangedDelegate? + + override init() { + super.init() + } + + init(data: AppearanceData) { + self.map = data.map + super.init() + } + + private func notifyAppearanceModified() { + delegate?.onAppearanceChanged() + } + + private func isValidValue(parameter: GpxParameter, value: Any?) -> Bool { + guard parameter.isAppearanceParameter() else { return false } + return true + } + + func getParameter(for parameter: GpxParameter) -> T? { + guard let pair = map[parameter] else { return nil } + return pair.value as? T + } + + func setParameter(_ parameter: GpxParameter, value: Any?) { + if isValidValue(parameter: parameter, value: value) { + map[parameter] = AppearancePair(shouldReset: false, value: value) + notifyAppearanceModified() + } + } + + func resetParameter(_ parameter: GpxParameter) { + guard parameter.isAppearanceParameter() else { return } + map[parameter] = AppearancePair(shouldReset: true, value: nil) + notifyAppearanceModified() + } + + func shouldResetParameter(_ parameter: GpxParameter) -> Bool { + guard parameter.isAppearanceParameter() else { return false } + return map[parameter]?.shouldReset ?? false + } + + func shouldResetAnything() -> Bool { + let params = GpxParameter.values() + for i in 0.. + private let callback: (() -> Void)? + + init(data: AppearanceData, items: Set, callback: (() -> Void)? = nil) { + self.data = data + self.items = items + self.callback = callback + gpxDbHelper = GpxDbHelper.shared + } + + private func doInBackground() { + let resetAnything = data.shouldResetAnything() + for track in items { + if let file = track.getFile() { + let gpxFile = resetAnything ? getGpxFile(for: file) : nil + updateTrackAppearance(file: file, gpxFile: gpxFile) + } else if track.isShowCurrentTrack { + updateCurrentTrackAppearance() + } + } + } + + private func updateTrackAppearance(file: KFile, gpxFile: GpxFile?) { + let callback = getGpxDataItemCallback(gpxFile: gpxFile) + if let dataItem = gpxDbHelper?.getItem(file: file, callback: callback) { + updateTrackAppearance(item: dataItem, gpxFile: gpxFile) + } + } + + private func getGpxFile(for file: KFile) -> GpxFile? { + let gpxFile = GpxUtilities.shared.loadGpxFile(file: file) + return gpxFile.error == nil ? gpxFile : nil + } + + private func updateCurrentTrackAppearance() { + let settings = OAAppSettings.sharedManager() + if let color: Int = data.getParameter(for: GpxParameter.color) { + settings?.currentTrackColor.set(Int32(color)) + } + if let coloringType: String = data.getParameter(for: GpxParameter.coloringType) { + let requiredValue = ColoringType.companion.requireValueOf(purpose: ColoringPurpose.track, name: coloringType) + settings?.currentTrackColoringType.set(Int32(requiredValue.ordinal)) + let routeInfoAttribute = ColoringType.companion.getRouteInfoAttribute(name: coloringType) + settings?.routeInfoAttribute.set(routeInfoAttribute) + } + if let width: String = data.getParameter(for: GpxParameter.width) { + settings?.currentTrackWidth.set(width) + } + if let showArrows: Bool = data.getParameter(for: GpxParameter.showArrows) { + settings?.currentTrackShowArrows.set(showArrows) + } + if let showStartFinish: Bool = data.getParameter(for: GpxParameter.showStartFinish) { + settings?.currentTrackShowStartFinish.set(showStartFinish) + } + if let trackVisualizationType: String = data.getParameter(for: GpxParameter.trackVisualizationType), + let intValue = Int32(trackVisualizationType) { + settings?.currentTrackVisualization3dByType.set(intValue) + } + if let gradientPalette: String = data.getParameter(for: GpxParameter.colorPalette) { + settings?.gradientPalettes.set(gradientPalette) + } + } + + private func getGpxDataItemCallback(gpxFile: GpxFile?) -> GpxDbHelperGpxDataItemCallback { + let handler = GpxDataItemHandler() + handler.onGpxDataItemReady = { [weak self] item in + self?.updateTrackAppearance(item: item, gpxFile: gpxFile) + } + + return handler + } + + private func updateTrackAppearance(item: GpxDataItem, gpxFile: GpxFile?) { + for parameter in GpxParameter.companion.getAppearanceParameters() { + if data.shouldResetParameter(parameter) { + if let gpxFile = gpxFile { + item.readGpxAppearanceParameter(gpxFile: gpxFile, parameter: parameter) + } + } else if let value: Any = data.getParameter(for: parameter) { + item.setParameter(parameter: parameter, value: value) + } + } + + gpxDbHelper?.updateDataItem(item: item) + } + + private func onPostExecute() { + callback?() + } + + func execute() { + DispatchQueue.global(qos: .default).async { + self.doInBackground() + DispatchQueue.main.async { + self.onPostExecute() + } + } + } +} diff --git a/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift b/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift index 6b1d343e27..e29f0b1538 100644 --- a/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift +++ b/Sources/Controllers/MyPlaces/TracksChangeAppearanceViewController.swift @@ -8,8 +8,7 @@ import UIKit -final class TracksChangeAppearanceViewController: OABaseNavbarViewController { - private static let buttonTitleKey = "buttonTitleKey" +final class TracksChangeAppearanceViewController: OABaseNavbarViewController, AppearanceChangedDelegate { private static let directionArrowsRowKey = "directionArrowsRowKey" private static let startFinishIconsRowKey = "startFinishIconsRowKey" private static let coloringRowKey = "coloringRowKey" @@ -19,10 +18,14 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { private static let splitIntervalRow = "splitIntervalRow" private static let splitIntervalDescrRow = "splitIntervalDescrRow" - private var tracks: [TrackItem] + private var tracks: Set + private var initialData: AppearanceData + private var data: AppearanceData - init(tracks: [TrackItem]) { + init(tracks: Set) { self.tracks = tracks + self.initialData = Self.buildAppearanceData() + self.data = AppearanceData(data: self.initialData) super.init(nibName: "OABaseNavbarViewController", bundle: nil) initTableData() } @@ -36,6 +39,11 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { addCell(OAButtonTableViewCell.reuseIdentifier) } + override func viewDidLoad() { + super.viewDidLoad() + self.data.delegate = self + } + override func getTitle() -> String? { localizedString("change_appearance") } @@ -60,19 +68,16 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { directionArrowsRow.cellType = OAButtonTableViewCell.reuseIdentifier directionArrowsRow.key = Self.directionArrowsRowKey directionArrowsRow.title = localizedString("gpx_direction_arrows") - directionArrowsRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) let startFinishIconsRow = directionSection.createNewRow() startFinishIconsRow.cellType = OAButtonTableViewCell.reuseIdentifier startFinishIconsRow.key = Self.startFinishIconsRowKey startFinishIconsRow.title = localizedString("track_show_start_finish_icons") - startFinishIconsRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) let coloringSection = tableData.createNewSection() let coloringRow = coloringSection.createNewRow() coloringRow.cellType = OAButtonTableViewCell.reuseIdentifier coloringRow.key = Self.coloringRowKey coloringRow.title = localizedString("shared_string_coloring") - coloringRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) let coloringDescrRow = coloringSection.createNewRow() coloringDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier coloringDescrRow.key = Self.coloringDescRowKey @@ -83,7 +88,6 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { widthRow.cellType = OAButtonTableViewCell.reuseIdentifier widthRow.key = Self.widthRowKey widthRow.title = localizedString("routing_attr_width_name") - widthRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) let widthDescrRow = widthSection.createNewRow() widthDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier widthDescrRow.key = Self.widthDescrRow @@ -94,7 +98,6 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { splitIntervalRow.cellType = OAButtonTableViewCell.reuseIdentifier splitIntervalRow.key = Self.splitIntervalRow splitIntervalRow.title = localizedString("gpx_split_interval") - splitIntervalRow.setObj(localizedString("shared_string_unchanged"), forKey: Self.buttonTitleKey) let splitIntervalDescrRow = splitIntervalSection.createNewRow() splitIntervalDescrRow.cellType = OASimpleTableViewCell.reuseIdentifier splitIntervalDescrRow.key = Self.splitIntervalDescrRow @@ -110,6 +113,14 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { cell.leftIconVisibility(false) cell.descriptionVisibility(false) cell.titleLabel.text = item.title + let config = UIButton.Configuration.plain() + cell.button.configuration = config + if let key = item.key { + cell.button.menu = createStateSelectionMenu(for: key) + } + cell.button.showsMenuAsPrimaryAction = true + cell.button.changesSelectionAsPrimaryAction = true + cell.button.contentHorizontalAlignment = .right return cell } else if item.cellType == OASimpleTableViewCell.reuseIdentifier { let cell = tableView.dequeueReusableCell(withIdentifier: OASimpleTableViewCell.reuseIdentifier) as! OASimpleTableViewCell @@ -122,4 +133,141 @@ final class TracksChangeAppearanceViewController: OABaseNavbarViewController { return nil } + + override func onRightNavbarButtonPressed() { + let task = ChangeTracksAppearanceTask(data: self.data, items: self.tracks) { [weak self] in + guard let self = self else { return } + self.dismiss(animated: true) { + OsmAndApp.swiftInstance().updateGpxTracksOnMapObservable.notifyEvent() + } + } + + task.execute() + } + + private static func buildAppearanceData() -> AppearanceData { + let data = AppearanceData() + for parameter in GpxParameter.companion.getAppearanceParameters() { + data.setParameter(parameter, value: nil) + } + + return data + } + + private func createStateSelectionMenu(for key: String) -> UIMenu { + if key == Self.directionArrowsRowKey { + return createArrowsMenu() + } else if key == Self.startFinishIconsRowKey { + return createStartFinishMenu() + } else if key == Self.coloringRowKey { + return createColoringMenu() + } else if key == Self.widthRowKey { + return createWidthMenu() + } else if key == Self.splitIntervalRow { + return createStartFinishMenu() + } else { + return UIMenu() + } + } + + private func createArrowsMenu() -> UIMenu { + let unchangedAction = UIAction(title: localizedString("shared_string_unchanged"), state: .on) { _ in } + let originalAction = UIAction(title: localizedString("simulate_location_movement_speed_original"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.resetParameter(GpxParameter.showArrows) + } + let unchangedOriginalMenu = inlineMenu(withActions: [unchangedAction, originalAction]) + + let onAction = UIAction(title: localizedString("shared_string_on"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.setParameter(GpxParameter.showArrows, value: true) + } + let offAction = UIAction(title: localizedString("shared_string_off"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.setParameter(GpxParameter.showArrows, value: false) + } + let onOffMenu = inlineMenu(withActions: [onAction, offAction]) + + return UIMenu(title: "", options: .singleSelection, children: [unchangedOriginalMenu, onOffMenu]) + } + + private func createStartFinishMenu() -> UIMenu { + let unchangedAction = UIAction(title: localizedString("shared_string_unchanged"), state: .on) { _ in } + let originalAction = UIAction(title: localizedString("simulate_location_movement_speed_original"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.resetParameter(GpxParameter.showStartFinish) + } + let unchangedOriginalMenu = inlineMenu(withActions: [unchangedAction, originalAction]) + + let onAction = UIAction(title: localizedString("shared_string_on"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.setParameter(GpxParameter.showStartFinish, value: true) + } + let offAction = UIAction(title: localizedString("shared_string_off"), state: .off) { [weak self] _ in + guard let self else { return } + self.data.setParameter(GpxParameter.showStartFinish, value: false) + } + let onOffMenu = inlineMenu(withActions: [onAction, offAction]) + + return UIMenu(title: "", options: .singleSelection, children: [unchangedOriginalMenu, onOffMenu]) + } + + private func createColoringMenu() -> UIMenu { + let unchangedAction = UIAction(title: localizedString("shared_string_unchanged"), state: .on) { _ in } + let originalAction = UIAction(title: localizedString("simulate_location_movement_speed_original"), state: .off) { _ in } + let unchangedOriginalMenu = inlineMenu(withActions: [unchangedAction, originalAction]) + + let solidColorAction = UIAction(title: localizedString("track_coloring_solid"), state: .off) { _ in } + let solidColorMenu = inlineMenu(withActions: [solidColorAction]) + + let altitudeAction = UIAction(title: localizedString("altitude"), state: .off) { _ in } + let speedAction = UIAction(title: localizedString("shared_string_speed"), state: .off) { _ in } + let slopeAction = UIAction(title: localizedString("shared_string_slope"), state: .off) { _ in } + let gradientColorMenu = inlineMenu(withActions: [altitudeAction, speedAction, slopeAction]) + + let roadTypeAction = UIAction(title: localizedString("routeInfo_roadClass_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let surfaceAction = UIAction(title: localizedString("routeInfo_surface_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let smoothhnessAction = UIAction(title: localizedString("routeInfo_smoothness_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let winterRoadsAction = UIAction(title: localizedString("routeInfo_winter_ice_road_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let thicknessRoadsAction = UIAction(title: localizedString("routeInfo_tracktype_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let horseRoadsAction = UIAction(title: localizedString("routeInfo_horse_scale_name"), image: UIImage(named: "ic_payment_label_pro"), state: .off) { _ in } + let proColorMenu = inlineMenu(withActions: [roadTypeAction, surfaceAction, smoothhnessAction, winterRoadsAction, thicknessRoadsAction, horseRoadsAction]) + + return UIMenu(title: "", options: .singleSelection, children: [unchangedOriginalMenu, solidColorMenu, gradientColorMenu, proColorMenu]) + } + + private func createWidthMenu() -> UIMenu { + let unchangedAction = UIAction(title: localizedString("shared_string_unchanged"), state: .on) { _ in } + let originalAction = UIAction(title: localizedString("simulate_location_movement_speed_original"), state: .off) { _ in } + let unchangedOriginalMenu = inlineMenu(withActions: [unchangedAction, originalAction]) + + let thinAction = UIAction(title: localizedString("rendering_value_thin_name"), state: .off) { _ in } + let mediumAction = UIAction(title: localizedString("rendering_value_medium_w_name"), state: .off) { _ in } + let boldAction = UIAction(title: localizedString("rendering_value_bold_name"), state: .off) { _ in } + let widthMenu = inlineMenu(withActions: [thinAction, mediumAction, boldAction]) + + let customAction = UIAction(title: localizedString("shared_string_custom"), state: .off) { _ in } + let customWidthMenu = inlineMenu(withActions: [customAction]) + + return UIMenu(title: "", options: .singleSelection, children: [unchangedOriginalMenu, widthMenu, customWidthMenu]) + } + + private func createSplitIntervalMenu() -> UIMenu { + let unchangedAction = UIAction(title: localizedString("shared_string_unchanged"), state: .on) { _ in } + let originalAction = UIAction(title: localizedString("simulate_location_movement_speed_original"), state: .off) { _ in } + let unchangedOriginalMenu = inlineMenu(withActions: [unchangedAction, originalAction]) + + let onAction = UIAction(title: localizedString("shared_string_on"), state: .off) { _ in } + let offAction = UIAction(title: localizedString("shared_string_off"), state: .off) { _ in } + let onOffMenu = inlineMenu(withActions: [onAction, offAction]) + + return UIMenu(title: "", options: .singleSelection, children: [unchangedOriginalMenu, onOffMenu]) + } + + private func inlineMenu(withActions actions: [UIAction]) -> UIMenu { + UIMenu(title: "", options: .displayInline, children: actions) + } + + func onAppearanceChanged() { + } }