Commit 66f0c4e5 by Demid Merzlyakov

Removed unused files.

parent 2bfd5b55
...@@ -8,11 +8,6 @@ ...@@ -8,11 +8,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
34EAFD887EF2D1D7449A016C /* Pods_1Weather.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B543196B99BA697763514F6 /* Pods_1Weather.framework */; }; 34EAFD887EF2D1D7449A016C /* Pods_1Weather.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B543196B99BA697763514F6 /* Pods_1Weather.framework */; };
8708801C2578DDD50076BFB1 /* WdtLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8708801B2578DDD50076BFB1 /* WdtLocation.swift */; };
870880212578ED190076BFB1 /* WdtDaySummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8708801E2578ED190076BFB1 /* WdtDaySummary.swift */; };
870880222578ED190076BFB1 /* WdtHourSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8708801F2578ED190076BFB1 /* WdtHourSummary.swift */; };
870880232578ED190076BFB1 /* WdtCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870880202578ED190076BFB1 /* WdtCondition.swift */; };
870880262578F7030076BFB1 /* WeatherUpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870880252578F7030076BFB1 /* WeatherUpdateManager.swift */; };
CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1237C2255D5C5900C98139 /* AppDelegate.swift */; }; CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1237C2255D5C5900C98139 /* AppDelegate.swift */; };
CD1237CC255D5C5C00C98139 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD1237CB255D5C5C00C98139 /* Assets.xcassets */; }; CD1237CC255D5C5C00C98139 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD1237CB255D5C5C00C98139 /* Assets.xcassets */; };
CD1237CF255D5C5C00C98139 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD1237CD255D5C5C00C98139 /* LaunchScreen.storyboard */; }; CD1237CF255D5C5C00C98139 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD1237CD255D5C5C00C98139 /* LaunchScreen.storyboard */; };
...@@ -28,7 +23,6 @@ ...@@ -28,7 +23,6 @@
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; }; CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */; };
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; }; CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B303D25726960004B34B3 /* ThemeProtocol.swift */; };
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B304225726AD1004B34B3 /* DefaultTheme.swift */; }; CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6B304225726AD1004B34B3 /* DefaultTheme.swift */; };
CD8091772578D73F003541A4 /* PopularCitiesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8091762578D73F003541A4 /* PopularCitiesManager.swift */; };
CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */; }; CD80917B2578E4A8003541A4 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */; };
CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD822FF425D6817000A05501 /* CityForecastCell.swift */; }; CD822FF525D6817000A05501 /* CityForecastCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD822FF425D6817000A05501 /* CityForecastCell.swift */; };
CD822FFA25D6890900A05501 /* OneWeatherColorsAsset.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */; }; CD822FFA25D6890900A05501 /* OneWeatherColorsAsset.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */; };
...@@ -36,10 +30,6 @@ ...@@ -36,10 +30,6 @@
CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300225D69DE400A05501 /* CityConditionsCell.swift */; }; CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300225D69DE400A05501 /* CityConditionsCell.swift */; };
CD82300725D6A73F00A05501 /* CityConditionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300625D6A73E00A05501 /* CityConditionButton.swift */; }; CD82300725D6A73F00A05501 /* CityConditionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300625D6A73E00A05501 /* CityConditionButton.swift */; };
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300925D6B2AF00A05501 /* AppTabBarController.swift */; }; CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD82300925D6B2AF00A05501 /* AppTabBarController.swift */; };
CDA69B2C2574F3C800CB6409 /* CityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B2B2574F3C800CB6409 /* CityCell.swift */; };
CDA69B30257500E200CB6409 /* GeoNamesPlace.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */; };
CDA69B3325750D3400CB6409 /* LocationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */; };
CDD0F1DF2572403E00CF5017 /* LocationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */; };
CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CDD0F1E42572425200CF5017 /* SF-Pro.ttf */; }; CDD0F1E52572425200CF5017 /* SF-Pro.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CDD0F1E42572425200CF5017 /* SF-Pro.ttf */; };
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1E72572429E00CF5017 /* AppFont.swift */; }; CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1E72572429E00CF5017 /* AppFont.swift */; };
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */; }; CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */; };
...@@ -54,11 +44,6 @@ ...@@ -54,11 +44,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
6B543196B99BA697763514F6 /* Pods_1Weather.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_1Weather.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6B543196B99BA697763514F6 /* Pods_1Weather.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_1Weather.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8708801B2578DDD50076BFB1 /* WdtLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WdtLocation.swift; sourceTree = "<group>"; };
8708801E2578ED190076BFB1 /* WdtDaySummary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtDaySummary.swift; sourceTree = "<group>"; };
8708801F2578ED190076BFB1 /* WdtHourSummary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtHourSummary.swift; sourceTree = "<group>"; };
870880202578ED190076BFB1 /* WdtCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WdtCondition.swift; sourceTree = "<group>"; };
870880252578F7030076BFB1 /* WeatherUpdateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherUpdateManager.swift; sourceTree = "<group>"; };
C8C576F6184B547435CFF0F3 /* Pods-1Weather.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-1Weather.debug.xcconfig"; path = "Target Support Files/Pods-1Weather/Pods-1Weather.debug.xcconfig"; sourceTree = "<group>"; }; C8C576F6184B547435CFF0F3 /* Pods-1Weather.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-1Weather.debug.xcconfig"; path = "Target Support Files/Pods-1Weather/Pods-1Weather.debug.xcconfig"; sourceTree = "<group>"; };
CD1237BF255D5C5900C98139 /* 1Weather.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 1Weather.app; sourceTree = BUILT_PRODUCTS_DIR; }; CD1237BF255D5C5900C98139 /* 1Weather.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 1Weather.app; sourceTree = BUILT_PRODUCTS_DIR; };
CD1237C2255D5C5900C98139 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; CD1237C2255D5C5900C98139 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
...@@ -78,7 +63,6 @@ ...@@ -78,7 +63,6 @@
CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingButton.swift; sourceTree = "<group>"; }; CD6B303A2572680C004B34B3 /* SelfSizingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfSizingButton.swift; sourceTree = "<group>"; };
CD6B303D25726960004B34B3 /* ThemeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeProtocol.swift; sourceTree = "<group>"; }; CD6B303D25726960004B34B3 /* ThemeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeProtocol.swift; sourceTree = "<group>"; };
CD6B304225726AD1004B34B3 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; }; CD6B304225726AD1004B34B3 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; };
CD8091762578D73F003541A4 /* PopularCitiesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularCitiesManager.swift; sourceTree = "<group>"; };
CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = "<group>"; }; CD80917A2578E4A8003541A4 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = "<group>"; };
CD822FF425D6817000A05501 /* CityForecastCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityForecastCell.swift; sourceTree = "<group>"; }; CD822FF425D6817000A05501 /* CityForecastCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityForecastCell.swift; sourceTree = "<group>"; };
CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OneWeatherColorsAsset.xcassets; sourceTree = "<group>"; }; CD822FF925D6890900A05501 /* OneWeatherColorsAsset.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OneWeatherColorsAsset.xcassets; sourceTree = "<group>"; };
...@@ -86,10 +70,6 @@ ...@@ -86,10 +70,6 @@
CD82300225D69DE400A05501 /* CityConditionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionsCell.swift; sourceTree = "<group>"; }; CD82300225D69DE400A05501 /* CityConditionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionsCell.swift; sourceTree = "<group>"; };
CD82300625D6A73E00A05501 /* CityConditionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionButton.swift; sourceTree = "<group>"; }; CD82300625D6A73E00A05501 /* CityConditionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityConditionButton.swift; sourceTree = "<group>"; };
CD82300925D6B2AF00A05501 /* AppTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabBarController.swift; sourceTree = "<group>"; }; CD82300925D6B2AF00A05501 /* AppTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabBarController.swift; sourceTree = "<group>"; };
CDA69B2B2574F3C800CB6409 /* CityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityCell.swift; sourceTree = "<group>"; };
CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoNamesPlace.swift; sourceTree = "<group>"; };
CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsViewModel.swift; sourceTree = "<group>"; };
CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationViewController.swift; sourceTree = "<group>"; };
CDD0F1E42572425200CF5017 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = "<group>"; }; CDD0F1E42572425200CF5017 /* SF-Pro.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Pro.ttf"; sourceTree = "<group>"; };
CDD0F1E72572429E00CF5017 /* AppFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFont.swift; sourceTree = "<group>"; }; CDD0F1E72572429E00CF5017 /* AppFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFont.swift; sourceTree = "<group>"; };
CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; }; CDD0F1ED25725BCF00CF5017 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
...@@ -147,7 +127,6 @@ ...@@ -147,7 +127,6 @@
children = ( children = (
CD1237DA255D5DFA00C98139 /* PG.playground */, CD1237DA255D5DFA00C98139 /* PG.playground */,
CD17C5F925D15B5500EE884E /* Coordinators */, CD17C5F925D15B5500EE884E /* Coordinators */,
CD8091752578D726003541A4 /* Managers */,
CDD0F1DC2572400200CF5017 /* UI */, CDD0F1DC2572400200CF5017 /* UI */,
CD1237EF255D838600C98139 /* Extensions */, CD1237EF255D838600C98139 /* Extensions */,
CDD0F1E2257240DA00CF5017 /* Resources */, CDD0F1E2257240DA00CF5017 /* Resources */,
...@@ -203,7 +182,6 @@ ...@@ -203,7 +182,6 @@
children = ( children = (
CD17C5F425D15B3400EE884E /* Today */, CD17C5F425D15B3400EE884E /* Today */,
CDE18DCF25D166DD00C80ED9 /* Forecast */, CDE18DCF25D166DD00C80ED9 /* Forecast */,
CDD0F1DD2572401000CF5017 /* Location */,
CD82300925D6B2AF00A05501 /* AppTabBarController.swift */, CD82300925D6B2AF00A05501 /* AppTabBarController.swift */,
); );
path = "View controllers"; path = "View controllers";
...@@ -228,14 +206,6 @@ ...@@ -228,14 +206,6 @@
path = Themes; path = Themes;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CD8091752578D726003541A4 /* Managers */ = {
isa = PBXGroup;
children = (
CD8091762578D73F003541A4 /* PopularCitiesManager.swift */,
);
path = Managers;
sourceTree = "<group>";
};
CD822FF325D6814300A05501 /* Cells */ = { CD822FF325D6814300A05501 /* Cells */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -256,23 +226,9 @@ ...@@ -256,23 +226,9 @@
path = CityConditions; path = CityConditions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CDA69B2A2574F3A700CB6409 /* Cells */ = {
isa = PBXGroup;
children = (
CDA69B2B2574F3C800CB6409 /* CityCell.swift */,
);
path = Cells;
sourceTree = "<group>";
};
CDA69B2E2575008700CB6409 /* Models */ = { CDA69B2E2575008700CB6409 /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
870880252578F7030076BFB1 /* WeatherUpdateManager.swift */,
CDA69B2F257500E200CB6409 /* GeoNamesPlace.swift */,
8708801B2578DDD50076BFB1 /* WdtLocation.swift */,
870880202578ED190076BFB1 /* WdtCondition.swift */,
8708801E2578ED190076BFB1 /* WdtDaySummary.swift */,
8708801F2578ED190076BFB1 /* WdtHourSummary.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -289,16 +245,6 @@ ...@@ -289,16 +245,6 @@
path = UI; path = UI;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CDD0F1DD2572401000CF5017 /* Location */ = {
isa = PBXGroup;
children = (
CDA69B2A2574F3A700CB6409 /* Cells */,
CDD0F1DE2572403E00CF5017 /* LocationViewController.swift */,
CDA69B3225750D3400CB6409 /* LocationsViewModel.swift */,
);
path = Location;
sourceTree = "<group>";
};
CDD0F1E2257240DA00CF5017 /* Resources */ = { CDD0F1E2257240DA00CF5017 /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -470,20 +416,14 @@ ...@@ -470,20 +416,14 @@
files = ( files = (
CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */, CD82300325D69DE400A05501 /* CityConditionsCell.swift in Sources */,
CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */, CDD0F1E82572429E00CF5017 /* AppFont.swift in Sources */,
CDD0F1DF2572403E00CF5017 /* LocationViewController.swift in Sources */,
CD17C5FB25D15B6B00EE884E /* AppCoordinator.swift in Sources */, CD17C5FB25D15B6B00EE884E /* AppCoordinator.swift in Sources */,
CD15DB3D25DA6C5100024727 /* ForecastTimePeriodControl.swift in Sources */, CD15DB3D25DA6C5100024727 /* ForecastTimePeriodControl.swift in Sources */,
CDA69B30257500E200CB6409 /* GeoNamesPlace.swift in Sources */,
870880232578ED190076BFB1 /* WdtCondition.swift in Sources */,
CD1237F1255D83C500C98139 /* UIColor+Hex.swift in Sources */, CD1237F1255D83C500C98139 /* UIColor+Hex.swift in Sources */,
870880212578ED190076BFB1 /* WdtDaySummary.swift in Sources */,
CDFDAC6A25D406DA00754945 /* NeoButton.swift in Sources */, CDFDAC6A25D406DA00754945 /* NeoButton.swift in Sources */,
8708801C2578DDD50076BFB1 /* WdtLocation.swift in Sources */,
CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */, CDD0F1EE25725BCF00CF5017 /* ThemeManager.swift in Sources */,
CD1237F4255D889F00C98139 /* GradientView.swift in Sources */, CD1237F4255D889F00C98139 /* GradientView.swift in Sources */,
CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */, CD1237C3255D5C5900C98139 /* AppDelegate.swift in Sources */,
CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */, CD6B304325726AD1004B34B3 /* DefaultTheme.swift in Sources */,
870880262578F7030076BFB1 /* WeatherUpdateManager.swift in Sources */,
CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */, CDE18DD825D16CB200C80ED9 /* NavigationCityButton.swift in Sources */,
CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */, CD17C60225D15C8500EE884E /* CoordinatorProtocol.swift in Sources */,
CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */, CD17C5FF25D15B7C00EE884E /* TodayCoordinator.swift in Sources */,
...@@ -491,13 +431,9 @@ ...@@ -491,13 +431,9 @@
CD6B3036257262C2004B34B3 /* UIColor+Highlight.swift in Sources */, CD6B3036257262C2004B34B3 /* UIColor+Highlight.swift in Sources */,
CDEE8AD725DA882200C289DE /* PeriodForecastButton.swift in Sources */, CDEE8AD725DA882200C289DE /* PeriodForecastButton.swift in Sources */,
CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */, CDE18DD125D166F900C80ED9 /* ForecastViewController.swift in Sources */,
CDA69B3325750D3400CB6409 /* LocationsViewModel.swift in Sources */,
870880222578ED190076BFB1 /* WdtHourSummary.swift in Sources */,
CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */, CD82300A25D6B2AF00A05501 /* AppTabBarController.swift in Sources */,
CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */, CD6B303E25726960004B34B3 /* ThemeProtocol.swift in Sources */,
CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */, CD6B303B2572680C004B34B3 /* SelfSizingButton.swift in Sources */,
CD8091772578D73F003541A4 /* PopularCitiesManager.swift in Sources */,
CDA69B2C2574F3C800CB6409 /* CityCell.swift in Sources */,
CD15DB4225DA806C00024727 /* CityForecastTimePeriodCell.swift in Sources */, CD15DB4225DA806C00024727 /* CityForecastTimePeriodCell.swift in Sources */,
CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */, CDE18DCA25D165F100C80ED9 /* UITabBarController+Append.swift in Sources */,
CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */, CD822FFE25D6976F00A05501 /* TodayAdCell.swift in Sources */,
......
//
// PopularCitiesManager.swift
// 1Weather
//
// Created by Dmitry Stepanets on 03.12.2020.
//
import UIKit
class PopularCitiesManager {
func fetchPopularCities(completion:@escaping (_ result: Result<[GeoNamesPlace], Error>) -> Void) {
let ny = GeoNamesPlace()
ny.city = "New York"
ny.stateCode = "00001"
ny.state = "NY"
ny.country = "USA"
let chicago = GeoNamesPlace()
chicago.city = "Chicago"
chicago.stateCode = "00002"
chicago.state = "IL"
chicago.country = "USA"
let wg = GeoNamesPlace()
wg.city = "Washington DC"
wg.stateCode = "00003"
wg.state = "DC"
wg.country = "USA"
let boston = GeoNamesPlace()
boston.city = "Boston"
boston.stateCode = "00004"
boston.state = "MA"
boston.country = "USA"
completion(.success([ny, chicago, wg, boston]))
}
}
//
// GeoNamesPlace.swift
// OneWeather
//
// Created by Steven G Pint on 8/21/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import Foundation
class GeoNamesPlace: NSObject {
var latitude: String?
var longitude : String?
var city : String?
var state : String?
var stateCode : String?
var country : String?
var countryCode : String?
var fcodeName : String? // airport if airport
var toponymName : String? // airport name if fcodeName is airport
init(location: WdtLocation? = nil) {
guard let location = location else { return }
city = location.city
stateCode = location.region
countryCode = location.country
country = location.countryName
latitude = location.latitude(6)
longitude = location.longitude(6)
}
func detailName() -> String {
var sb = String()
if let state = self.state {
if state.count > 0 {
sb.append(state)
sb.append(", ")
}
}
if let country = self.country {
if country.count > 0 {
sb.append(country)
}
} else if let code = self.countryCode {
if code.count > 0 {
sb.append(code)
}
}
return sb
}
var fullName: String {
get {
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if state?.count ?? 0 > 0 {
sb.append(state!)
sb.append(", ")
}
if country?.count ?? 0 > 0 {
sb.append(country!)
sb.append(", ")
}
if (sb.count > 0) {
sb = sb.trim()
}
return sb
}
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? GeoNamesPlace {
return fullName == object.fullName
} else {
return false
}
}
override var hash: Int {
return fullName.hashValue
}
}
//
// WdtCondition.swift
// OneWeather
//
// Created by Steven G Pint on 8/13/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
class WdtCondition: NSObject, NSCoding {
let TAG = "SfcOb"
var time = ""
var weekDay = ""
var tempC = ""
var tempF = ""
var temp: String {
get {
return isMetric() ? "\(tempC)\(DEGREES)" : "\(tempF)\(DEGREES)"
}
}
var dewC = ""
var dewF = ""
var dew: String {
get {
if dewF.count > 0 {
return isMetric() ? "\(dewC)\(DEGREES)" : "\(dewF)\(DEGREES)"
}
return "NA"
}
}
var humidity = ""
var humidityPercent: String {
get {
if humidity.count > 0 {
return "\(humidity)%"
}
return "NA"
}
}
var apparentTempF = ""
var apparentTempC = ""
var apparentTemp: String {
get {
return isMetric() ? "\(apparentTempC)\(DEGREES)" : "\(apparentTempF)\(DEGREES)"
}
}
var windDir = ""
var windSpeedMph = ""
var windSpeedKph = ""
var windSpeed: String {
get {
if windSpeedMph.count == 0 {
return "NA"
}
return windUnits().windSpeed(windSpeedMph, kph: windSpeedKph)
}
}
var wind: String {
get {
if windSpeed.count == 0 {
return "NA"
}
return "\(windDir) \(windSpeed)"
}
}
var pressureIn = ""
var pressureMb = ""
var pressureTendency = ""
var pressure: String {
get {
if pressureIn.isEmpty {
return "NA"
}
return pressureUnits().pressure(pressureIn, mb: pressureMb, tendency: pressureTendency)
}
}
var pressureAccessibility: String {
get {
if pressureIn.isEmpty {
return "Not Available".localized
}
return pressureUnits().pressure(pressureIn, mb: pressureMb, tendency: pressureTendency, accessibility: true)
}
}
var weatherDesc = ""
var weatherCode = ""
var cloudCoverDesc = ""
var isDay = false // assuming day|night are options
var moonPhase = ""
var moonPhaseDesc: String {
get {
switch moonPhase {
case "New Moon":
return "New\nMoon".localized
case "Waxing Crescent Moon":
return "Waxing\nCrescent".localized
case "Quarter Moon":
return "Quarter".localized
case "Waxing Gibbous Moon":
return "Waxing\nGibbous".localized
case "Full Moon":
return "Full".localized
case "Waning Gibbous Moon":
return "Waning\nGibbous".localized
case "Last Quarter Moon":
return "Last Quarter".localized
case "Waning Crescent Moon":
return "Waning\nCrescent".localized
default:
return "Unknown"
}
}
}
var sunriseTime = ""
var sunriseTimeDesc: String {
get {
let date = Date(dateString: sunriseTime, format: "h:mm a")
return date.getTimePartFull().uppercased()
}
}
var sunsetTime = ""
var sunsetTimeDesc: String {
get {
let date = Date(dateString: sunsetTime, format: "h:mm a")
return date.getTimePartFull().uppercased()
}
}
var precipHourIn = ""
var precipHourMm = ""
var precipHour: String {
get {
if precipHourIn == "0" || precipHourIn.count == 0 {
return ""
}
return isMetric() ? "\(precipHourMm)\(MILLIMETERS)" : "\(precipHourIn)\(INCHES)"
}
}
var precipDayIn = ""
var precipDayMm = ""
var precipDay: String {
get {
if precipDayIn == "0" || precipDayIn.count == 0 {
return ""
}
return isMetric() ? "\(precipDayMm)\(MILLIMETERS)" : "\(precipDayIn)\(INCHES)"
}
}
var visibilityFt = ""
var visibility: String {
get {
if visibilityFt.count > 0 && visibilityFt.lowercased() != "nan" {
let num: Double = distanceUnits() == DistanceUnits.kilometers ? 1000 : 5280
let desc = distanceUnits() == DistanceUnits.kilometers ? "KM" : "MI"
if let dvalue = Double(visibilityFt) {
let str = String(format:"%.0f", (dvalue / num).roundTo(0))
return "\(str) \(desc)"
}
}
return "NA"
}
}
override init() {
super.init()
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
time = decoder.decodeObject(forKey: "time") as! String
weekDay = decoder.decodeObject(forKey: "weekDay") as! String
tempC = decoder.decodeObject(forKey: "tempC") as! String
tempF = decoder.decodeObject(forKey: "tempF") as! String
dewC = decoder.decodeObject(forKey: "dewC") as! String
dewF = decoder.decodeObject(forKey: "dewF") as! String
humidity = decoder.decodeObject(forKey: "humidity") as! String
apparentTempF = decoder.decodeObject(forKey: "apparentTempF") as! String
apparentTempC = decoder.decodeObject(forKey: "apparentTempC") as! String
windDir = decoder.decodeObject(forKey: "windDir") as! String
windSpeedMph = decoder.decodeObject(forKey: "windSpeedMph") as! String
windSpeedKph = decoder.decodeObject(forKey: "windSpeedKph") as! String
pressureIn = decoder.decodeObject(forKey: "pressureIn") as! String
pressureMb = decoder.decodeObject(forKey: "pressureMb") as! String
pressureTendency = decoder.decodeObject(forKey: "pressureTendency") as! String
weatherDesc = decoder.decodeObject(forKey: "weatherDesc") as! String
weatherCode = decoder.decodeObject(forKey: "weatherCode") as! String
cloudCoverDesc = decoder.decodeObject(forKey: "cloudCoverDesc") as! String
isDay = decoder.decodeBool(forKey: "isDay")
moonPhase = decoder.decodeObject(forKey: "moonPhase") as! String
sunriseTime = decoder.decodeObject(forKey: "sunriseTime") as! String
sunsetTime = decoder.decodeObject(forKey: "sunsetTime") as! String
precipHourIn = decoder.decodeObject(forKey: "precipHourIn") as! String
precipHourMm = decoder.decodeObject(forKey: "precipHourMm") as! String
precipDayIn = decoder.decodeObject(forKey: "precipDayIn") as! String
precipDayMm = decoder.decodeObject(forKey: "precipDayMm") as! String
visibilityFt = decoder.decodeObject(forKey: "visibilityFt") as! String
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.time, forKey: "time")
coder.encode(self.weekDay, forKey: "weekDay")
coder.encode(self.tempC, forKey: "tempC")
coder.encode(self.tempF, forKey: "tempF")
coder.encode(self.dewC, forKey: "dewC")
coder.encode(self.dewF, forKey: "dewF")
coder.encode(self.humidity, forKey: "humidity")
coder.encode(self.apparentTempF, forKey: "apparentTempF")
coder.encode(self.apparentTempC, forKey: "apparentTempC")
coder.encode(self.windDir, forKey: "windDir")
coder.encode(self.windSpeedMph, forKey: "windSpeedMph")
coder.encode(self.windSpeedKph, forKey: "windSpeedKph")
coder.encode(self.pressureIn, forKey: "pressureIn")
coder.encode(self.pressureMb, forKey: "pressureMb")
coder.encode(self.pressureTendency, forKey: "pressureTendency")
coder.encode(self.weatherDesc, forKey: "weatherDesc")
coder.encode(self.weatherCode, forKey: "weatherCode")
coder.encode(self.cloudCoverDesc, forKey: "cloudCoverDesc")
coder.encode(self.isDay, forKey: "isDay")
coder.encode(self.moonPhase, forKey: "moonPhase")
coder.encode(self.sunriseTime, forKey: "sunriseTime")
coder.encode(self.sunsetTime, forKey: "sunsetTime")
coder.encode(self.precipHourIn, forKey: "precipHourIn")
coder.encode(self.precipHourMm, forKey: "precipHourMm")
coder.encode(self.precipDayIn, forKey: "precipDayIn")
coder.encode(self.precipDayMm, forKey: "precipDayMm")
coder.encode(self.visibilityFt, forKey: "visibilityFt")
}
}
//
// WdtDaySummary.swift
// OneWeather
//
// Created by Steven G Pint on 8/12/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
class WdtDaySummary: NSObject, NSCoding {
var dayOfWeek = ""
var dayOfWeekShort: String {
get {
if self.dayOfWeek.count >= 3 {
return String(dayOfWeek[dayOfWeek.startIndex ..< dayOfWeek.index(dayOfWeek.startIndex, offsetBy: 3)])
} else {
return "NA"
}
}
}
var maxTempF = ""
var maxTempC = ""
var maxTemp: String {
get {
return isMetric() ? "\(maxTempC)\(DEGREES)" : "\(maxTempF)\(DEGREES)"
}
}
var maxTempNum: Double {
get {
if let val = isMetric() ? Double(maxTempC) : Double(maxTempF) {
return val
}
return 0.0
}
}
var isMaxFreezing: Bool {
get {
if isMetric() {
if let f = Int(maxTempC) {
return f <= 0
}
} else {
if let f = Int(maxTempF) {
return f <= 32
}
}
return false
}
}
var minTempF = ""
var minTempC = ""
var minTemp: String {
get {
return isMetric() ? "\(minTempC)\(DEGREES)" : "\(minTempF)\(DEGREES)"
}
}
var minTempNum: Double {
get {
if let val = isMetric() ? Double(minTempC) : Double(minTempF) {
return val
}
return 0.0
}
}
var tempRange: String {
get {
return "\(maxTemp) / \(minTemp)"
}
}
var tempRangeAccessibility: String {
get {
return "High \(maxTemp), Low \(minTemp)"
}
}
var windSpeedMph = ""
var windSpeedKph = ""
var windSpeed: String {
get {
return windUnits().windSpeed(windSpeedMph, kph: windSpeedKph)
}
}
var windDir = ""
var wind: String {
get {
return "\(windDir) \(windSpeed)"
}
}
var precip = ""
var precipPercent: String {
get {
if precip == "0" {
return ""
}
return "\(precip)%"
}
}
var precipPercentNotEmpty: String {
get {
return "\(precip)%"
}
}
var precipDouble: Double? {
get {
return Double(precip)
}
}
var weatherDesc = ""
var weatherCode = ""
var textDescription = ""
var sunriseTime = ""
var sunsetTime = ""
var summaryDate = ""
var monthDayPart: String {
get {
if self.summaryDate.count > 0 {
return Date(dateString: summaryDate, format: "MM/dd/yyyy").getDayPart().uppercased()
} else {
return ""
}
}
}
var dayPart: String {
get {
if self.summaryDate.count > 0 {
return Date(dateString: summaryDate, format: "MM/dd/yyyy").getWithFormat("dd")
} else {
return ""
}
}
}
var dayDatePart: String {
get {
if self.summaryDate.count > 0 {
return Date(dateString: summaryDate, format: "MM/dd/yyyy").getWithFormat("eeee, MMMM dd").uppercased()
} else {
return ""
}
}
}
var windGustMph = ""
var windGustKph = ""
var windGust: String {
get {
return windUnits().windSpeed(windGustMph, kph: windGustKph)
}
}
var moonPhase = ""
var moonPhaseDesc: String {
get {
switch moonPhase {
case "New Moon":
return "New\nMoon".localized
case "Waxing Crescent Moon":
return "Waxing\nCrescent".localized
case "Quarter Moon":
return "Quarter".localized
case "Waxing Gibbous Moon":
return "Waxing\nGibbous".localized
case "Full Moon":
return "Full".localized
case "Waning Gibbous Moon":
return "Waning\nGibbous".localized
case "Last Quarter Moon":
return "Last Quarter".localized
case "Waning Crescent Moon":
return "Waning\nCrescent".localized
default:
return "Unknown"
}
}
}
var morningWeather = ""
var morningPop = ""
var morningPrecipPercent: String {
get {
return "\(morningPop)%"
}
}
var morningTempF = ""
var morningTempC = ""
var morningTemp: String {
get {
return isMetric() ? "\(morningTempC)\(DEGREES)" : "\(morningTempF)\(DEGREES)"
}
}
var nightWeather = ""
var nightPop = ""
var nightPrecipPercent: String {
get {
return "\(nightPop)%"
}
}
var nightTempF = ""
var nightTempC = ""
var nightTemp: String {
get {
return isMetric() ? "\(nightTempC)\(DEGREES)" : "\(nightTempF)\(DEGREES)"
}
}
// added in version 2
var sunriseUtc: Date?
var sunsetUtc: Date?
var sunState: String?
var sunStateDesc: String {
get {
switch sunState ?? "" {
case "always_up":
return "Sun is always up".localized
case "never_up":
return "Sun is never up".localized
default:
return ""
}
}
}
var moonriseUtc: Date?
var moonsetUtc: Date?
var moonState: String?
var moonStateDesc: String {
get {
switch moonState ?? "" {
case "always_up":
return "Moon is always up".localized
case "never_up":
return "Moon is never up".localized
default:
return ""
}
}
}
var date : Date? // not persisted
override init() {
super.init()
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
let version = decoder.decodeCInt(forKey: "version")
dayOfWeek = decoder.decodeObject(forKey: "dayOfWeek") as! String
maxTempF = decoder.decodeObject(forKey: "maxTempF") as! String
maxTempC = decoder.decodeObject(forKey: "maxTempC") as! String
minTempF = decoder.decodeObject(forKey: "minTempF") as! String
minTempC = decoder.decodeObject(forKey: "minTempC") as! String
windSpeedMph = decoder.decodeObject(forKey: "windSpeedMph") as! String
windSpeedKph = decoder.decodeObject(forKey: "windSpeedKph") as! String
windDir = decoder.decodeObject(forKey: "windDir") as! String
precip = decoder.decodeObject(forKey: "precip") as! String
weatherDesc = decoder.decodeObject(forKey: "weatherDesc") as! String
weatherCode = decoder.decodeObject(forKey: "weatherCode") as! String
textDescription = decoder.decodeObject(forKey: "textDescription") as! String
sunriseTime = decoder.decodeObject(forKey: "sunriseTime") as! String
sunsetTime = decoder.decodeObject(forKey: "sunsetTime") as! String
summaryDate = decoder.decodeObject(forKey: "summaryDate") as! String
windGustMph = decoder.decodeObject(forKey: "windGustMph") as! String
windGustKph = decoder.decodeObject(forKey: "windGustKph") as! String
moonPhase = decoder.decodeObject(forKey: "moonPhase") as! String
morningWeather = decoder.decodeObject(forKey: "morningWeather") as! String
morningPop = decoder.decodeObject(forKey: "morningPop") as! String
morningTempF = decoder.decodeObject(forKey: "morningTempF") as! String
morningTempC = decoder.decodeObject(forKey: "morningTempC") as! String
nightWeather = decoder.decodeObject(forKey: "nightWeather") as! String
nightPop = decoder.decodeObject(forKey: "nightPop") as! String
morningPop = decoder.decodeObject(forKey: "morningPop") as! String
nightTempF = decoder.decodeObject(forKey: "nightTempF") as! String
nightTempC = decoder.decodeObject(forKey: "nightTempC") as! String
if version >= 2 {
sunriseUtc = decoder.decodeObject(forKey: "sunriseUtc") as? Date
sunsetUtc = decoder.decodeObject(forKey: "sunsetUtc") as? Date
sunState = decoder.decodeObject(forKey: "sunState") as? String
moonriseUtc = decoder.decodeObject(forKey: "moonriseUtc") as? Date
moonsetUtc = decoder.decodeObject(forKey: "moonsetUtc") as? Date
moonState = decoder.decodeObject(forKey: "moonState") as? String
}
}
func encode(with coder: NSCoder) {
coder.encodeCInt(2, forKey: "version") // changed to version 2 for moonrise values
coder.encode(self.dayOfWeek, forKey: "dayOfWeek")
coder.encode(self.maxTempF, forKey: "maxTempF")
coder.encode(self.maxTempC, forKey: "maxTempC")
coder.encode(self.minTempF, forKey: "minTempF")
coder.encode(self.minTempC, forKey: "minTempC")
coder.encode(self.windSpeedMph, forKey: "windSpeedMph")
coder.encode(self.windSpeedKph, forKey: "windSpeedKph")
coder.encode(self.windDir, forKey: "windDir")
coder.encode(self.precip, forKey: "precip")
coder.encode(self.weatherDesc, forKey: "weatherDesc")
coder.encode(self.weatherCode, forKey: "weatherCode")
coder.encode(self.textDescription, forKey: "textDescription")
coder.encode(self.sunriseTime, forKey: "sunriseTime")
coder.encode(self.sunsetTime, forKey: "sunsetTime")
coder.encode(self.summaryDate, forKey: "summaryDate")
coder.encode(self.windGustMph, forKey: "windGustMph")
coder.encode(self.windGustKph, forKey: "windGustKph")
coder.encode(self.moonPhase, forKey: "moonPhase")
coder.encode(self.morningWeather, forKey: "morningWeather")
coder.encode(self.morningPop, forKey: "morningPop")
coder.encode(self.morningTempF, forKey: "morningTempF")
coder.encode(self.morningTempC, forKey: "morningTempC")
coder.encode(self.nightWeather, forKey: "nightWeather")
coder.encode(self.nightPop, forKey: "nightPop")
coder.encode(self.nightTempF, forKey: "nightTempF")
coder.encode(self.nightTempC, forKey: "nightTempC")
coder.encode(sunriseUtc, forKey: "sunriseUtc")
coder.encode(sunsetUtc, forKey: "sunsetUtc")
coder.encode(sunState, forKey: "sunState")
coder.encode(moonriseUtc, forKey: "moonriseUtc")
coder.encode(moonsetUtc, forKey: "moonsetUtc")
coder.encode(moonState, forKey: "moonState")
}
}
//
// WdtHourSummary.swift
// OneWeather
//
// Created by Steven G Pint on 8/13/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
enum DayPhase: Int {
case morn, noon, eve, night
var description: String {
switch self {
case .morn:
return "MORN".localized
case .noon:
return "NOON".localized
case .eve:
return "EVE".localized
case .night:
return "NIGHT".localized
}
}
var accessibilityDescription: String {
switch self {
case .morn:
return "Morning"
case .noon:
return "Noon"
case .eve:
return "Evening"
case .night:
return "Night"
}
}
static let daySegments = [7, 12, 18, 23]
}
class WdtHourSummary: NSObject, NSCoding {
let TAG = "WdtHourSummary"
var day = ""
var time = ""
// var time = "" {
// didSet(value) {
// if value.length > 0 {
// _date = NSDate(dateString: value)
// time = value
// }
// }
// }
fileprivate var _date: Date?
var date: Date {
get {
if _date == nil {
_date = Date(dateString: self.time)
}
return _date!
}
}
var hour: Int {
get {
return Calendar.current.component(.hour, from: date)
}
}
var dayPhase: DayPhase = .morn
var dayPart: String {
get {
if self.time.count > 0 {
return date.getDayPart().uppercased()
} else {
return "NA"
}
}
}
var timePart: String {
get {
if self.time.count > 0 {
return date.getTimePart()
} else {
return "NA"
}
}
}
var tempC = ""
var tempF = ""
var temp: String {
get {
return isMetric() ? "\(tempC)\(DEGREES)" : "\(tempF)\(DEGREES)"
}
}
var tempNum: Double {
get {
return (isMetric() ? Double(tempC) : Double(tempF))!
}
}
var isFreezing: Bool {
get {
if isMetric() {
if let f = Int(tempC) {
return f <= 0
}
} else {
if let f = Int(tempF) {
return f <= 32
}
}
return false
}
}
var dewPointC = ""
var dewPointF = ""
var apparentTempC = ""
var apparentTempF = ""
var apparentTemp: String {
get {
return isMetric() ? apparentTempC : apparentTempF
}
}
var tempFeels: String {
get {
return "\(temp) Feels \(apparentTemp)" // degree symbol
}
}
var humidityPercent = ""
var pressureIn = ""
var pressureMb = ""
var weatherDesc = ""
var weatherCode = ""
var precip = ""
var precipPercent: String {
get {
return "\(precip)%"
}
}
var precipDouble: Double? {
get {
return Double(precip)
}
}
var windDir = ""
var windSpeedKph = ""
var windSpeedMph = ""
var windSpeed: String {
get {
return windUnits().windSpeed(windSpeedMph, kph: windSpeedKph)
}
}
var wind: String {
get {
return "\(windDir) \(windSpeed)"
}
}
override init() {
super.init()
}
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
day = decoder.decodeObject(forKey: "day") as! String
time = decoder.decodeObject(forKey: "time") as! String
tempC = decoder.decodeObject(forKey: "tempC") as! String
tempF = decoder.decodeObject(forKey: "tempF") as! String
dewPointC = decoder.decodeObject(forKey: "dewPointC") as! String
dewPointF = decoder.decodeObject(forKey: "dewPointF") as! String
apparentTempC = decoder.decodeObject(forKey: "apparentTempC") as! String
apparentTempF = decoder.decodeObject(forKey: "apparentTempF") as! String
humidityPercent = decoder.decodeObject(forKey: "humidityPercent") as! String
pressureIn = decoder.decodeObject(forKey: "pressureIn") as! String
pressureMb = decoder.decodeObject(forKey: "pressureMb") as! String
weatherDesc = decoder.decodeObject(forKey: "weatherDesc") as! String
weatherCode = decoder.decodeObject(forKey: "weatherCode") as! String
precip = decoder.decodeObject(forKey: "precip") as! String
windDir = decoder.decodeObject(forKey: "windDir") as! String
windSpeedKph = decoder.decodeObject(forKey: "windSpeedKph") as! String
windSpeedMph = decoder.decodeObject(forKey: "windSpeedMph") as! String
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.day, forKey: "day")
coder.encode(self.time, forKey: "time")
coder.encode(self.tempC, forKey: "tempC")
coder.encode(self.tempF, forKey: "tempF")
coder.encode(self.dewPointC, forKey: "dewPointC")
coder.encode(self.dewPointF, forKey: "dewPointF")
coder.encode(self.apparentTempC, forKey: "apparentTempC")
coder.encode(self.apparentTempF, forKey: "apparentTempF")
coder.encode(self.humidityPercent, forKey: "humidityPercent")
coder.encode(self.pressureIn, forKey: "pressureIn")
coder.encode(self.pressureMb, forKey: "pressureMb")
coder.encode(self.weatherDesc, forKey: "weatherDesc")
coder.encode(self.weatherCode, forKey: "weatherCode")
coder.encode(self.precip, forKey: "precip")
coder.encode(self.windDir, forKey: "windDir")
coder.encode(self.windSpeedKph, forKey: "windSpeedKph")
coder.encode(self.windSpeedMph, forKey: "windSpeedMph")
}
var isDay: Bool {
get {
if (hour > 6 && hour < 19) {
return true
}
return false
}
}
}
//
// WdtLocation.swift
// OneWeather
//
// Created by Steven G Pint on 8/5/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import UIKit
class WdtLocation: NSObject, NSCoding {
static let LOCATION = "location"
static let CONDITIONS = "conditions"
static let DAYSUMMARIES = "daysummaries"
static let HOURSUMMARIES = "hoursummaries"
static let LONGRANGEFORECASTS = "longrangeforecasts"
static let NWSALERTS = "nwsalerts"
static let NWSDISCUSSIONS = "nwsdiscussions"
let DEFAULT_LOCATION = "default_location"
let STALE_THRESHOLD: TimeInterval = 15 * 60 // 15 minutes
let ALERT_STALE_THRESHOLD: TimeInterval = 60 // 60 seconds
let MY_LOCATION_FILE_NAME = "_default"
var city: String?
var region: String? // aka state
var country: String? // actually countryCode (i.e US)
var countryName: String? // actually country (i.e United States)
var zip: String?
fileprivate var _nickName: String?
var nickName: String?
var fullName: String {
get {
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if region?.count ?? 0 > 0 {
sb.append(region!)
sb.append(", ")
}
if country?.count ?? 0 > 0 {
sb.append(country!)
sb.append(", ")
}
if (sb.count > 0) {
sb = sb.trim(", ") // trim comma and space
}
return sb
}
}
var cityName: String {
get {
if let nickName = self.nickName, nickName.count > 0 {
return nickName
}
return city ?? ""
}
}
var cityStateName: String {
get {
if let nickName = self.nickName, nickName.count > 0 {
return nickName
}
var sb = String()
if city?.count ?? 0 > 0 {
sb.append(city!)
sb.append(", ")
}
if region?.count ?? 0 > 0 {
sb.append(region!)
sb.append(", ")
} else if country?.count ?? 0 > 0 {
if !(myLocation && isUSA) {
sb.append(country!)
sb.append(", ")
}
}
sb = sb.trim(", ")
return sb
}
}
var cityState: String
var myLocation = false
var selectedLocation = false
var geoPointLat: Double = -1
var geoPointLong: Double = -1
var lastUpdateTime: TimeInterval = 0
var lastPartialUpdateTime: TimeInterval = 0 // background updates will no longer update all data
var lastUpdateAttemptedTime: TimeInterval = 0
var lastNwsDiscussionUpdateTime: TimeInterval = 0
// Not cached, used to make sure we don't check for new alerts too often
var lastAlertUpdateTime: TimeInterval = 0
fileprivate var _currentConditions: WdtCondition?
var currentConditions: WdtCondition?
var uvIndex: String?
var uvDesc: String
var _timezone: String?
var timezone: String?
var _timezoneOffset: Double?
var timezoneOffset: String?
// NWS ALERTS
// NWS region code
var fips: String?
// private ArrayList<NwsAfdSection> afdSections
func latitude(_ maxFraction: Int = 3) -> String {
let p = pow(10.0, Double(maxFraction))
let y = Double(round(p * geoPointLat) / p)
return String(y)
}
func longitude(_ maxFraction: Int = 3) -> String {
let p = pow(10.0, Double(maxFraction))
let y = Double(round(p * geoPointLong) / p)
return String(y)
}
// add a single alert - e.g. from a push event
// base weather class data is saved in Documents directory so it doesn't get deleted by the system
/*
* for location that should follow you
*/
override init() {
cityState = ""
uvDesc = ""
detailName = ""
locationId = "us"
fileName = "us"
simpleFileName = "us"
isUSA = true
super.init()
myLocation = true
}
init(zip: String?, city: String?, region: String?, countryCode: String?, country: String?, lat: String?, lng: String?) {
cityState = ""
uvDesc = ""
detailName = ""
locationId = "us"
fileName = "us"
simpleFileName = "us"
isUSA = true
super.init()
self.zip = zip
self.city = city
self.region = region
self.countryName = country
if let co = countryCode {
switch co.count {
case 3 where countryCode == "USA":
self.country = "US"
default:
self.country = co
}
}
if let lat1 = lat {
setLatitude(lat1)
}
if let lng1 = lng {
setLongitude(lng1)
}
}
convenience init(place: GeoNamesPlace) {
self.init(zip: nil, city: place.city, region: place.stateCode, countryCode: place.countryCode, country: place.country, lat: place.latitude, lng: place.longitude)
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
// let version = decoder.decodeIntForKey("version")
cityState = ""
uvDesc = ""
detailName = ""
locationId = ""
fileName = ""
simpleFileName = ""
isUSA = true
self.city = decoder.decodeObject(forKey: "city") as? String
self._nickName = decoder.decodeObject(forKey: "nickName") as? String
self.country = decoder.decodeObject(forKey: "country") as? String
self.countryName = decoder.decodeObject(forKey: "countryName") as? String
self.region = decoder.decodeObject(forKey: "region") as? String
self.geoPointLat = decoder.decodeDouble(forKey: "geoPointLat")
self.geoPointLong = decoder.decodeDouble(forKey: "geoPointLong")
self.lastUpdateTime = decoder.decodeDouble(forKey: "lastUpdateTime")
self.lastPartialUpdateTime = decoder.decodeDouble(forKey: "lastPartialUpdateTime")
self.lastUpdateAttemptedTime = decoder.decodeDouble(forKey: "lastUpdateAttemptedTime")
self.uvIndex = decoder.decodeObject(forKey: "uvIndex") as? String
self.myLocation = decoder.decodeBool(forKey: "myLocation")
self.selectedLocation = decoder.decodeBool(forKey: "selectedLocation")
self._timezone = decoder.decodeObject(forKey: "timezone") as? String
self._timezoneOffset = decoder.decodeDouble(forKey: "timezoneOffset")
self.fips = decoder.decodeObject(forKey: "fips") as? String
self.lastNwsDiscussionUpdateTime = decoder.decodeDouble(forKey: "lastNwsDiscussionUpdateTime")
//print("************** decode city: \(city) nickName: \(self._nickName)")
}
func encode(with coder: NSCoder) {
coder.encodeCInt(1, forKey: "version")
coder.encode(self.city, forKey: "city")
coder.encode(self._nickName, forKey: "nickName")
coder.encode(self.country, forKey: "country")
coder.encode(self.countryName, forKey: "countryName")
coder.encode(self.region, forKey: "region")
coder.encode(self.geoPointLat, forKey: "geoPointLat")
coder.encode(self.geoPointLong, forKey: "geoPointLong")
coder.encode(self.lastUpdateTime, forKey: "lastUpdateTime")
coder.encode(self.lastPartialUpdateTime, forKey: "lastPartialUpdateTime")
coder.encode(self.lastUpdateAttemptedTime, forKey: "lastUpdateAttemptedTime")
coder.encode(self.uvIndex, forKey: "uvIndex")
coder.encode(self.myLocation, forKey: "myLocation")
coder.encode(self.selectedLocation, forKey: "selectedLocation")
coder.encode(self._timezone, forKey: "timezone")
if self._timezoneOffset != nil {
coder.encode(self._timezoneOffset!, forKey: "timezoneOffset")
}
coder.encode(self.fips, forKey: "fips")
coder.encode(self.lastNwsDiscussionUpdateTime, forKey: "lastNwsDiscussionUpdateTime")
//print("*************** encode city: \(city) nickName: \(self._nickName)")
}
// Since multiple existing icon condition lookups use WDT condition codes, easiest to
// translate Weather2020 codes -> WDT
var detailName: String
var isDay: Bool = false
// Calculates totale minutes of the day based on dayTimeString (i.e 07:40 am, 06:10 pm
var locationId: String
var fileName: String
var simpleFileName: String
// remove non letter/digits
func setLatitude(_ latitude: String?) {
if let l = latitude, !l.isEmpty, let value = Double(l) {
geoPointLat = value
}
}
func setLongitude(_ longitude: String?) {
if let l = longitude, !l.isEmpty, let value = Double(l) {
geoPointLong = value
}
}
override var hash: Int {
if myLocation {
return "\(geoPointLat.roundTo(2)),\(geoPointLong.roundTo(2))".hash
} else {
return fullName.hash
}
}
var isUSA: Bool
}
//
// WeatherUpdateManager.swift
// OneWeather
//
// Created by Steven G Pint on 9/28/15.
// Copyright © 2015 OneLouder, Inc. All rights reserved.
//
import Foundation
import UserNotifications
class WeatherUpdateManager: NSObject {
static let shared = WeatherUpdateManager()
private override init() {
super.init()
let sf = WdtLocation(zip: "000001",
city: "San-Francisco",
region: "CA",
countryCode: "US",
country: "USA",
lat: "0",
lng: "0")
sf.currentConditions = WdtCondition()
sf.currentConditions?.tempC = "30"
sf.selectedLocation = true
let seattle = WdtLocation(zip: "000002",
city: "Seattle",
region: "WA",
countryCode: "US",
country: "USA",
lat: "0",
lng: "0")
seattle.currentConditions = WdtCondition()
seattle.currentConditions?.tempC = "17"
seattle.selectedLocation = false
self.allWdtLocations = [sf, seattle]
}
var allLocationsCount: Int {
return allWdtLocations?.count ?? 0
}
// set private var so we don't have to access disk all the time
private(set) var allWdtLocations: [WdtLocation]?
// Adds the location if it's not a duplicate, makes flurry event, registered for PP notifications, sets this as selected location
// Return true if it added as a new location, false if it didn't add it or it was a dup
func addLocation(_ location: WdtLocation) -> Bool {
print("Add location \(location)")
allWdtLocations?.forEach{$0.selectedLocation = false}
if let index = (allWdtLocations?.firstIndex{$0.hash == location.hash}) {
allWdtLocations?[index].selectedLocation = true
}
else {
location.selectedLocation = true
allWdtLocations?.append(location)
}
return true
}
// Deletes the location if found, register/deregister for PP notifications, change selected location to first item if it's the same as deleted location
// Return true if it added as a new location, false if it didn't add it or it was a dup
func deleteLocation(_ location: WdtLocation) -> Bool {
print("Delete location \(location)")
if let locationToDeleteIndex = (allWdtLocations?.firstIndex{location.hash == $0.hash}) {
allWdtLocations?.remove(at: locationToDeleteIndex)
if location.selectedLocation {
allWdtLocations?.first?.selectedLocation = true
}
}
return true
}
var defaultWdtLocation: WdtLocation? {
get {
if let locs = allWdtLocations, locs.first?.myLocation ?? false {
return locs.first
}
return nil
}
}
var selectedWdtLocation: WdtLocation?
}
//
// CityCell.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
class CityCell: UITableViewCell {
//Public
static let kIdentifier = "cityCell"
var onSelect:(() -> Void)?
var onAdd:(() -> Void)?
//Private
private let cityLabel = UILabel()
private let selectedButton = UIButton()
private let addButton = UIButton()
private let temperatureLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
prepareCell()
prepareCityLabel()
prepareAddButton()
prepareSelectButton()
prepareTemperatureLabel()
}
func configure(wdtLocation:WdtLocation, mode:LocationsViewModelDisplayMode) {
cityLabel.text = wdtLocation.cityStateName
temperatureLabel.text = wdtLocation.currentConditions?.temp ?? "--"
switch mode {
case .savedCities:
addButton.isHidden = true
selectedButton.isHidden = false
UIView.performWithoutAnimation {
self.selectedButton.tintColor = wdtLocation.selectedLocation ? ThemeManager.Colors.citySelected : ThemeManager.Colors.cityNoSelected
}
case .popularCities, .searchResults:
addButton.isHidden = false
selectedButton.isHidden = true
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleAddButton() {
onAdd?()
}
@objc private func handleSelectButton() {
onSelect?()
}
}
//MARK: Prepare
private extension CityCell {
func prepareCell() {
backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
contentView.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
selectionStyle = .none
}
func prepareCityLabel() {
cityLabel.font = AppFont.SFPro.regular(size: 14)
cityLabel.numberOfLines = 1
cityLabel.lineBreakMode = .byTruncatingTail
cityLabel.textColor = ThemeManager.currentTheme.primaryTextColor
cityLabel.setContentHuggingPriority(.init(900), for: .horizontal)
contentView.addSubview(cityLabel)
cityLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareTemperatureLabel() {
let container = UIView()
container.backgroundColor = ThemeManager.Colors.temperatureLabelBG
container.clipsToBounds = true
container.layer.cornerRadius = 5
container.setContentHuggingPriority(.init(1000), for: .horizontal)
temperatureLabel.text = "27°"
temperatureLabel.font = AppFont.SFPro.semibold(size: 16)
temperatureLabel.textColor = ThemeManager.currentTheme.primaryTextColor
temperatureLabel.setContentHuggingPriority(.init(1000), for: .horizontal)
temperatureLabel.setContentCompressionResistancePriority(.init(1000), for: .horizontal)
container.addSubview(temperatureLabel)
temperatureLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(6)
make.right.equalToSuperview().inset(6)
make.top.equalToSuperview().inset(4)
make.bottom.equalToSuperview().inset(4)
}
contentView.addSubview(container)
container.snp.makeConstraints { (make) in
make.left.equalTo(cityLabel.snp.right).offset(14)
make.centerY.equalTo(cityLabel)
make.right.lessThanOrEqualTo(addButton.snp.left).offset(-14)
}
}
func prepareAddButton() {
addButton.isHidden = false
addButton.setTitle("+ ADD", for: .normal)
addButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
addButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
addButton.backgroundColor = ThemeManager.Colors.cityAddButtonBG
addButton.layer.cornerRadius = 12
addButton.addTarget(self, action: #selector(handleAddButton), for: .touchUpInside)
addButton.layer.shadowOffset = .init(width: 0, height: 2)
addButton.layer.shadowColor = UIColor.black.cgColor
addButton.layer.shadowRadius = 2
addButton.layer.shadowOpacity = 0.5
addButton.layer.shadowPath = UIBezierPath(roundedRect: .init(origin: .zero, size: .init(width: 55, height: 24)),
cornerRadius: 12).cgPath
contentView.addSubview(addButton)
addButton.snp.makeConstraints { (make) in
make.size.equalTo(CGSize(width: 55, height: 24))
make.right.equalToSuperview().inset(24)
make.centerY.equalToSuperview()
}
}
func prepareSelectButton() {
selectedButton.isHidden = true
selectedButton.imageView?.contentMode = .scaleAspectFit
selectedButton.setImage(UIImage(named: "city_checkmark"), for: .normal)
selectedButton.tintColor = ThemeManager.Colors.cityNoSelected
selectedButton.addTarget(self, action: #selector(handleAddButton), for: .touchUpInside)
contentView.addSubview(selectedButton)
selectedButton.snp.makeConstraints { (make) in
make.right.equalToSuperview().inset(24)
make.size.equalTo(CGSize(width: 24, height: 24))
make.centerY.equalToSuperview()
}
}
}
//
// LocationViewController.swift
// 1Weather
//
// Created by Dmitry Stepanets on 28.11.2020.
//
import UIKit
//MARK:- Location Navigation View Controller
class LocationViewController: UINavigationController {
init(closeButtonIsHidden:Bool = false) {
let savedCitiesViewController = CitiesViewController(closeButtonHidden: closeButtonIsHidden)
super.init(rootViewController: savedCitiesViewController)
self.modalPresentationStyle = .fullScreen
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK:- Cities View Controller
private enum EditButtonStyle {
case edit
case done
}
private class CitiesViewController:UIViewController {
//Private
private let searchBar = UISearchBar()
private let locationButton = SelfSizingButton(frame: .zero)
private let tableView = UITableView()
private let editButton = SelfSizingButton()
private let locationsViewModel = LocationsViewModel()
private let searchQueue:OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
init(closeButtonHidden:Bool = false) {
super.init(nibName: nil, bundle: nil)
prepareNavBar(closeButtonIsHidden: closeButtonHidden)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
edgesForExtendedLayout = []
self.locationsViewModel.delegate = self
prepareSearchBar()
prepareLocationButton()
prepareTableView()
prepareEditButton()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//Update navigation title view constraints
guard let containerView = self.navigationItem.titleView?.superview else { return }
self.navigationItem.titleView?.snp.remakeConstraints({ (make) in
make.left.equalTo(containerView).inset(24).priority(.medium)
make.top.equalTo(containerView).priority(.medium)
make.right.equalTo(containerView).priority(.medium)
make.bottom.equalTo(containerView).priority(.medium)
})
}
private func updateEditButton(style:EditButtonStyle) {
switch style {
case .edit:
UIView.performWithoutAnimation {
self.editButton.setImage(UIImage(named: "edit_pencil"), for: .normal)
self.editButton.setTitle("EDIT".localized, for: .normal)
}
case .done:
UIView.performWithoutAnimation {
self.editButton.setImage(nil, for: .normal)
self.editButton.setTitle("DONE".localized, for: .normal)
}
}
}
@objc private func handleCloseButton() {
self.navigationController?.dismiss(animated: true)
}
@objc private func handleLocationButton() {
}
@objc private func handleEditButton(button:UIButton) {
tableView.setEditing(!tableView.isEditing, animated: true)
self.updateEditButton(style: tableView.isEditing ? .done : .edit)
}
}
//MARK:- Saved cities prepare
private extension CitiesViewController {
func prepareNavBar(closeButtonIsHidden:Bool) {
self.navigationController?.view.backgroundColor = .white
//Title
let titleLabel = UILabel()
titleLabel.textAlignment = .left
titleLabel.font = AppFont.SFPro.bold(size: 24)
titleLabel.text = "Select Location"
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.textColor = ThemeManager.currentTheme.navigationTintColor
navigationItem.titleView = titleLabel
if !closeButtonIsHidden {
//Close button
let closeButton = UIBarButtonItem(image: UIImage(named: "close_cross"), style: .plain, target: self, action: #selector(handleCloseButton))
closeButton.tintColor = ThemeManager.currentTheme.navigationTintColor
self.navigationItem.rightBarButtonItems = [closeButton]
}
}
func prepareSearchBar() {
let searchBarTextField = searchBar.value(forKey: "searchField") as? UITextField
searchBarTextField?.textColor = ThemeManager.currentTheme.navigationTintColor
searchBarTextField?.leftView?.tintColor = ThemeManager.Colors.searchBarTint
searchBar.searchBarStyle = .minimal
searchBar.placeholder = "Search".localized(comment: "Search bar placeholder text.")
searchBar.delegate = self
view.addSubview(searchBar)
searchBar.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.left.equalToSuperview().inset(16)
make.right.equalToSuperview().inset(16)
}
}
func prepareLocationButton() {
locationButton.tintColor = ThemeManager.Colors.locationBlue
locationButton.setImage(UIImage(named: "location_arrow"), for: .normal)
locationButton.setTitle("Use Current Location".localized, for: .normal)
locationButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
locationButton.setTitleColor(ThemeManager.Colors.locationBlue.highlighted, for: .highlighted)
locationButton.addTarget(self, action: #selector(handleLocationButton), for: .touchUpInside)
locationButton.titleLabel?.font = AppFont.SFPro.regular(size: 18)
locationButton.titleEdgeInsets = .init(top: 0, left: 26, bottom: 0, right: 0)
locationButton.sizeToFit()
view.addSubview(locationButton)
locationButton.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(16)
make.top.equalTo(searchBar.snp.bottom).offset(4)
}
}
func prepareTableView() {
tableView.backgroundColor = view.backgroundColor
tableView.register(CityCell.self, forCellReuseIdentifier: CityCell.kIdentifier)
tableView.tableFooterView = UIView()
tableView.rowHeight = 42
tableView.delegate = self
tableView.dataSource = self
view.addSubview(tableView)
tableView.snp.makeConstraints { (make) in
make.top.equalTo(locationButton.snp.bottom).offset(24)
make.left.equalToSuperview()
make.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
func prepareEditButton() {
editButton.setImage(UIImage(named: "edit_pencil"), for: .normal)
editButton.tintColor = ThemeManager.Colors.locationBlue
editButton.setTitle("EDIT".localized, for: .normal)
editButton.setTitleColor(ThemeManager.Colors.locationBlue.highlighted, for: .highlighted)
editButton.setTitleColor(ThemeManager.Colors.locationBlue, for: .normal)
editButton.titleLabel?.font = AppFont.SFPro.regular(size: 12)
editButton.titleEdgeInsets = .init(top: 0, left: 6, bottom: 0, right: 0)
editButton.addTarget(self, action: #selector(handleEditButton(button:)), for: .touchUpInside)
}
}
//MARK:- LocationsViewModel Delegate
extension CitiesViewController: LocationsViewModelDelegate {
func viewModelDidChange(model: LocationsViewModel) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func viewModelDisplayModeDidChange(model: LocationsViewModel) {
DispatchQueue.main.async {
self.tableView.reloadData()
if model.displayMode == .savedCities {
self.searchBar.text = nil
self.searchBar.endEditing(true)
self.searchBar.setShowsCancelButton(false, animated: true)
}
}
}
func viewModelError(model: LocationsViewModel, title: String?, message: String?) {
self.showAlert(withTitle: title, message: message)
}
}
//MARK: UItableView Data Source
extension CitiesViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationsViewModel.cities.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CityCell.kIdentifier, for: indexPath) as! CityCell
cell.configure(wdtLocation: locationsViewModel.cities[indexPath.row],
mode: locationsViewModel.displayMode)
cell.onAdd = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.locationsViewModel.add(city: strongSelf.locationsViewModel.cities[indexPath.row])
}
cell.onSelect = {[weak self] in
guard let strongSelf = self else { return }
strongSelf.locationsViewModel.select(city: strongSelf.locationsViewModel.cities[indexPath.row])
}
return cell
}
}
//MARK: UITableView Delegate
extension CitiesViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let container = UIView()
container.backgroundColor = ThemeManager.currentTheme.navigationBarBackgroundColor
let titleLabel = UILabel()
titleLabel.font = AppFont.SFPro.bold(size: 24)
titleLabel.textColor = ThemeManager.currentTheme.primaryTextColor
container.addSubview(titleLabel)
switch self.locationsViewModel.displayMode {
case .savedCities:
titleLabel.text = "Saved Cities".localized + "(\(self.locationsViewModel.cities.count))"
//Add Edit button
container.addSubview(editButton)
editButton.snp.makeConstraints { (make) in
make.right.equalToSuperview().inset(24)
make.bottom.equalTo(titleLabel)
}
case .popularCities:
titleLabel.text = "5 Popular cities".localized
case .searchResults:
titleLabel.text = "Results".localized + "(\(self.locationsViewModel.cities.count))"
}
titleLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().inset(24)
make.top.equalToSuperview()
make.bottom.equalToSuperview().inset(24)
}
return container
}
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
print("Will begin editing")
self.updateEditButton(style: .done)
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
print("Did end editing")
self.updateEditButton(style: .edit)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let cityToDelete = self.locationsViewModel.cities[indexPath.row]
self.locationsViewModel.delete(city: cityToDelete)
}
}
//MARK:- UISearchBar Delegate
extension CitiesViewController: UISearchBarDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(true, animated: true)
self.locationsViewModel.displayMode = .popularCities
self.locationsViewModel.fetchPopularCities()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
defer {
self.locationsViewModel.displayMode = .searchResults
}
if searchText.count == 0 {
self.locationsViewModel.clean()
return
}
searchQueue.cancelAllOperations()
let operation = BlockOperation()
weak var weakOperation = operation
operation.addExecutionBlock { [weak self] in
Thread.sleep(forTimeInterval: 0.5)
guard weakOperation?.isCancelled == false else {
return
}
self?.locationsViewModel.fetchCities(query: searchText)
}
searchQueue.addOperation(operation)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: true)
self.searchBar.endEditing(true)
searchBar.text = nil
self.locationsViewModel.displayMode = .savedCities
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: true)
self.searchBar.endEditing(true)
}
}
//
// LocationsViewModel.swift
// 1Weather
//
// Created by Dmitry Stepanets on 30.11.2020.
//
import UIKit
import AlgoliaSearchClient
protocol LocationsViewModelDelegate:class {
func viewModelDidChange(model:LocationsViewModel)
func viewModelDisplayModeDidChange(model: LocationsViewModel)
func viewModelError(model:LocationsViewModel, title:String?, message:String?)
}
enum LocationsViewModelDisplayMode {
case savedCities
case popularCities
case searchResults
}
class LocationsViewModel {
//Public
weak var delegate:LocationsViewModelDelegate?
var cities:[WdtLocation] {
switch self.displayMode {
case .savedCities:
return WeatherUpdateManager.shared.allWdtLocations ?? [WdtLocation]()
case .popularCities:
return self.popularCities
case .searchResults:
return self.fetchedCities
}
}
var displayMode:LocationsViewModelDisplayMode = .savedCities {
didSet {
if oldValue == .searchResults {
clean()
}
self.delegate?.viewModelDisplayModeDidChange(model: self)
}
}
//Private
private let popularCitiesManager = PopularCitiesManager()
private var popularCities = [WdtLocation]() {
didSet {
assert(Thread.isMainThread)
self.delegate?.viewModelDidChange(model: self)
}
}
private var fetchedCities = [WdtLocation]() {
didSet {
assert(Thread.isMainThread)
self.delegate?.viewModelDidChange(model: self)
}
}
//MARK:- ViewModel life cycle
func clean() {
fetchedCities.removeAll()
}
func fetchPopularCities() {
self.popularCitiesManager.fetchPopularCities {[weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let popularCities):
DispatchQueue.main.async {
strongSelf.popularCities = popularCities.map{return WdtLocation(place: $0)}
}
case .failure(let error):
strongSelf.delegate?.viewModelError(model: strongSelf, title: "Error", message: error.localizedDescription)
}
}
}
func fetchCities(query:String) {
// This is a test location that can be interpreted on the server to return all alerts
guard query != "1wville" else {
let fakePlace = GeoNamesPlace()
fakePlace.latitude = "67.25639"
fakePlace.longitude = "-150.18417"
fakePlace.city = "1WVille"
fakePlace.state = "Alaska"
fakePlace.stateCode = "AK"
fakePlace.country = "United States"
fakePlace.countryCode = "US"
fakePlace.fcodeName = "populated place"
fakePlace.toponymName = "1WVille"
DispatchQueue.main.async {
self.fetchedCities = []
}
return
}
Logger.minSeverityLevel = .warning
var algoliaQuery = PlacesQuery(query)
algoliaQuery.aroundLatLngViaIP = true
algoliaQuery.type = .city
let placesClient: PlacesClient = PlacesClient(appID: ApplicationID (rawValue: kAlgoliaAppId),
apiKey: APIKey(rawValue: kAlgoliaAPIKey))
let language: Language = Language(rawValue: NSLocale.preferredLanguages.first?.components(separatedBy: "-").first ?? "en")
print("Search: using language \(language.rawValue)")
var filteredPlaces = [GeoNamesPlace]()
placesClient.search(query: algoliaQuery, language: language, requestOptions: nil) {[weak self] (result: Result<PlacesClient.SingleLanguageResponse, Error>) in
guard let strongSelf = self else { return }
switch (result) {
case .success(let response):
print("Search: got \(response.nbHits) results")
for hit: Hit<Place> in response.hits {
let place = GeoNamesPlace()
if hit.object.localeNames?.first == nil {
print("Search: skip 1 object.");
continue
}
place.city = hit.object.localeNames?.first
place.state = hit.object.administrative?.first
place.country = hit.object.country ?? "" // ?? "" part is from the Android code, may be not needed on iOS
place.countryCode = hit.object.countryCode?.rawValue.uppercased() ?? "" // ?? "" part is from the Android code, may be not needed on iOS
if let geolocation = hit.geolocation {
switch geolocation {
case .single(let location):
place.latitude = "\(location.latitude)"
place.longitude = "\(location.longitude)"
case .list(let locations):
if let firstLat = locations.first?.latitude,
let firstLong = locations.first?.longitude
{
place.latitude = "\(firstLat)"
place.longitude = "\(firstLong)"
}
else {
place.latitude = ""
place.longitude = ""
}
}
}
else {
place.latitude = ""
place.longitude = ""
}
filteredPlaces.append(place)
}
DispatchQueue.main.async {
strongSelf.fetchedCities = filteredPlaces.map{return WdtLocation(place: $0)}
}
break
case .failure(let error):
DispatchQueue.main.async {
strongSelf.delegate?.viewModelError(model: strongSelf, title: "Error", message: error.localizedDescription)
}
break
}
}
}
//MARK:- City CUD methods
func add(city:WdtLocation) {
// TODO: Just update self.cities, it will trigger a viewModelDidChange call
if WeatherUpdateManager.shared.allLocationsCount >= 12 {
self.delegate?.viewModelError(model: self, title: nil, message: "To keep your 1Weather running in tip-top shape, please limit the number of locations to 12.".localized)
return
}
if WeatherUpdateManager.shared.addLocation(city) {
self.displayMode = .savedCities
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to add location")
}
}
func delete(city:WdtLocation) {
// TODO: Just update self.cities, it will trigger a viewModelDidChange call
if WeatherUpdateManager.shared.deleteLocation(city) {
self.delegate?.viewModelDidChange(model: self)
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to delete location")
}
}
func select(city:WdtLocation) {
// TODO: Tell delegate to refresh UI
if WeatherUpdateManager.shared.addLocation(city) {
self.delegate?.viewModelDidChange(model: self)
}
else {
self.delegate?.viewModelError(model: self, title: "Error", message: "Failed to add location")
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment