diff --git a/OsmAnd.xcodeproj/project.pbxproj b/OsmAnd.xcodeproj/project.pbxproj index dc0ce729fa..2608a581e4 100644 --- a/OsmAnd.xcodeproj/project.pbxproj +++ b/OsmAnd.xcodeproj/project.pbxproj @@ -713,6 +713,7 @@ 46964F762C345448007F3938 /* ColorPaletteHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46964F752C345448007F3938 /* ColorPaletteHelper.swift */; }; 46993C0A2986BCB70047F2F5 /* img_help_announcement_time_day@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 46993C092986BCB60047F2F5 /* img_help_announcement_time_day@3x.png */; }; 46A27270281DD05E00FF591D /* osm-carto.render.xml in Resources */ = {isa = PBXBuildFile; fileRef = 46A2726F281DD05D00FF591D /* osm-carto.render.xml */; }; + 46A35EF52C53F9510089B21C /* DirectoryObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A35EF42C53F9510089B21C /* DirectoryObserver.swift */; }; 46A8FD33297D985700AF9C0C /* OAUninstallSpeedCamerasViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 46A8FD32297D985700AF9C0C /* OAUninstallSpeedCamerasViewController.mm */; }; 46AB58A729C4C979003B2FB9 /* OAWikipediaImagesSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46AB58A629C4C979003B2FB9 /* OAWikipediaImagesSettingsViewController.m */; }; 46AD052A2C29E2060032C3B7 /* color-palette in Resources */ = {isa = PBXBuildFile; fileRef = 46AD05292C29E2060032C3B7 /* color-palette */; }; @@ -4199,6 +4200,7 @@ 46964F752C345448007F3938 /* ColorPaletteHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteHelper.swift; sourceTree = ""; }; 46993C092986BCB60047F2F5 /* img_help_announcement_time_day@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "img_help_announcement_time_day@3x.png"; path = "Resources/Icons/img_help_announcement_time_day@3x.png"; sourceTree = ""; }; 46A2726F281DD05D00FF591D /* osm-carto.render.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = "osm-carto.render.xml"; path = "../resources/rendering_styles/osm-carto.render.xml"; sourceTree = ""; }; + 46A35EF42C53F9510089B21C /* DirectoryObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryObserver.swift; sourceTree = ""; }; 46A8FD31297D982200AF9C0C /* OAUninstallSpeedCamerasViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OAUninstallSpeedCamerasViewController.h; sourceTree = ""; }; 46A8FD32297D985700AF9C0C /* OAUninstallSpeedCamerasViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = OAUninstallSpeedCamerasViewController.mm; sourceTree = ""; }; 46AB58A529C4C967003B2FB9 /* OAWikipediaImagesSettingsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OAWikipediaImagesSettingsViewController.h; sourceTree = ""; }; @@ -13371,6 +13373,7 @@ 0590D19B2BF88AF400C62AF5 /* ConcurrentDictionary.swift */, 32A0CDF52C29B07B00FDD8E0 /* Model3dHelper */, 4665DB6D2C20BBFD00F897EC /* ColorsPaletteUtils.swift */, + 46A35EF42C53F9510089B21C /* DirectoryObserver.swift */, ); path = Helpers; sourceTree = ""; @@ -17136,6 +17139,7 @@ DA5A836B26C563A800F274C7 /* OADestinationItemsListViewController.mm in Sources */, DA5A81B026C563A700F274C7 /* OADownloadsManager.m in Sources */, DA5A84D326C563A900F274C7 /* OAMapSettingsOverlayUnderlayScreen.mm in Sources */, + 46A35EF52C53F9510089B21C /* DirectoryObserver.swift in Sources */, DA5A813126C563A700F274C7 /* OAWebClient.mm in Sources */, DA5A833926C563A800F274C7 /* OAFoldersCell.m in Sources */, 3234101A28889D1B00E90FBA /* OARowInfo.m in Sources */, diff --git a/Sources/Controllers/DashboardOnMap/MapSettings/OAMapSettingsTerrainParametersViewController.mm b/Sources/Controllers/DashboardOnMap/MapSettings/OAMapSettingsTerrainParametersViewController.mm index a959c121a9..651b7991e8 100644 --- a/Sources/Controllers/DashboardOnMap/MapSettings/OAMapSettingsTerrainParametersViewController.mm +++ b/Sources/Controllers/DashboardOnMap/MapSettings/OAMapSettingsTerrainParametersViewController.mm @@ -122,6 +122,11 @@ - (void)commonInit if (!_basePaletteColorItem) _basePaletteColorItem = [_gradientColorsCollection getGradientPaletteBy:[[TerrainMode getDefaultMode:_terrainMode.type] getKeyName]]; _currentPaletteColorItem = _basePaletteColorItem; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onColorPalettesFilesUpdated:) + name:ColorPaletteHelper.colorPalettesUpdatedNotification + object:nil]; } _minZoom = _baseMinZoom; @@ -129,6 +134,55 @@ - (void)commonInit _currentAlpha = _baseAlpha; } +- (void)onColorPalettesFilesUpdated:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:NSDictionary.class] || _terrainType != EOATerrainSettingsTypePalette) + return; + + NSDictionary *colorPaletteFiles = (NSDictionary *) notification.object; + if (!colorPaletteFiles) + return; + NSString *currentPaletteFile = [_terrainMode getMainFile]; + BOOL reloadData = NO; + BOOL deleted = NO; + for (NSString *colorPaletteFile in colorPaletteFiles.allKeys) + { + if ([_gradientColorsCollection hasTerrainGradientPaletteBy:colorPaletteFile] + || [colorPaletteFiles[colorPaletteFile] isEqualToString:DirectoryObserver.createdKey]) + { + reloadData = YES; + if ([currentPaletteFile isEqualToString:colorPaletteFile] + && [colorPaletteFiles[colorPaletteFile] isEqualToString:DirectoryObserver.deletedKey]) + { + deleted = YES; + break; + } + } + } + if (reloadData) + { + _gradientColorsCollection = [[GradientColorsCollection alloc] initWithTerrainType:_terrainMode.type]; + _sortedPaletteColorItems = [NSMutableArray arrayWithArray:[_gradientColorsCollection getPaletteColors]]; + if (deleted) + _basePaletteColorItem = [_gradientColorsCollection getDefaultGradientPalette]; + else + _basePaletteColorItem = [_gradientColorsCollection getGradientPaletteBy:[_terrainMode getKeyName]]; + _currentPaletteColorItem = _basePaletteColorItem; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self generateData]; + [UIView transitionWithView:self.tableView + duration:0.35f + options:UIViewAnimationOptionTransitionCrossDissolve + animations:^(void) + { + [self.tableView reloadData]; + } + completion:nil]; + }); + } +} + - (void)configureGPXVerticalExaggerationScale:(CGFloat)scale { if (_terrainType == EOAGPXSettingsTypeVerticalExaggeration) @@ -224,7 +278,7 @@ - (NSString *)getHeaderText result = OALocalizedString(@"visibility"); break; case EOATerrainSettingsTypePalette: - result = _terrainMode.translateName; + result = [_terrainMode getDescription]; break; case EOATerrainSettingsTypeZoomLevels: result = OALocalizedString(@"shared_string_zoom_levels"); @@ -792,6 +846,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N OASimpleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[OASimpleTableViewCell getCellIdentifier]]; [cell leftIconVisibility:NO]; [cell descriptionVisibility:NO]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.titleLabel.text = [_currentPaletteColorItem toHumanString]; cell.titleLabel.textColor = UIColorFromRGB(color_extra_text_gray); cell.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; @@ -811,7 +866,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [cell setCollectionHandler:paletteHandler]; [cell rightActionButtonVisibility:NO]; - [cell.collectionView reloadData]; + [cell.collectionView performBatchUpdates:^{ + for (NSInteger i = 0; i < cell.collectionView.numberOfSections; i ++) + { + [cell.collectionView reloadSections:[NSIndexSet indexSetWithIndex:i]]; + } + } completion:nil]; [cell layoutIfNeeded]; return cell; } diff --git a/Sources/Controllers/Map/Layers/OAGPXLayer.mm b/Sources/Controllers/Map/Layers/OAGPXLayer.mm index 289052b824..966635b1f3 100644 --- a/Sources/Controllers/Map/Layers/OAGPXLayer.mm +++ b/Sources/Controllers/Map/Layers/OAGPXLayer.mm @@ -66,6 +66,7 @@ @implementation OAGPXLayer QHash< QString, QList > _cachedColors; QHash< QString, QList > _cachedWallColors; NSMutableDictionary *_cachedTrackWidth; + NSMutableDictionary *_updatedColorPaletteFiles; NSOperationQueue *_splitLabelsQueue; NSObject* _splitLock; @@ -98,8 +99,14 @@ - (void) initLayer _cachedTracks = [NSMutableDictionary dictionary]; _cachedTrackWidth = [NSMutableDictionary dictionary]; + _updatedColorPaletteFiles = [NSMutableDictionary dictionary]; _plugin = (OASRTMPlugin *) [OAPluginsHelper getPlugin:OASRTMPlugin.class]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onColorPalettesFilesUpdated:) + name:ColorPaletteHelper.colorPalettesUpdatedNotification + object:nil]; } - (void) resetLayer @@ -137,6 +144,33 @@ - (BOOL) updateLayer return YES; } +- (void)onColorPalettesFilesUpdated:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:NSDictionary.class]) + return; + + NSDictionary *colorPaletteFiles = (NSDictionary *) notification.object; + if (!colorPaletteFiles) + return; + BOOL refresh = NO; + for (NSString *colorPaletteFile in colorPaletteFiles) + { + if ([colorPaletteFile hasPrefix:ColorPaletteHelper.routePrefix]) + { + _updatedColorPaletteFiles[colorPaletteFile] = colorPaletteFiles[colorPaletteFile]; + refresh = YES; + } + } + if (refresh) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.mapViewController runWithRenderSync:^{ + [self refreshGpxTracks:_gpxDocs reset:YES]; + }]; + }); + } +} + - (void) refreshGpxTracks:(QHash< QString, std::shared_ptr >)gpxDocs reset:(BOOL)reset { if (reset) @@ -222,7 +256,7 @@ - (void)addTrackToCached:(QString)key value:(std::shared_ptr 0 ? gpx.gradientPaletteName : @"default"; + cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : PaletteGradientColor.defaultName; cachedTrack[@"prev_wall_coloring_type"] = @(gpx.visualization3dWallColorType); _cachedTracks[filePath] = cachedTrack; _cachedColors[key] = QList(); @@ -244,28 +278,20 @@ - (void)configureCachedWallColorsFor:(OAColoringType *)type } else { - NSError *error = nil; ColorPalette *palette = [[ColorPaletteHelper shared] getGradientColorPaletteSync:(ColorizationType) [type toColorizationType] - gradientPaletteName:gradientPalette - error:&error]; - if (error) - { - NSLog(@"Error reading color palette file: %@", error.description); + gradientPaletteName:gradientPalette]; + if (!palette) return; - } - else - { - OARouteColorize *routeColorize = - [[OARouteColorize alloc] initWithGpxFile:doc - analysis:analysis ?: [doc getAnalysis:0] - type:[type toColorizationType] - palette:palette - maxProfileSpeed:0]; - _cachedWallColors[key].clear(); - if (routeColorize) - _cachedWallColors[key].append([routeColorize getResultQList]); - } + OARouteColorize *routeColorize = + [[OARouteColorize alloc] initWithGpxFile:doc + analysis:analysis ?: [doc getAnalysis:0] + type:[type toColorizationType] + palette:palette + maxProfileSpeed:0]; + _cachedWallColors[key].clear(); + if (routeColorize) + _cachedWallColors[key].append([routeColorize getResultQList]); } } @@ -312,15 +338,27 @@ - (void) refreshGpxTracks type = OAColoringType.DEFAULT; OAGPXTrackAnalysis *analysis; + NSString *colorPaletteFile = @""; + if ([type isGradient]) + { + colorPaletteFile = + [ColorPaletteHelper getRoutePaletteFileName:(ColorizationType) [type toColorizationType] + gradientPaletteName:cachedTrack[@"prev_color_palette"]]; + } if ([type isGradient] && (![cachedTrack[@"prev_coloring_type"] isEqualToString:gpx.coloringType] || ![cachedTrack[@"prev_color_palette"] isEqualToString:gpx.gradientPaletteName] || [cachedTrack[@"colorization_scheme"] intValue] != COLORIZATION_GRADIENT + || [_updatedColorPaletteFiles.allKeys containsObject:colorPaletteFile] || _cachedColors[key].isEmpty())) { + NSString *updatedColorPaletteValue = _updatedColorPaletteFiles[colorPaletteFile]; + BOOL isColorPaletteDeleted = [updatedColorPaletteValue isEqualToString:DirectoryObserver.deletedKey]; + [_updatedColorPaletteFiles removeObjectForKey:colorPaletteFile]; + cachedTrack[@"colorization_scheme"] = @(COLORIZATION_GRADIENT); cachedTrack[@"prev_coloring_type"] = gpx.coloringType; - cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : @"default"; + cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length == 0 || isColorPaletteDeleted ? PaletteGradientColor.defaultName : gpx.gradientPaletteName; BOOL shouldCalculateColorCache = YES; // check if we already have a cached array of wall color points that can be reused for route line color, provided that the coloring type matches switch (gpx.visualization3dWallColorType) @@ -341,28 +379,20 @@ - (void) refreshGpxTracks if (shouldCalculateColorCache) { analysis = [doc getAnalysis:0]; - NSError *error = nil; ColorPalette *palette = [[ColorPaletteHelper shared] getGradientColorPaletteSync:(ColorizationType) [type toColorizationType] - gradientPaletteName:cachedTrack[@"prev_color_palette"] - error:&error]; - if (error) - { - NSLog(@"Error reading color palette file: %@", error.description); + gradientPaletteName:cachedTrack[@"prev_color_palette"]]; + if (!palette) return; - } - else - { - OARouteColorize *routeColorize = + OARouteColorize *routeColorize = [[OARouteColorize alloc] initWithGpxFile:doc analysis:analysis type:[type toColorizationType] palette:palette maxProfileSpeed:0]; - _cachedColors[key].clear(); - if (routeColorize) - _cachedColors[key].append([routeColorize getResultQList]); - } + _cachedColors[key].clear(); + if (routeColorize) + _cachedColors[key].append([routeColorize getResultQList]); } else { @@ -388,7 +418,7 @@ - (void) refreshGpxTracks } cachedTrack[@"colorization_scheme"] = @(COLORIZATION_SOLID); cachedTrack[@"prev_coloring_type"] = gpx.coloringType; - cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : @"default"; + cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : PaletteGradientColor.defaultName; _cachedColors[key].clear(); [self calculateSegmentsColor:_cachedColors[key] attrName:gpx.coloringType @@ -402,7 +432,7 @@ - (void) refreshGpxTracks { cachedTrack[@"colorization_scheme"] = @(COLORIZATION_NONE); cachedTrack[@"prev_coloring_type"] = gpx.coloringType; - cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : @"default"; + cachedTrack[@"prev_color_palette"] = gpx.gradientPaletteName.length > 0 ? gpx.gradientPaletteName : PaletteGradientColor.defaultName; _cachedColors[key].clear(); } diff --git a/Sources/Controllers/Map/Layers/OAMapLayers.h b/Sources/Controllers/Map/Layers/OAMapLayers.h index c89770ae70..053c263e7e 100644 --- a/Sources/Controllers/Map/Layers/OAMapLayers.h +++ b/Sources/Controllers/Map/Layers/OAMapLayers.h @@ -77,6 +77,7 @@ - (void) updateLayers; - (void) updateWeatherDate:(NSDate *)date; +- (void) updateWeatherLayers; - (void) showLayer:(NSString *)layerId; - (void) hideLayer:(NSString *)layerId; diff --git a/Sources/Controllers/Map/Layers/OAMapLayers.mm b/Sources/Controllers/Map/Layers/OAMapLayers.mm index 607a0f994f..4ce00ec4e5 100644 --- a/Sources/Controllers/Map/Layers/OAMapLayers.mm +++ b/Sources/Controllers/Map/Layers/OAMapLayers.mm @@ -171,6 +171,13 @@ - (void) updateWeatherDate:(NSDate *)date [_weatherContourLayer updateDate:date]; } +- (void) updateWeatherLayers +{ + [_weatherLayerLow updateWeatherLayer]; + [_weatherLayerHigh updateWeatherLayer]; + [_weatherContourLayer updateLayer]; +} + - (void) addLayer:(OAMapLayer *)layer { [layer initLayer]; diff --git a/Sources/Controllers/Map/Layers/OAPreviewRouteLineLayer.mm b/Sources/Controllers/Map/Layers/OAPreviewRouteLineLayer.mm index afa61a50d1..6fb461fde8 100644 --- a/Sources/Controllers/Map/Layers/OAPreviewRouteLineLayer.mm +++ b/Sources/Controllers/Map/Layers/OAPreviewRouteLineLayer.mm @@ -77,7 +77,7 @@ - (void) initLayer { [super initLayer]; - _routeGradientPalette = @"default"; + _routeGradientPalette = PaletteGradientColor.defaultName; _routingHelper = OARoutingHelper.sharedInstance; _collection = std::make_shared(); @@ -597,15 +597,8 @@ - (void)fillSlopeGradientArrays:(QVector &)points distances:(NSM if (gradientScaleType) { ColorizationType colorizationType = (ColorizationType) [gradientScaleType toColorizationType]; - NSError *error = nil; previewPalette = [[ColorPaletteHelper shared] requireGradientColorPaletteSync:colorizationType - gradientPaletteName:_routeGradientPalette - error:&error]; - if (error) - { - NSLog(@"Error reading color palette file: %@", error.description); - return; - } + gradientPaletteName:_routeGradientPalette]; } NSMutableArray *palette = [NSMutableArray array]; for (ColorValue *colorValue in [previewPalette colorValues]) diff --git a/Sources/Controllers/Map/Layers/OARouteLayer.mm b/Sources/Controllers/Map/Layers/OARouteLayer.mm index cd431fa179..a9b685bb83 100644 --- a/Sources/Controllers/Map/Layers/OARouteLayer.mm +++ b/Sources/Controllers/Map/Layers/OARouteLayer.mm @@ -79,6 +79,7 @@ @implementation OARouteLayer OAColoringType *_prevRouteColoringType; NSString *_prevRouteInfoAttribute; NSCache *_cachedRouteLineWidth; + NSMutableDictionary *_updatedColorPaletteFiles; int _currentAnimatedRoute; CLLocation *_lastProj; @@ -108,7 +109,7 @@ - (void) initLayer { [super initLayer]; - _routeGradientPalette = @"default"; + _routeGradientPalette = PaletteGradientColor.defaultName; _routingHelper = [OARoutingHelper sharedInstance]; _transportHelper = [OATransportRoutingHelper sharedInstance]; @@ -135,6 +136,7 @@ - (void) initLayer _routeColoringType = OAColoringType.DEFAULT; _colorizationScheme = COLORIZATION_NONE; _cachedRouteLineWidth = [[NSCache alloc] init]; + _updatedColorPaletteFiles = [NSMutableDictionary dictionary]; _mapZoomObserver = [[OAAutoObserverProxy alloc] initWith:self withHandler:@selector(onMapZoomChanged:withKey:andValue:) @@ -142,6 +144,11 @@ - (void) initLayer _updateGpxTracksOnMapObserver = [[OAAutoObserverProxy alloc] initWith:self withHandler:@selector(refreshRoute) andObserve:[OsmAndApp instance].updateGpxTracksOnMapObservable]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onColorPalettesFilesUpdated:) + name:ColorPaletteHelper.colorPalettesUpdatedNotification + object:nil]; } - (void) resetLayer @@ -176,6 +183,31 @@ - (BOOL) updateLayer return YES; } +- (void)onColorPalettesFilesUpdated:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:NSDictionary.class]) + return; + + NSDictionary *colorPaletteFiles = (NSDictionary *) notification.object; + if (!colorPaletteFiles) + return; + BOOL refresh = NO; + for (NSString *colorPaletteFile in colorPaletteFiles) + { + if ([colorPaletteFile hasPrefix:ColorPaletteHelper.routePrefix]) + { + _updatedColorPaletteFiles[colorPaletteFile] = colorPaletteFiles[colorPaletteFile]; + refresh = YES; + } + } + if (refresh) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self refreshRoute]; + }); + } +} + - (NSInteger)getCustomRouteWidthMin { return 1; @@ -833,37 +865,42 @@ - (void) refreshRouteWithSync:(BOOL)sync NSArray *locations = [route getImmutableAllLocations]; BOOL routeUpdated = NO; + NSString *colorPaletteFile = @""; + if ([routeColoringType isGradient]) + { + colorPaletteFile = + [ColorPaletteHelper getRoutePaletteFileName:(ColorizationType) [routeColoringType toColorizationType] + gradientPaletteName:_routeGradientPalette]; + } if ([routeColoringType isGradient] && (_route != route || _prevRouteColoringType != routeColoringType - || _colorizationScheme != COLORIZATION_GRADIENT)) + || _colorizationScheme != COLORIZATION_GRADIENT + || [_updatedColorPaletteFiles.allKeys containsObject:colorPaletteFile])) { + NSString *updatedColorPaletteValue = _updatedColorPaletteFiles[colorPaletteFile]; + if ([updatedColorPaletteValue isEqualToString:DirectoryObserver.deletedKey]) + _routeGradientPalette = PaletteGradientColor.defaultName; + [_updatedColorPaletteFiles removeObjectForKey:colorPaletteFile]; + OAGPXDocument *gpx = [OAGPXUIHelper makeGpxFromRoute:route]; - NSError *error = nil; ColorPalette *colorPalette = [[ColorPaletteHelper shared] getGradientColorPaletteSync:(ColorizationType) [routeColoringType toColorizationType] - gradientPaletteName:_routeGradientPalette - error:&error]; - if (error) - { - NSLog(@"Error reading color palette file: %@", error.description); + gradientPaletteName:_routeGradientPalette]; + if (!colorPalette) return; - } - else - { - OARouteColorize *colorizationHelper = - [[OARouteColorize alloc] initWithGpxFile:gpx - analysis:[gpx getAnalysis:0] - type:[routeColoringType toColorizationType] - palette:colorPalette - maxProfileSpeed:0 - ]; - _colorizationScheme = COLORIZATION_GRADIENT; - _colors.clear(); - if (colorizationHelper) - _colors.append([colorizationHelper getResultQList]); - _route = route; - routeUpdated = YES; - } + OARouteColorize *colorizationHelper = + [[OARouteColorize alloc] initWithGpxFile:gpx + analysis:[gpx getAnalysis:0] + type:[routeColoringType toColorizationType] + palette:colorPalette + maxProfileSpeed:0 + ]; + _colorizationScheme = COLORIZATION_GRADIENT; + _colors.clear(); + if (colorizationHelper) + _colors.append([colorizationHelper getResultQList]); + _route = route; + routeUpdated = YES; } else if ([routeColoringType isRouteInfoAttribute] && (_route != route || ![_prevRouteInfoAttribute isEqualToString:_routeInfoAttribute] || _colorizationScheme != COLORIZATION_SOLID)) diff --git a/Sources/Controllers/Map/Layers/OATerrainMapLayer.mm b/Sources/Controllers/Map/Layers/OATerrainMapLayer.mm index fb6f495320..e03249c46d 100644 --- a/Sources/Controllers/Map/Layers/OATerrainMapLayer.mm +++ b/Sources/Controllers/Map/Layers/OATerrainMapLayer.mm @@ -56,6 +56,11 @@ - (void) initLayer selector:@selector(onProfileSettingSet:) name:kNotificationSetProfileSetting object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onColorPalettesFilesUpdated:) + name:ColorPaletteHelper.colorPalettesUpdatedNotification + object:nil]; } - (void) deinitLayer @@ -122,25 +127,45 @@ - (void)updateTerrainLayer - (void)onProfileSettingSet:(NSNotification *)notification { - if (notification.object == _plugin.terrainEnabledPref || notification.object == _plugin.terrainModeTypePref) - { - [self updateTerrainLayer]; - } - else if ([notification.object isKindOfClass:OACommonInteger.class]) - { - if ([_terrainMode isTransparencySetting:notification.object]) + dispatch_async(dispatch_get_main_queue(), ^{ + if (notification.object == _plugin.terrainEnabledPref || notification.object == _plugin.terrainModeTypePref) { - dispatch_async(dispatch_get_main_queue(), ^{ + [self updateTerrainLayer]; + } + else if ([notification.object isKindOfClass:OACommonInteger.class]) + { + if ([_terrainMode isTransparencySetting:notification.object]) + { [self.mapViewController runWithRenderSync:^{ OsmAnd::MapLayerConfiguration config; config.setOpacityFactor([_terrainMode getTransparency] * 0.01); [self.mapView setMapLayerConfiguration:self.layerIndex configuration:config forcedUpdate:NO]; }]; - }); + } + else if ([_terrainMode isZoomSetting:notification.object]) + { + [self updateTerrainLayer]; + } } - else if ([_terrainMode isZoomSetting:notification.object]) + }); +} + +- (void)onColorPalettesFilesUpdated:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:NSDictionary.class]) + return; + + NSDictionary *colorPaletteFiles = (NSDictionary *) notification.object; + if (!colorPaletteFiles) + return; + for (NSString *colorPaletteFile in colorPaletteFiles) + { + if ([[_terrainMode getMainFile] isEqualToString:colorPaletteFile]) { - [self updateTerrainLayer]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateTerrainLayer]; + }); + return; } } } diff --git a/Sources/Controllers/Map/Layers/OAWeatherContourLayer.h b/Sources/Controllers/Map/Layers/OAWeatherContourLayer.h index 413bb5df0e..1fe116c177 100644 --- a/Sources/Controllers/Map/Layers/OAWeatherContourLayer.h +++ b/Sources/Controllers/Map/Layers/OAWeatherContourLayer.h @@ -17,4 +17,6 @@ - (void) updateDate:(NSDate *)date; - (void) updateWeatherLayer; +- (void) setDateTime:(NSTimeInterval)dateTime; + @end diff --git a/Sources/Controllers/Map/Layers/OAWeatherContourLayer.mm b/Sources/Controllers/Map/Layers/OAWeatherContourLayer.mm index 2172eadf38..fd8725e029 100644 --- a/Sources/Controllers/Map/Layers/OAWeatherContourLayer.mm +++ b/Sources/Controllers/Map/Layers/OAWeatherContourLayer.mm @@ -267,6 +267,11 @@ - (void) updateDate:(NSDate *)date }); } +- (void) setDateTime:(NSTimeInterval)dateTime +{ + _date = [NSDate dateWithTimeIntervalSince1970:dateTime]; +} + - (void) onWeatherLayerChanged { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.h b/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.h index 449bcdeae7..5d516dff52 100644 --- a/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.h +++ b/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.h @@ -14,6 +14,10 @@ typedef NS_ENUM(NSInteger, EOAWeatherLayer) WEATHER_LAYER_HIGH = 1 }; +static int64_t FORECAST_ANIMATION_DURATION_HOURS = 6; +static int64_t HOUR_IN_MILLISECONDS = 60 * 60 * 1000; +static int64_t DAY_IN_MILLISECONDS = 24 * HOUR_IN_MILLISECONDS; + @interface OAWeatherRasterLayer : OARasterMapLayer @property (nonatomic, readonly) EOAWeatherLayer weatherLayer; @@ -25,4 +29,6 @@ typedef NS_ENUM(NSInteger, EOAWeatherLayer) - (void) updateWeatherLayer; - (void) updateWeatherLayerAlpha; +- (void) setDateTime:(int64_t)dateTime goForward:(BOOL)goForward resetPeriod:(BOOL)resetPeriod; + @end diff --git a/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.mm b/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.mm index ae8f79f51d..1a2cdf5d4a 100644 --- a/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.mm +++ b/Sources/Controllers/Map/Layers/OAWeatherRasterLayer.mm @@ -21,6 +21,7 @@ #import "OAPluginsHelper.h" #import "OAAppData.h" #import "OAObservable.h" +#import "OAOsmAndFormatter.h" #include #include @@ -42,6 +43,12 @@ @implementation OAWeatherRasterLayer OsmAnd::PointI _cachedCenterPixel; BOOL _cachedAnyWidgetVisible; NSTimeInterval _lastUpdateTime; + + int64_t _timePeriodStart; + int64_t _timePeriodEnd; + int64_t _timePeriodStep; + BOOL _requireTimePeriodChange; + int64_t _dateTime; } - (instancetype) initWithMapViewController:(OAMapViewController *)mapViewController layerIndex:(int)layerIndex weatherLayer:(EOAWeatherLayer)weatherLayer date:(NSDate *)date @@ -132,8 +139,6 @@ - (BOOL) updateLayer //[self showProgressHUD]; - NSDate *roundedDate = [OAWeatherHelper roundForecastTimeToHour:_date]; - int64_t dateTime = roundedDate.timeIntervalSince1970 * 1000; OsmAnd::WeatherLayer layer; switch (_weatherLayer) { case WEATHER_LAYER_LOW: @@ -146,29 +151,23 @@ - (BOOL) updateLayer layer = OsmAnd::WeatherLayer::Low; break; } - int64_t dateTimeStep = 60 * 60 * 1000; - if ((dateTime - NSDate.date.timeIntervalSince1970 * 1000) > dateTimeStep * 24) - dateTimeStep *= 3; - int64_t dateTimeFirst = dateTime; - int64_t dateTimeLast = dateTime; - [self.mapView setDateTime:dateTime]; - if (true)//!_provider) + if (!_provider) { - _provider = std::make_shared(_resourcesManager, layer, dateTimeFirst, dateTimeLast, dateTimeStep, bands, self.app.data.weatherUseOfflineData); + _requireTimePeriodChange = NO; + _provider = std::make_shared(_resourcesManager, layer, _timePeriodStart, _timePeriodEnd, _timePeriodStep, bands, self.app.data.weatherUseOfflineData); [self.mapView setProvider:_provider forLayer:self.layerIndex]; OsmAnd::MapLayerConfiguration config; config.setOpacityFactor(1.0f); [self.mapView setMapLayerConfiguration:self.layerIndex configuration:config forcedUpdate:NO]; } - else + if (_requireTimePeriodChange) { - _provider->setDateTime(dateTimeFirst, dateTimeLast, dateTimeStep); - _provider->setBands(bands); - - [self.mapView invalidateFrame]; + _requireTimePeriodChange = NO; + _provider->setDateTime(_timePeriodStart, _timePeriodEnd, _timePeriodStep); + [self.mapView changeTimePeriod]; } - + [self.mapView setDateTime:_dateTime]; //[self hideProgressHUD]; return YES; @@ -176,6 +175,67 @@ - (BOOL) updateLayer return NO; } +- (void) setDateTime:(int64_t)dateTime goForward:(BOOL)goForward resetPeriod:(BOOL)resetPeriod +{ + if (_timePeriodStart == 0) + _timePeriodStart = [[NSDate now] timeIntervalSince1970] * 1000; + + int64_t dayStart = [OAOsmAndFormatter getStartOfDayForTime:_timePeriodStart / 1000] * 1000; + int64_t dayEnd = dayStart + DAY_IN_MILLISECONDS; + if (dateTime < dayStart || dayStart > dayEnd) + { + dayStart = [OAOsmAndFormatter getStartOfDayForTime:dayStart]; + dayEnd = dayStart + DAY_IN_MILLISECONDS; + } + + int64_t todayStep = HOUR_IN_MILLISECONDS; + int64_t nextStep = todayStep * 3; + int64_t startOfToday = [OAOsmAndFormatter getStartOfToday] * 1000; + int64_t step = dayStart == startOfToday ? todayStep : nextStep; + int64_t switchStepTime = ((int64_t)([[NSDate now] timeIntervalSince1970] * 1000 + DAY_IN_MILLISECONDS)) / nextStep * nextStep; + if (switchStepTime > startOfToday && switchStepTime >= dayStart + todayStep && switchStepTime <= dayEnd - nextStep) + { + if (dateTime < switchStepTime) + { + dayEnd = switchStepTime; + step = todayStep; + } + else + { + dayStart = switchStepTime; + } + } + + int64_t prevTime = (dateTime - dayStart) / step * step + dayStart; + int64_t nextTime = prevTime + step; + if (goForward) + { + if (resetPeriod || _timePeriodStep != step + || (_timePeriodStart > dayStart && prevTime < _timePeriodStart) + || (_timePeriodEnd < dayEnd && nextTime > _timePeriodEnd)) + { + _timePeriodStart = MAX(prevTime, dayStart); + _timePeriodEnd = MIN(nextTime + FORECAST_ANIMATION_DURATION_HOURS * HOUR_IN_MILLISECONDS, dayEnd); + _timePeriodStep = step; + _requireTimePeriodChange = YES; + } + } + else + { + int64_t nearestTime = dateTime - prevTime < nextTime - dateTime ? prevTime : nextTime; + if (resetPeriod || _timePeriodStep != step + || (_timePeriodStart > dayStart && nearestTime <= _timePeriodStart) + || (_timePeriodEnd < dayEnd && nearestTime >= _timePeriodEnd)) + { + _timePeriodStart = MAX(nearestTime - step, dayStart); + _timePeriodEnd = MIN(nearestTime + step, dayEnd); + _timePeriodStep = step; + _requireTimePeriodChange = YES; + } + } + _dateTime = dateTime; +} + - (void)onWeatherToolbarStateChanged { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Sources/Controllers/Map/OAMapViewController.mm b/Sources/Controllers/Map/OAMapViewController.mm index ac6087808a..68fdf8eeea 100644 --- a/Sources/Controllers/Map/OAMapViewController.mm +++ b/Sources/Controllers/Map/OAMapViewController.mm @@ -3683,7 +3683,6 @@ - (void) refreshGpxTracks { @synchronized(_rendererSync) { - [_mapLayers.gpxMapLayer resetLayer]; if (![_selectedGpxHelper buildGpxList]) [self initRendererWithGpxTracks]; } diff --git a/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.mm b/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.mm index 20b9782442..2e1e8e78f0 100644 --- a/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.mm +++ b/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.mm @@ -27,21 +27,38 @@ #import "OAAutoObserverProxy.h" #import "OAAppData.h" #import "OAObservable.h" +#import "OAWeatherWebClient.h" #import "OsmAnd_Maps-Swift.h" -#define kDefaultZoom 10 +static int kDefaultZoom = 10; + +static int kForecastStepsPerHour = 6; // 10 minutes step +static NSInteger kForecastMaxStepsCount = FORECAST_ANIMATION_DURATION_HOURS * kForecastStepsPerHour; + +static NSTimeInterval kAnimationStartDelaySec = 0.1; +static NSTimeInterval kAnimationFrameDelaySec = 0.083 * 2; +static NSTimeInterval kDownloadingCompleteDelaySec = 0.25; + +typedef NS_ENUM(NSInteger, EOAWeatherToolbarAnimationState) { + EOAWeatherToolbarAnimationStateIdle = 0, + EOAWeatherToolbarAnimationStateStarted, + EOAWeatherToolbarAnimationStateInProgress, + EOAWeatherToolbarAnimationStateSuspended, +}; @interface OAWeatherToolbar () @property (weak, nonatomic) IBOutlet OAFoldersCollectionView *dateCollectionView; @property (weak, nonatomic) IBOutlet OASegmentedSlider *timeSliderView; @property (weak, nonatomic) IBOutlet UIStackView *weatherStackView; +@property (weak, nonatomic) IBOutlet UIButton *playButton; @end @implementation OAWeatherToolbar { OsmAndAppInstance _app; + OAWeatherPlugin *_plugin; NSMutableArray *_layerChangeObservers; OAAutoObserverProxy *_contourNameChangeObserver; OAAutoObserverProxy *_mapSourceUpdatedObserver; @@ -54,6 +71,22 @@ @implementation OAWeatherToolbar float _prevZoom; OsmAnd::PointI _prevTarget31; NSArray *_weatherWidgetControlsArray; + + NSInteger _lastUpdatedIndex; + NSDate *_currentDate; + NSDate *_selectedDate; + + EOAWeatherToolbarAnimationState _animationState; + NSInteger _animationStartStep; + NSInteger _currentStep; + NSInteger _animateStepCount; + NSInteger _animationStartStepCount; + BOOL _isDownloading; + BOOL _wasDownloading; + + CADisplayLink * _displayLink; + CFTimeInterval _currentLoopStart; + CFTimeInterval _currentLoopDuration; } - (instancetype)init @@ -86,6 +119,7 @@ - (void)commonInit { self.hidden = YES; _app = [OsmAndApp instance]; + _plugin = (OAWeatherPlugin *) [OAPluginsHelper getPlugin:OAWeatherPlugin.class]; _currentTimezoneCalendar = NSCalendar.autoupdatingCurrentCalendar; _currentTimezoneCalendar.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; _previousSelectedDayIndex = 0; @@ -98,8 +132,14 @@ - (void)commonInit self.timeSliderView.stepsAmountWithoutDrawMark = 145.0; [self.timeSliderView clearTouchEventsUpInsideUpOutside]; + [self.timeSliderView setUsingExtraThumbInset:YES]; + + _currentDate = [NSDate now]; + _selectedDate = _currentDate; + _animationState = EOAWeatherToolbarAnimationStateIdle; + [self.timeSliderView removeTarget:self action:NULL forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside]; - [self.timeSliderView addTarget:self action:@selector(timeChanged:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside]; + [self.timeSliderView addTarget:self action:@selector(timeChanged:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside | UIControlEventValueChanged]; _layerChangeObservers = [NSMutableArray array]; for (OAWeatherBand *band in [OAWeatherHelper sharedInstance].bands) @@ -112,6 +152,11 @@ - (void)commonInit _mapSourceUpdatedObserver = [[OAAutoObserverProxy alloc] initWith:self withHandler:@selector(updateLayersHandlerData) andObserve:[OARootViewController instance].mapPanel.mapViewController.mapSourceUpdatedObservable]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onDownloadStateChanged:) + name:kOAWeatherWebClientNotificationKey object:nil]; + [self updateInfo]; } @@ -135,7 +180,7 @@ - (void)dealloc - (void)configureWidgetControlsStackView { - _weatherWidgetControlsArray = [[(OAWeatherPlugin *)[OAPluginsHelper getPlugin:OAWeatherPlugin.class] createWidgetsControls] copy]; + _weatherWidgetControlsArray = [[_plugin createWidgetsControls] copy]; if (_weatherWidgetControlsArray && _weatherWidgetControlsArray.count > 0) { [_weatherStackView removeAllArrangedSubviews]; @@ -419,18 +464,210 @@ - (void)updateWidgetsInfo } } +#pragma mark - Start/stop button + +- (IBAction)onPlayForecastClicked:(id)sender +{ + EOAWeatherToolbarAnimationState animationState = _animationState == EOAWeatherToolbarAnimationStateIdle ? EOAWeatherToolbarAnimationStateStarted : EOAWeatherToolbarAnimationStateIdle; + _animationState = animationState; + if (animationState == EOAWeatherToolbarAnimationStateStarted) + { + _currentStep = [self.timeSliderView getIndexForOptionStepsAmountWithoutDrawMark]; + _animationStartStep = _currentStep; + _selectedDate = _timeValues[_currentStep]; + + NSInteger remainingStepsForMidnight = _timeValues.count - _currentStep - 1; + _animateStepCount = MIN(kForecastMaxStepsCount, remainingStepsForMidnight); + _animationStartStepCount = _animateStepCount; + + [_plugin prepareForDayAnimation:_selectedDate]; + [self updateSelectedDate:_selectedDate forAnimation:YES resetPeriod:YES]; + [self scheduleAnimationStart]; + } + else + { + [self stopAnimation]; + } + [self updatePlayForecastButton]; +} + +- (void) updatePlayForecastButton +{ + NSString *iconName = _animationState == EOAWeatherToolbarAnimationStateIdle ? @"ic_custom_play" : @"ic_custom_pause"; + [_playButton setImage:[UIImage templateImageNamed:iconName] forState:UIControlStateNormal]; +} + +- (void) scheduleAnimationStart +{ + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationTick:)]; + _currentLoopStart = CACurrentMediaTime(); + _currentLoopDuration = kAnimationFrameDelaySec; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (void) onAnimationTick:(CADisplayLink *)timer +{ + if (_animationState != EOAWeatherToolbarAnimationStateIdle) + { + CFTimeInterval nextLoopStart = _currentLoopStart + _currentLoopDuration; + + if (timer.timestamp >= nextLoopStart) + { + _currentLoopStart = nextLoopStart; + + if (!_isDownloading) + { + if (!_wasDownloading) + { + _currentLoopDuration = kAnimationFrameDelaySec; + dispatch_async(dispatch_get_main_queue(), ^{ + [self moveToNextForecastFrame]; + }); + } + else + { + _wasDownloading = NO; + _currentLoopDuration = kDownloadingCompleteDelaySec; + } + } + else + { + _wasDownloading = YES; + } + } + } + else + { + // stop animation loop + [timer invalidate]; + _displayLink = nil; + } +} + +- (void) stopAnimation +{ + _animationState = EOAWeatherToolbarAnimationStateIdle; + [self updateProgressBar]; + [self updatePlayForecastButton]; +} + +- (void) moveToNextForecastFrame +{ + if (_animationState == EOAWeatherToolbarAnimationStateIdle) + return; + + if (_isDownloading) + { + _animationState = EOAWeatherToolbarAnimationStateSuspended; + return; + } + + if ([OAWeatherHelper.sharedInstance isProcessingTiles]) + { + _animationState = EOAWeatherToolbarAnimationStateSuspended; + [self updateProgressBar]; + return; + } + + if (_currentStep + 1 > _timeValues.count || _animateStepCount == 0) + { + _currentStep = _animationStartStep; + _animateStepCount = _animationStartStepCount; + } + else + { + _currentStep++; + _animateStepCount--; + } + + [self updateProgressBar]; + [self updateSliderValue]; + [self updateSelectedDate:_timeValues[_currentStep] forAnimation:YES resetPeriod:NO]; + + if (_animationState == EOAWeatherToolbarAnimationStateStarted || _animationState == EOAWeatherToolbarAnimationStateSuspended) + { + _animationState = EOAWeatherToolbarAnimationStateInProgress; + [self updateProgressBar]; + } +} + +#pragma mark - Downloading methods + +- (void) onDownloadStateChanged:(NSNotification *)notification +{ + NSNumber *requestsCount = notification.object; + + if (requestsCount) + { + dispatch_async(dispatch_get_main_queue(), ^{ + _isDownloading = requestsCount.intValue > 0; + [self updateProgressBar]; + }); + } +} + +- (void) updateProgressBar +{ + if (_isDownloading || (_animationState != EOAWeatherToolbarAnimationStateIdle && [OAWeatherHelper.sharedInstance isProcessingTiles])) + { + [[OARootViewController instance].view addSpinnerInCenterOfCurrentView:YES]; + } + else + { + [[OARootViewController instance].view removeSpinner]; + } +} + #pragma mark - UISlider - (void)timeChanged:(UISlider *)sender { + BOOL fromUser = sender != nil; + if (fromUser) + [self stopAnimation]; + NSInteger index = [self.timeSliderView getIndexForOptionStepsAmountWithoutDrawMark]; - NSDate *selectedDate = _timeValues[index]; - [[OARootViewController instance].mapPanel.mapViewController.mapLayers updateWeatherDate:selectedDate]; + _selectedDate = _timeValues[index]; + [self updateSelectedDate:_selectedDate forAnimation:!fromUser resetPeriod:NO]; +} - if ([_layersHandler isAllLayersDisabled]) +- (void) updateSliderValue +{ + float value = ((float)_currentStep) / ((float)_timeValues.count); + [self.timeSliderView setValue:value animated:YES]; +} + +- (void) updateSelectedDate:(NSDate *)date forAnimation:(BOOL)forAnimation resetPeriod:(BOOL)resetPeriod +{ + [_plugin setForecastDate:date forAnimation:forAnimation resetPeriod:resetPeriod]; + if (date) + date = [OAWeatherHelper roundForecastTimeToHour:date]; + + [self checkDateOffset:date]; + + // TODO: replace to widgetsPanel.setSelectedDate(date); + [_plugin updateWidgetsInfo]; + [self updateWidgetsInfo]; + + // TODO: replace to [[OARootViewController instance].mapPanel refreshMap]; + [[OARootViewController instance].mapPanel.mapViewController.mapLayers updateWeatherLayers]; +} + +- (void) checkDateOffset:(NSDate *)date +{ + NSInteger MIN_UTC_HOURS_OFFSET = 24 * 60 * 60; + if (date && (([date timeIntervalSince1970] - [_currentDate timeIntervalSince1970]) >= MIN_UTC_HOURS_OFFSET)) { - [(OAWeatherPlugin *) [OAPluginsHelper getPlugin:OAWeatherPlugin.class] updateWidgetsInfo]; - [self updateWidgetsInfo]; + NSCalendar *utcCalendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; + utcCalendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + NSDateComponents *dateComponents = [utcCalendar components:NSCalendarUnitHour fromDate:date]; + NSInteger hours = dateComponents.hour; + NSInteger offset = hours % 3; + if (offset == 2) + [dateComponents setHour:hours + 1]; + else if (offset == 1) + [dateComponents setHour:hours - 1]; + date = [utcCalendar dateFromComponents:dateComponents]; } } diff --git a/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.xib b/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.xib index 6ce2a71b57..b0c7ad997f 100644 --- a/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.xib +++ b/Sources/Controllers/Map/Toolbars/OAWeatherToolbar.xib @@ -49,7 +49,7 @@ - + @@ -60,11 +60,31 @@ + + @@ -72,18 +92,20 @@ + + - + @@ -91,6 +113,13 @@ + + + + + + + diff --git a/Sources/Controllers/TargetMenu/GPX/TrackMenu/OATrackMenuAppearanceHudViewController.mm b/Sources/Controllers/TargetMenu/GPX/TrackMenu/OATrackMenuAppearanceHudViewController.mm index 5f36f85d54..5d8d37fab0 100644 --- a/Sources/Controllers/TargetMenu/GPX/TrackMenu/OATrackMenuAppearanceHudViewController.mm +++ b/Sources/Controllers/TargetMenu/GPX/TrackMenu/OATrackMenuAppearanceHudViewController.mm @@ -181,10 +181,77 @@ - (void)commonInit _app = [OsmAndApp instance]; _settings = [OAAppSettings sharedManager]; _appearanceCollection = [OAGPXAppearanceCollection sharedInstance]; - _gradientColorsCollection = [[GradientColorsCollection alloc] initWithColorizationType:(ColorizationType) [_selectedItem.coloringType toColorizationType]]; + + OAColoringType *type = self.gpx.coloringType.length > 0 + ? [OAColoringType getNonNullTrackColoringTypeByName:self.gpx.coloringType] + : OAColoringType.TRACK_SOLID; + _gradientColorsCollection = [[GradientColorsCollection alloc] initWithColorizationType:(ColorizationType) [type toColorizationType]]; [self setOldValues]; [self updateAllValues]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onColorPalettesFilesUpdated:) + name:ColorPaletteHelper.colorPalettesUpdatedNotification + object:nil]; +} + +- (void)onColorPalettesFilesUpdated:(NSNotification *)notification +{ + if (![notification.object isKindOfClass:NSDictionary.class] || ![self isSelectedTypeGradient]) + return; + + NSDictionary *colorPaletteFiles = (NSDictionary *) notification.object; + if (!colorPaletteFiles) + return; + OAColoringType *currentType = [OAColoringType getNonNullTrackColoringTypeByName:self.gpx.coloringType]; + NSString *currentPaletteFile = [ColorPaletteHelper getRoutePaletteFileName:(ColorizationType) [currentType toColorizationType] gradientPaletteName:self.gpx.gradientPaletteName]; + BOOL reloadData = NO; + BOOL deleted = NO; + for (NSString *colorPaletteFile in colorPaletteFiles.allKeys) + { + if ([_gradientColorsCollection hasRouteGradientPaletteBy:colorPaletteFile] + || [colorPaletteFiles[colorPaletteFile] isEqualToString:DirectoryObserver.createdKey]) + { + reloadData = YES; + if ([currentPaletteFile isEqualToString:colorPaletteFile] + && [colorPaletteFiles[colorPaletteFile] isEqualToString:DirectoryObserver.deletedKey]) + { + deleted = YES; + break; + } + } + } + if (reloadData) + { + _gradientColorsCollection = [[GradientColorsCollection alloc] initWithColorizationType:(ColorizationType) [_selectedItem.coloringType toColorizationType]]; + _sortedPaletteColorItems = [NSMutableArray arrayWithArray:[_gradientColorsCollection getPaletteColors]]; + if (deleted) + { + self.gpx.gradientPaletteName = PaletteGradientColor.defaultName; + _backupGpxItem.gradientPaletteName = self.gpx.gradientPaletteName; + if (self.isCurrentTrack) + [self.doc setGradientColorPalette:self.gpx.gradientPaletteName]; + _selectedPaletteColorItem = [_gradientColorsCollection getDefaultGradientPalette]; + } + else + { + _selectedPaletteColorItem = [_gradientColorsCollection getGradientPaletteBy:self.gpx.gradientPaletteName]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + OAGPXTableSectionData *section = _tableData[kColorsSection]; + [self updateData:section]; + + [UIView transitionWithView:self.tableView + duration:0.35f + options:UIViewAnimationOptionTransitionCrossDissolve + animations:^(void) + { + [self.tableView reloadData]; + } + completion:nil]; + }); + } } - (void)setOldValues @@ -1645,6 +1712,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N if (cell) { BOOL isPaletteName = [cellData.key isEqualToString:@"paletteName"]; + cell.selectionStyle = isPaletteName ? UITableViewCellSelectionStyleNone : UITableViewCellSelectionStyleDefault; cell.separatorInset = UIEdgeInsetsMake(0., isPaletteName ? 0. : self.tableView.frame.size.width, 0., 0.); cell.titleLabel.text = cellData.title; cell.titleLabel.textColor = cellData.tintColor ?: [UIColor colorNamed:ACColorNameTextColorPrimary]; @@ -2283,7 +2351,7 @@ - (void)onColoringTypeSelected:(OATrackAppearanceItem *)selectedItem _gradientColorsCollection = [[GradientColorsCollection alloc] initWithColorizationType:(ColorizationType) [_selectedItem.coloringType toColorizationType]]; _sortedPaletteColorItems = [NSMutableArray arrayWithArray:[_gradientColorsCollection getPaletteColors]]; _selectedPaletteColorItem = [_gradientColorsCollection getDefaultGradientPalette]; - self.gpx.gradientPaletteName = @"default"; + self.gpx.gradientPaletteName = PaletteGradientColor.defaultName; } NSString *coloringType = [self isSelectedTypeAttribute] ? _selectedItem.attrName : _selectedItem.coloringType.name; @@ -2415,9 +2483,9 @@ - (void)deleteColorItem:(OAColorItem *)colorItem - (void)onCollectionItemSelected:(NSIndexPath *)indexPath { - _isNewColorSelected = YES; if ([self isSelectedTypeSolid]) { + _isNewColorSelected = YES; _selectedColorItem = _sortedColorItems[indexPath.row]; self.gpx.color = _selectedColorItem.value; if (_wholeFolderTracks) diff --git a/Sources/Helpers/Appearance/GradientColorsCollection.swift b/Sources/Helpers/Appearance/GradientColorsCollection.swift index 28cc3b84fb..e23d826f97 100644 --- a/Sources/Helpers/Appearance/GradientColorsCollection.swift +++ b/Sources/Helpers/Appearance/GradientColorsCollection.swift @@ -52,6 +52,24 @@ final class GradientColorsCollection: ColorsCollection { self.init(terrainType) } + func hasRouteGradientPalette(by fileName: String) -> Bool { + return getPaletteColors().contains { paletteColor in + guard let gradientColor = paletteColor as? PaletteGradientColor else { return false } + let expectedFileName = ColorPaletteHelper.routePrefix + gradientColor.stringId + TXT_EXT + return fileName == expectedFileName + } + } + + func hasTerrainGradientPalette(by fileName: String) -> Bool { + guard gradientType is TerrainType else { return false } + let prefixes = [TerrainMode.hillshadePrefix, TerrainMode.colorSlopePrefix, TerrainMode.heightPrefix] + return getPaletteColors().contains { paletteColor in + guard let gradientColor = paletteColor as? PaletteGradientColor else { return false } + guard let key = TerrainMode.getKeyByPaletteName(gradientColor.paletteName) else { return false } + return prefixes.contains(where: { $0 + key + TXT_EXT == fileName }) + } + } + func getPaletteColors() -> [PaletteColor] { getColors(.lastUsedTime) } diff --git a/Sources/Helpers/ConcurrentDictionary.swift b/Sources/Helpers/ConcurrentDictionary.swift index dc9c1d11e5..f0ad24ffb6 100644 --- a/Sources/Helpers/ConcurrentDictionary.swift +++ b/Sources/Helpers/ConcurrentDictionary.swift @@ -43,5 +43,11 @@ class ConcurrentDictionary { defer { pthread_rwlock_unlock(&rwlock) } return Array(dictionary.values) } + + func getAllKeys() -> [Key] { + pthread_rwlock_rdlock(&rwlock) + defer { pthread_rwlock_unlock(&rwlock) } + return Array(dictionary.keys) + } } diff --git a/Sources/Helpers/DirectoryObserver.swift b/Sources/Helpers/DirectoryObserver.swift new file mode 100644 index 0000000000..59a0cfee59 --- /dev/null +++ b/Sources/Helpers/DirectoryObserver.swift @@ -0,0 +1,80 @@ +// +// DirectoryObserver.swift +// OsmAnd Maps +// +// Created by Skalii on 26.07.2024. +// Copyright © 2024 OsmAnd. All rights reserved. +// + +import Foundation + +@objc enum DirectoryObserverType: Int { + case colorPalette + + var path: String { + switch self { + case .colorPalette: + return OsmAndApp.swiftInstance().colorsPalettePath + } + } + + var notificationName: NSNotification.Name { + switch self { + case .colorPalette: + return NSNotification.Name("ColorPaletteDicrectoryUpdated") + } + } +} + +@objcMembers +final class DirectoryObserver: NSObject { + + static let updatedKey = "updated" + static let deletedKey = "deleted" + static let createdKey = "created" + + private let type: DirectoryObserverType + + private var directoryFileDescriptor: CInt = -1 + private var dispatchSource: DispatchSourceFileSystemObject? + private let queue = DispatchQueue(label: "DirectoryObserverQueue", attributes: .concurrent) + + init(_ type: DirectoryObserverType) { + self.type = type + } + + func startObserving() { + // Open the directory + directoryFileDescriptor = open(type.path, O_EVTONLY) + + guard directoryFileDescriptor != -1 else { + debugPrint("Unable to open directory: \(type.path)") + return + } + + // Create the dispatch source + dispatchSource = DispatchSource.makeFileSystemObjectSource( + fileDescriptor: directoryFileDescriptor, + eventMask: .all, + queue: queue + ) + + dispatchSource?.setEventHandler { [weak self] in + guard let self else { return } + + debugPrint("Directory contents changed: \(self.type.path)") + NotificationCenter.default.post(name: self.type.notificationName, object: self.type.path) + } + + dispatchSource?.setCancelHandler { [weak self] in + guard let self else { return } + + close(self.directoryFileDescriptor) + self.directoryFileDescriptor = -1 + dispatchSource = nil + } + + // Start monitoring + dispatchSource?.resume() + } +} diff --git a/Sources/Helpers/OAOsmAndFormatter.h b/Sources/Helpers/OAOsmAndFormatter.h index 89b0318b5b..dd0283303a 100644 --- a/Sources/Helpers/OAOsmAndFormatter.h +++ b/Sources/Helpers/OAOsmAndFormatter.h @@ -64,5 +64,7 @@ + (NSString *)getFormattedOsmTagValue:(NSString *)tagValue; + (NSString *)getFormattedDurationShort:(NSTimeInterval)seconds fullForm:(BOOL)fullForm; + (NSString *)getFormattedDuration:(NSTimeInterval)seconds; ++ (NSTimeInterval)getStartOfDayForTime:(NSTimeInterval)timestamp; ++ (NSTimeInterval)getStartOfToday; @end diff --git a/Sources/Helpers/OAOsmAndFormatter.mm b/Sources/Helpers/OAOsmAndFormatter.mm index e2423dfe9c..e57f588372 100644 --- a/Sources/Helpers/OAOsmAndFormatter.mm +++ b/Sources/Helpers/OAOsmAndFormatter.mm @@ -829,4 +829,21 @@ + (int)lowerTo10BaseRoundingBounds:(int)num withRoundRange:(NSArray return num; } ++ (NSTimeInterval) getStartOfDayForTime:(NSTimeInterval)timestamp +{ + NSCalendar *calendar = [NSCalendar currentCalendar]; + NSDateComponents *components = [calendar components:(NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:[NSDate dateWithTimeIntervalSince1970:timestamp]]; + [components setHour:0]; + [components setMinute:0]; + [components setSecond:0]; + [components setNanosecond:0]; + NSDate *formattedDate = [calendar dateFromComponents:components]; + return [formattedDate timeIntervalSince1970]; +} + ++ (NSTimeInterval) getStartOfToday +{ + return [self.class getStartOfDayForTime:[[NSDate now] timeIntervalSince1970]]; +} + @end diff --git a/Sources/Plugins/SRTMPlugin/TerrainMode.swift b/Sources/Plugins/SRTMPlugin/TerrainMode.swift index 1995b9c63a..894ca67219 100644 --- a/Sources/Plugins/SRTMPlugin/TerrainMode.swift +++ b/Sources/Plugins/SRTMPlugin/TerrainMode.swift @@ -49,6 +49,8 @@ final class TerrainMode: NSObject { static let colorSlopePrefix = "slope_" static let heightPrefix = "height_" + private static let queue = DispatchQueue(label: "TerrainModeValuesQueue") + static var values: [TerrainMode] { guard let terrainModes else { Self.reloadTerrainModes() @@ -80,55 +82,71 @@ final class TerrainMode: NSObject { } static func getMode(_ type: TerrainType, keyName: String) -> TerrainMode? { - guard let terrainModes else { + queue.sync { + guard let terrainModes else { + return nil + } + for mode in terrainModes where mode.type == type && mode.getKeyName() == keyName { + return mode + } return nil } - for mode in terrainModes where mode.type == type && mode.getKeyName() == keyName { - return mode - } - return nil } static func getDefaultMode(_ type: TerrainType) -> TerrainMode? { - guard let terrainModes else { + queue.sync { + guard let terrainModes else { + return nil + } + for mode in terrainModes where mode.type == type && mode.isDefaultMode() { + return mode + } return nil } - for mode in terrainModes where mode.type == type && mode.isDefaultMode() { - return mode - } - return nil } static func getByKey(_ key: String) -> TerrainMode? { - return terrainModes?.first { $0.getKeyName() == key } + queue.sync { + return terrainModes?.first { $0.getKeyName() == key } ?? terrainModes?.first { $0.type == .hillshade } + } + } + + static func getKeyByPaletteName(_ name: String) -> String? { + queue.sync { + terrainModes?.first(where: { $0.getKeyName() == name })?.key + } } static func isModeExist(_ key: String) -> Bool { - terrainModes?.contains { $0.getKeyName() == key } ?? false - } - - private static func reloadTerrainModes() { - var modes = [TerrainMode]() - modes.append(TerrainMode(defaultKey, type: .hillshade, translateName: localizedString("shared_string_hillshade"))) - modes.append(TerrainMode(defaultKey, type: .slope, translateName: localizedString("shared_string_slope"))) - - let prefixes = [ - Pair(hillshadePrefix, TerrainType.hillshade), - Pair(colorSlopePrefix, TerrainType.slope), - Pair(heightPrefix, TerrainType.height) - ] - if let dir = OsmAndApp.swiftInstance().colorsPalettePath, - let files = try? FileManager.default.contentsOfDirectory(atPath: dir) { - for file in files where file.hasSuffix(TXT_EXT) { - for prefix in prefixes { - if let terrainMode = getTerrainMode(by: file, prefix: prefix) { - modes.append(terrainMode) + queue.sync { + terrainModes?.contains { $0.getKeyName() == key } ?? false + } + } + + static func reloadTerrainModes() { + queue.sync { + var modes = [TerrainMode]() + modes.append(TerrainMode(defaultKey, type: .hillshade, translateName: localizedString("shared_string_hillshade"))) + modes.append(TerrainMode(defaultKey, type: .slope, translateName: localizedString("shared_string_slope"))) + + let prefixes = [ + Pair(hillshadePrefix, TerrainType.hillshade), + Pair(colorSlopePrefix, TerrainType.slope), + Pair(heightPrefix, TerrainType.height) + ] + if let dir = OsmAndApp.swiftInstance().colorsPalettePath, + let files = try? FileManager.default.contentsOfDirectory(atPath: dir) { + for file in files where file.hasSuffix(TXT_EXT) { + for prefix in prefixes { + if let terrainMode = getTerrainMode(by: file, prefix: prefix) { + modes.append(terrainMode) + } } } } + terrainModes = modes } - terrainModes = modes } private static func getTerrainMode(by file: String, prefix: Pair) -> TerrainMode? { diff --git a/Sources/Plugins/Weather/OAWeatherPlugin.h b/Sources/Plugins/Weather/OAWeatherPlugin.h index 8fed114489..0c5a4d6315 100644 --- a/Sources/Plugins/Weather/OAWeatherPlugin.h +++ b/Sources/Plugins/Weather/OAWeatherPlugin.h @@ -22,4 +22,7 @@ static NSString * const kWeatherPrecip = @"weather_precip"; - (void)updateWidgetsInfo; - (NSArray *)createWidgetsControls; +- (void) setForecastDate:(NSDate *)date forAnimation:(BOOL)forAnimation resetPeriod:(BOOL)resetPeriod; +- (void) prepareForDayAnimation:(NSDate *)date; + @end diff --git a/Sources/Plugins/Weather/OAWeatherPlugin.mm b/Sources/Plugins/Weather/OAWeatherPlugin.mm index 6dae4d5b31..213fd7d650 100644 --- a/Sources/Plugins/Weather/OAWeatherPlugin.mm +++ b/Sources/Plugins/Weather/OAWeatherPlugin.mm @@ -12,6 +12,7 @@ #import "OAMapHudViewController.h" #import "OAMapInfoWidgetsFactory.h" #import "OAMapWidgetRegInfo.h" +#import "OAMapLayers.h" #import "OAAppData.h" #import "OAIAPHelper.h" #import "OAProducts.h" @@ -32,6 +33,8 @@ @implementation OAWeatherPlugin OAWeatherWidget *_weatherWindSpeedControl; OAWeatherWidget *_weatherCloudControl; OAWeatherWidget *_weatherPrecipControl; + + NSDate *_forecastDate; } - (instancetype)init @@ -188,4 +191,39 @@ - (NSString *) getDescription ]; } +- (void) setForecastDate:(NSDate *)date forAnimation:(BOOL)forAnimation resetPeriod:(BOOL)resetPeriod +{ + _forecastDate = date; + [self updateLayersDate:forAnimation resetPeriod:resetPeriod]; +} + +- (void) updateLayersDate:(BOOL)forAnimation resetPeriod:(BOOL)resetPeriod +{ + NSDate *time = _forecastDate ?: [NSDate now]; + NSTimeInterval timestampSec = [time timeIntervalSince1970]; + int64_t timestamp = timestampSec * 1000; + OAMapViewController *mapViewController = [OARootViewController instance].mapPanel.mapViewController; + + if (mapViewController.mapLayers.weatherLayerLow) + [mapViewController.mapLayers.weatherLayerLow setDateTime:timestamp goForward:forAnimation resetPeriod:resetPeriod]; + + if (mapViewController.mapLayers.weatherLayerHigh) + [mapViewController.mapLayers.weatherLayerHigh setDateTime:timestamp goForward:forAnimation resetPeriod:resetPeriod]; + + if (mapViewController.mapLayers.weatherContourLayer) + [mapViewController.mapLayers.weatherContourLayer setDateTime:timestampSec]; +} + +- (void) prepareForDayAnimation:(NSDate *)date +{ + int64_t timestamp = [date timeIntervalSince1970] * 1000; + + OAMapViewController *mapViewController = [OARootViewController instance].mapPanel.mapViewController; + if (mapViewController.mapLayers.weatherLayerLow) + [mapViewController.mapLayers.weatherLayerLow setDateTime:timestamp goForward:YES resetPeriod:NO]; + + if (mapViewController.mapLayers.weatherLayerHigh) + [mapViewController.mapLayers.weatherLayerHigh setDateTime:timestamp goForward:YES resetPeriod:NO]; +} + @end diff --git a/Sources/Router/Colorization/ColorPalette.swift b/Sources/Router/Colorization/ColorPalette.swift index 7d97992116..a0991195bc 100644 --- a/Sources/Router/Colorization/ColorPalette.swift +++ b/Sources/Router/Colorization/ColorPalette.swift @@ -141,10 +141,8 @@ final class ColorPalette: NSObject { static func parseColorPalette(from filePath: String, shouldSort: Bool) throws -> ColorPalette { let palette = ColorPalette() - do { - let attributes = try FileManager.default.attributesOfItem(atPath: filePath) - palette.lastModified = attributes[.modificationDate] as? Date - } + let attributes = try FileManager.default.attributesOfItem(atPath: filePath) + palette.lastModified = attributes[.modificationDate] as? Date let content = try String(contentsOfFile: filePath, encoding: .utf8) let lines = content.components(separatedBy: .newlines) diff --git a/Sources/Router/Colorization/ColorPaletteHelper.swift b/Sources/Router/Colorization/ColorPaletteHelper.swift index 64fd3b8a53..6124bd8a21 100644 --- a/Sources/Router/Colorization/ColorPaletteHelper.swift +++ b/Sources/Router/Colorization/ColorPaletteHelper.swift @@ -13,13 +13,60 @@ final class ColorPaletteHelper: NSObject { static let shared = ColorPaletteHelper() static let routePrefix = "route_" + static let weatherPrefix = "weather_" static let gradientIdSplitter = "_" + static let colorPalettesUpdatedNotification = NSNotification.Name("ColorPalettesUpdated") + + private let directoryObserverType = DirectoryObserverType.colorPalette private var app: OsmAndAppProtocol + private var directoryObserver: DirectoryObserver private var cachedColorPalette = ConcurrentDictionary() private override init() { app = OsmAndApp.swiftInstance() + directoryObserver = DirectoryObserver(directoryObserverType) + directoryObserver.startObserving() + + super.init() + + NotificationCenter.default.addObserver(self, selector: #selector(onColorPaletteDirectoryUpdated(_ :)), name: directoryObserverType.notificationName, object: nil) + } + + @objc private func onColorPaletteDirectoryUpdated(_ notification: Notification) { + if let path = notification.object as? String { + do { + var colorPaletteFilesUpdated = [String: String]() + let files = try FileManager.default.contentsOfDirectory(atPath: path) + let deletedPalettes = cachedColorPalette.getAllKeys().filter { !files.contains($0) } + for deletedPalette in deletedPalettes { + colorPaletteFilesUpdated[deletedPalette] = DirectoryObserver.deletedKey + cachedColorPalette.removeValue(forKey: deletedPalette) + } + for file in files { + let colorPaletteFileName = file.lastPathComponent() + let cachedPalette = cachedColorPalette.getValue(forKey: colorPaletteFileName) + if let cachedPalette { + let attributes = try FileManager.default.attributesOfItem(atPath: getColorPaletteDir().appendingPathComponent(colorPaletteFileName)) + if attributes[.modificationDate] as? Date != cachedPalette.lastModified { + if parseGradientColorPalette(colorPaletteFileName) != nil { + colorPaletteFilesUpdated[colorPaletteFileName] = DirectoryObserver.updatedKey + } + } + } else { + if parseGradientColorPalette(colorPaletteFileName) != nil { + colorPaletteFilesUpdated[colorPaletteFileName] = DirectoryObserver.createdKey + } + } + } + if colorPaletteFilesUpdated.keys.contains(where: { !$0.hasPrefix(Self.routePrefix) && !$0.hasPrefix(Self.weatherPrefix) && colorPaletteFilesUpdated[$0] != DirectoryObserver.updatedKey }) { + TerrainMode.reloadTerrainModes() + } + NotificationCenter.default.post(name: Self.colorPalettesUpdatedNotification, object: colorPaletteFilesUpdated) + } catch { + debugPrint("Error updated color palette contents of: \(path)") + } + } } static func getRoutePaletteFileName(_ colorizationType: ColorizationType, gradientPaletteName: String) -> String { @@ -36,50 +83,39 @@ final class ColorPaletteHelper: NSObject { return colorPalettes } - func requireGradientColorPaletteSync(_ colorizationType: ColorizationType, gradientPaletteName: String, error: NSErrorPointer) -> ColorPalette { - guard let colorPalette = getGradientColorPaletteSync(colorizationType, gradientPaletteName: gradientPaletteName, error: error), isValidPalette(colorPalette) else { + func requireGradientColorPaletteSync(_ colorizationType: ColorizationType, gradientPaletteName: String) -> ColorPalette { + guard let colorPalette = getGradientColorPaletteSync(colorizationType, gradientPaletteName: gradientPaletteName), isValidPalette(colorPalette) else { return OARouteColorize.getDefaultPalette(colorizationType.rawValue) } return colorPalette } - func getGradientColorPaletteSync(_ colorizationType: ColorizationType, gradientPaletteName: String, error: NSErrorPointer) -> ColorPalette? { - getGradientColorPalette(Self.getRoutePaletteFileName(colorizationType, gradientPaletteName: gradientPaletteName), error: error) + func getGradientColorPaletteSync(_ colorizationType: ColorizationType, gradientPaletteName: String) -> ColorPalette? { + getGradientColorPalette(Self.getRoutePaletteFileName(colorizationType, gradientPaletteName: gradientPaletteName)) } - func getGradientColorPaletteSyncWithModeKey(_ modeKey: String, error: NSErrorPointer) -> ColorPalette? { - getGradientColorPalette(modeKey, error: error) + func getGradientColorPaletteSyncWithModeKey(_ modeKey: String) -> ColorPalette? { + getGradientColorPalette(modeKey) } - func getGradientColorPalette(_ colorPaletteFileName: String, error: NSErrorPointer) -> ColorPalette? { - if isColorPaletteUpdated(colorPaletteFileName, error: error) { - return parseGradientColorPalette(colorPaletteFileName) - } - return cachedColorPalette.getValue(forKey: colorPaletteFileName) - } - - func isColorPaletteUpdated(_ colorPaletteFileName: String, error: NSErrorPointer) -> Bool { + func getGradientColorPalette(_ colorPaletteFileName: String) -> ColorPalette? { guard let cachedPalette = cachedColorPalette.getValue(forKey: colorPaletteFileName) else { - return true - } - do { - let attributes = try FileManager.default.attributesOfItem(atPath: getColorPaletteDir().appendingPathComponent(colorPaletteFileName)) - return attributes[.modificationDate] as? Date != cachedPalette.lastModified - } catch let err as NSError { - error?.pointee = err - return false + return parseGradientColorPalette(colorPaletteFileName) } + return cachedPalette } private func parseGradientColorPalette(_ colorPaletteFileName: String) -> ColorPalette? { - let filePath = getColorPaletteDir().appendingPathComponent(colorPaletteFileName) - if FileManager.default.fileExists(atPath: filePath) { - do { - let colorPalette = try ColorPalette.parseColorPalette(from: filePath) - cachedColorPalette.setValue(colorPalette, forKey: colorPaletteFileName) - return colorPalette - } catch { - debugPrint("Error reading color file: \(error)") + if colorPaletteFileName.hasSuffix(TXT_EXT) { + let filePath = getColorPaletteDir().appendingPathComponent(colorPaletteFileName) + if FileManager.default.fileExists(atPath: filePath) { + do { + let colorPalette = try ColorPalette.parseColorPalette(from: filePath) + cachedColorPalette.setValue(colorPalette, forKey: colorPaletteFileName) + return colorPalette + } catch { + debugPrint("Error reading color file: \(error)") + } } } return nil @@ -92,13 +128,10 @@ final class ColorPaletteHelper: NSObject { let colorFiles = try FileManager.default.contentsOfDirectory(atPath: getColorPaletteDir()) for fileName in colorFiles where fileName.hasPrefix(colorTypePrefix) && fileName.hasSuffix(TXT_EXT) { let colorPalleteName = fileName.replacingOccurrences(of: colorTypePrefix, with: "").replacingOccurrences(of: TXT_EXT, with: "") - var error: NSError? - if let colorPalette = getGradientColorPalette(fileName, error: &error) { + if let colorPalette = getGradientColorPalette(fileName) { let fileAttributes = try FileManager.default.attributesOfItem(atPath: getColorPaletteDir().appendingPathComponent(fileName)) let modificationDate = fileAttributes[.modificationDate] as! Date colorPalettes[colorPalleteName] = [colorPalette, Int64(modificationDate.timeIntervalSince1970)] - } else if let error { - debugPrint("Error reading color palette file: \(error.description)") } } } catch { @@ -112,13 +145,11 @@ final class ColorPaletteHelper: NSObject { for mode in TerrainMode.values where mode.type == type { let fileName = mode.getMainFile() let filePath = getColorPaletteDir().appendingPathComponent(fileName) - var error: NSError? - if let colorPalette = getGradientColorPalette(fileName, error: &error), FileManager.default.fileExists(atPath: filePath) { + if let colorPalette = getGradientColorPalette(fileName), + FileManager.default.fileExists(atPath: filePath) { let fileAttributes = try? FileManager.default.attributesOfItem(atPath: filePath) let modificationDate = fileAttributes?[.modificationDate] as? Date colorPalettes[mode.getKeyName()] = [colorPalette, Int64(modificationDate?.timeIntervalSince1970 ?? 0)] - } else if let error { - debugPrint("Error reading color palette file: \(error.description)") } } return colorPalettes diff --git a/Sources/Views/OAMapRendererView.h b/Sources/Views/OAMapRendererView.h index 9c60debf38..c52ee2471c 100644 --- a/Sources/Views/OAMapRendererView.h +++ b/Sources/Views/OAMapRendererView.h @@ -107,6 +107,7 @@ struct CLLocationCoordinate2D; - (BOOL) suspendGpuWorker; - (BOOL) resumeGpuWorker; - (void) invalidateFrame; +- (void) changeTimePeriod; - (void) setSymbolsOpacity:(float)opacityFactor; - (void) setDateTime:(int64_t)dateTime; - (void) setSymbolSubsectionConfiguration:(int)subsectionIndex configuration:(const OsmAnd::SymbolSubsectionConfiguration &)configuration; diff --git a/Sources/Views/OAMapRendererView.mm b/Sources/Views/OAMapRendererView.mm index a8358d0f8d..523b4da6bc 100644 --- a/Sources/Views/OAMapRendererView.mm +++ b/Sources/Views/OAMapRendererView.mm @@ -1152,6 +1152,11 @@ - (void)setDateTime:(int64_t)dateTime _renderer->setDateTime(dateTime); } +- (void) changeTimePeriod +{ + _renderer->changeTimePeriod(); +} + - (void)setSymbolSubsectionConfiguration:(int)subsectionIndex configuration:(const OsmAnd::SymbolSubsectionConfiguration &)configuration { _renderer->setSymbolSubsectionConfiguration(subsectionIndex, configuration); diff --git a/Sources/Views/OASegmentedSlider.h b/Sources/Views/OASegmentedSlider.h index cfa4ff5c87..b9259605f1 100644 --- a/Sources/Views/OASegmentedSlider.h +++ b/Sources/Views/OASegmentedSlider.h @@ -23,5 +23,6 @@ additionalMarksBetween:(NSInteger)additionalMarksBetween; - (void)makeCustom; +- (void)setUsingExtraThumbInset:(BOOL)isUsing; @end diff --git a/Sources/Views/OASegmentedSlider.mm b/Sources/Views/OASegmentedSlider.mm index 7a9b566ec2..a5f8258622 100644 --- a/Sources/Views/OASegmentedSlider.mm +++ b/Sources/Views/OASegmentedSlider.mm @@ -12,16 +12,17 @@ #import "Localization.h" #import "GeneratedAssetSymbols.h" -#define kMarkTag 1000 -#define kAdditionalMarkTag 2000 -#define kTitleLabelTag 3000 +static NSInteger kMarkTag = 1000; +static NSInteger kAdditionalMarkTag = 2000; +static NSInteger kTitleLabelTag = 3000; -#define kMarkHeight 16. -#define kCustomMarkHeight 14. -#define kAdditionalMarkHeight 8. -#define kCurrentMarkHeight 30. +static CGFloat kMarkHeight = 16; +static CGFloat kCustomMarkHeight = 14; +static CGFloat kAdditionalMarkHeight = 8; +static CGFloat kCurrentMarkHeight = 30; +static CGFloat kRoundThumbSize = 24; -#define kMarkWidth 2. +static CGFloat kMarkWidth = 2; @implementation OASegmentedSlider { @@ -32,6 +33,7 @@ @implementation OASegmentedSlider NSInteger _selectingMark; NSInteger _additionalMarksBetween; BOOL _isCustomSlider; + BOOL _useExtraThumbOffset; UIView *_selectingMarkTitleBackground; UILabel *_selectingMarkTitle; @@ -299,14 +301,14 @@ - (void)layoutMarks return; CGFloat segments = [self getMarksCount] - 1; - CGFloat sliderViewWidth = self.frame.size.width; + CGFloat sliderViewWidth = self.frame.size.width - [self getExtraThumbInset] * 2; CGFloat sliderViewHeight = self.frame.size.height; CGRect sliderViewBounds = CGRectMake(0., 0., sliderViewWidth, sliderViewHeight); CGRect trackRect = [self trackRectForBounds:sliderViewBounds]; CGFloat trackWidth = trackRect.size.width; CGFloat trackHeight = trackRect.size.height; - CGFloat inset = (sliderViewWidth - trackRect.size.width) / 2; + CGFloat inset = (sliderViewWidth - trackRect.size.width) / 2 + [self getExtraThumbInset]; CGFloat markHeight = _isCustomSlider ? kCustomMarkHeight + 1. : kMarkHeight; CGFloat x = inset; @@ -332,12 +334,19 @@ - (void)layoutMarks CGSize textSize = [OAUtilities calculateTextBounds:titleLabel.text font:titleLabel.font]; CGFloat tx; - if (i == 0) - tx = mark.frame.origin.x; - else if (i == marksCount - 1) - tx = trackWidth - textSize.width; + if (!_useExtraThumbOffset) + { + if (i == 0) + tx = mark.frame.origin.x; + else if (i == marksCount - 1) + tx = trackWidth - textSize.width; + else + tx = mark.frame.origin.x + mark.frame.size.width / 2 - textSize.width / 2; + } else + { tx = mark.frame.origin.x + mark.frame.size.width / 2 - textSize.width / 2; + } titleLabel.frame = CGRectMake(tx, mark.frame.origin.y + mark.frame.size.height, textSize.width, textSize.height); } @@ -349,9 +358,9 @@ - (void)layoutCurrentMarkLine { CGRect sliderViewBounds = CGRectMake(0., 0., self.frame.size.width, self.frame.size.height); CGRect trackRect = [self trackRectForBounds:sliderViewBounds]; - CGFloat trackWidth = trackRect.size.width; + CGFloat trackWidth = trackRect.size.width - [self getExtraThumbInset] * 2; CGFloat trackHeight = trackRect.size.height; - CGFloat inset = (self.frame.size.width - trackRect.size.width) / 2; + CGFloat inset = (self.frame.size.width - trackRect.size.width) / 2 + [self getExtraThumbInset]; if (_currentMarkX != -1) { @@ -615,4 +624,14 @@ - (void)makeCustom } } +- (void) setUsingExtraThumbInset:(BOOL)isUsing +{ + _useExtraThumbOffset = isUsing; +} + +- (CGFloat) getExtraThumbInset +{ + return _useExtraThumbOffset ? kRoundThumbSize / 2 : 0; +} + @end diff --git a/Sources/Weather/OAWeatherHelper.h b/Sources/Weather/OAWeatherHelper.h index e9442268ee..6c6158b94e 100644 --- a/Sources/Weather/OAWeatherHelper.h +++ b/Sources/Weather/OAWeatherHelper.h @@ -105,6 +105,8 @@ typedef NS_ENUM(NSInteger, EOAWeatherAutoUpdate) + (NSDate *) roundForecastTimeToHour:(NSDate *)date; - (BOOL)allLayersAreDisabled; +- (BOOL)isProcessingTiles; + @end //NS_ASSUME_NONNULL_END diff --git a/Sources/Weather/OAWeatherHelper.mm b/Sources/Weather/OAWeatherHelper.mm index 7d418d9d15..c76ff9afce 100644 --- a/Sources/Weather/OAWeatherHelper.mm +++ b/Sources/Weather/OAWeatherHelper.mm @@ -26,6 +26,7 @@ #import "OAColors.h" #import #import "OAPluginsHelper.h" +#import "OAWeatherWebClient.h" #import "OsmAnd_Maps-Swift.h" #include @@ -857,4 +858,9 @@ - (BOOL)allLayersAreDisabled return bands.isEmpty(); } +- (BOOL)isProcessingTiles +{ + return _weatherResourcesManager != nil && _weatherResourcesManager->isProcessingTiles(); +} + @end diff --git a/Sources/Weather/OAWeatherWebClient.h b/Sources/Weather/OAWeatherWebClient.h index 3109396db1..d8456b7bb8 100644 --- a/Sources/Weather/OAWeatherWebClient.h +++ b/Sources/Weather/OAWeatherWebClient.h @@ -8,9 +8,11 @@ #include #include +#import "OAAtomicInteger.h" #include +static NSString *kOAWeatherWebClientNotificationKey = @"kOAWeatherWebClientNotificationKey"; class OAWeatherRequestResult : public OsmAnd::IWebClient::IRequestResult { @@ -42,6 +44,7 @@ class OAWeatherWebClient : public OsmAnd::IWebClient { private: NSURLSession *_urlSession; + OAAtomicInteger *_activeRequestsCounter; protected: public: OAWeatherWebClient(); diff --git a/Sources/Weather/OAWeatherWebClient.mm b/Sources/Weather/OAWeatherWebClient.mm index 3d10479353..145756c036 100644 --- a/Sources/Weather/OAWeatherWebClient.mm +++ b/Sources/Weather/OAWeatherWebClient.mm @@ -41,6 +41,7 @@ OAWeatherWebClient::OAWeatherWebClient() { + _activeRequestsCounter = [[OAAtomicInteger alloc] initWithInteger:0]; NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfiguration.timeoutIntervalForRequest = 300.0; sessionConfiguration.timeoutIntervalForResource = 600.0; @@ -95,11 +96,13 @@ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSURLSessionDataTask *task = [_urlSession dataTaskWithRequest:request completionHandler:^(NSData *_data, NSURLResponse *_response, NSError *_error) { + [NSNotificationCenter.defaultCenter postNotificationName:kOAWeatherWebClientNotificationKey object:@([_activeRequestsCounter decrementAndGet])]; response = _response; error = _error; dispatch_semaphore_signal(semaphore); } ]; + [NSNotificationCenter.defaultCenter postNotificationName:kOAWeatherWebClientNotificationKey object:@([_activeRequestsCounter incrementAndGet])]; [task resume]; if (dataRequest.queryController) @@ -156,12 +159,15 @@ semaphore = dispatch_semaphore_create(0); task = [_urlSession dataTaskWithRequest:request completionHandler:^(NSData *_data, NSURLResponse *_response, NSError *_error) { + [NSNotificationCenter.defaultCenter postNotificationName:kOAWeatherWebClientNotificationKey object:@([_activeRequestsCounter decrementAndGet])]; response = _response; data = _data; error = _error; dispatch_semaphore_signal(semaphore); } ]; + + [NSNotificationCenter.defaultCenter postNotificationName:kOAWeatherWebClientNotificationKey object:@([_activeRequestsCounter incrementAndGet])]; [task resume]; if (dataRequest.queryController) diff --git a/Sources/Weather/WeatherNavigationBarView/WeatherNavigationBarView.swift b/Sources/Weather/WeatherNavigationBarView/WeatherNavigationBarView.swift index 5b038ca5d6..edcfdc7be2 100644 --- a/Sources/Weather/WeatherNavigationBarView/WeatherNavigationBarView.swift +++ b/Sources/Weather/WeatherNavigationBarView/WeatherNavigationBarView.swift @@ -11,6 +11,10 @@ final class WeatherNavigationBarView: UIView { didSet { rightButton.setImage(UIImage.templateImageNamed("ic_navbar_settings"), for: .normal) rightButton.tintColor = .iconColorActive + + //temporarily hiding Data Source button + rightButton.isHidden = true + rightButton.isEnabled = false } }