diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..261d6e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +# Created by https://www.toptal.com/developers/gitignore/api/swift,xcode +# Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ +/Tracker/ApiKey/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/swift,xcode \ No newline at end of file diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..fd5ffc9 --- /dev/null +++ b/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'Tracker' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for Tracker + + target 'TrackerTests' do + inherit! :search_paths + # Pods for testing + end + +pod 'YandexMobileMetrica/Dynamic', '4.5.2' + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..b61653f --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,21 @@ +PODS: + - YandexMobileMetrica/Dynamic (4.5.2): + - YandexMobileMetrica/Dynamic/Core (= 4.5.2) + - YandexMobileMetrica/Dynamic/Crashes (= 4.5.2) + - YandexMobileMetrica/Dynamic/Core (4.5.2) + - YandexMobileMetrica/Dynamic/Crashes (4.5.2): + - YandexMobileMetrica/Dynamic/Core + +DEPENDENCIES: + - YandexMobileMetrica/Dynamic (= 4.5.2) + +SPEC REPOS: + trunk: + - YandexMobileMetrica + +SPEC CHECKSUMS: + YandexMobileMetrica: f5368ee93f286c793d73b58da00929babfc897c1 + +PODFILE CHECKSUM: 240fd84f5bd7692fb2fe751b5cad5d39b700da0b + +COCOAPODS: 1.11.3 diff --git a/Tracker.xcodeproj/project.pbxproj b/Tracker.xcodeproj/project.pbxproj index a1c7625..112b1d7 100644 --- a/Tracker.xcodeproj/project.pbxproj +++ b/Tracker.xcodeproj/project.pbxproj @@ -12,8 +12,18 @@ 2A12A2B42A9A2AF10024B3E4 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A12A2B32A9A2AF10024B3E4 /* TabBarController.swift */; }; 2A12A2BC2A9A74200024B3E4 /* StatisticViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A12A2BB2A9A74200024B3E4 /* StatisticViewController.swift */; }; 2A162FFB2AC8A8DE00BDBF79 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A162FFA2AC8A8DE00BDBF79 /* Observable.swift */; }; + 2A1F65E02ADB319000DB86D7 /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F65DF2ADB319000DB86D7 /* AnalyticsService.swift */; }; + 2A1F65E32ADB47E800DB86D7 /* AnalyticsServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F65E22ADB47E800DB86D7 /* AnalyticsServiceProtocol.swift */; }; + 2A1F65E62ADB4FA700DB86D7 /* ApiKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A1F65E52ADB4FA700DB86D7 /* ApiKeys.swift */; }; 2A28C1812ABE11AD0083CAA4 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A28C1802ABE11AD0083CAA4 /* UIViewController.swift */; }; + 2A2AA81E2AD4909000B39896 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 2A2AA8202AD4909000B39896 /* Localizable.stringsdict */; }; + 2A4229142AD9AC4500ABDDA3 /* ImageAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4229132AD9AC4500ABDDA3 /* ImageAssets.swift */; }; + 2A4542F42AE008B80020A0D7 /* StatisticsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4542F32AE008B80020A0D7 /* StatisticsViewModel.swift */; }; 2A4F9BA32AC0AB8200C46DA4 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4F9BA22AC0AB8200C46DA4 /* OnboardingViewController.swift */; }; + 2A5383CE2ADACA310012598F /* TrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5383CD2ADACA310012598F /* TrackerTests.swift */; }; + 2A5383D62ADACADC0012598F /* InlineSnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 2A5383D52ADACADC0012598F /* InlineSnapshotTesting */; }; + 2A5383D82ADACADC0012598F /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 2A5383D72ADACADC0012598F /* SnapshotTesting */; }; + 2A5383DC2ADAD8020012598F /* ColoursTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5383DB2ADAD8020012598F /* ColoursTheme.swift */; }; 2A68040B2AB358B60067DB79 /* TrackerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A68040A2AB358B60067DB79 /* TrackerStore.swift */; }; 2A68040D2AB358CD0067DB79 /* TrackerCategoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A68040C2AB358CD0067DB79 /* TrackerCategoryStore.swift */; }; 2A68040F2AB358DB0067DB79 /* TrackerRecordStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A68040E2AB358DB0067DB79 /* TrackerRecordStore.swift */; }; @@ -25,11 +35,16 @@ 2A7FD86F2A97F107003ED0DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A7FD86E2A97F107003ED0DD /* Assets.xcassets */; }; 2A7FD8722A97F107003ED0DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2A7FD8702A97F107003ED0DD /* LaunchScreen.storyboard */; }; 2A7FD87A2A97FA75003ED0DD /* TrackerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7FD8792A97FA75003ED0DD /* TrackerCell.swift */; }; + 2A9D48522AD566220058AF33 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2A9D48542AD566220058AF33 /* Localizable.strings */; }; + 2A9D48572AD5A9A20058AF33 /* LocalizableKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D48562AD5A9A20058AF33 /* LocalizableKeys.swift */; }; + 2A9D48592AD5AB440058AF33 /* WeekDays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D48582AD5AB440058AF33 /* WeekDays.swift */; }; + 2A9D485F2AD5B6500058AF33 /* WeekDay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9D485E2AD5B6500058AF33 /* WeekDay.swift */; }; 2AAAAD982AA724F100E6C6CE /* TimetableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AAAAD972AA724F100E6C6CE /* TimetableViewController.swift */; }; 2AAAAD9A2AA72D2100E6C6CE /* TimetableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AAAAD992AA72D2100E6C6CE /* TimetableCell.swift */; }; 2ACDCA4C2AC48DDA008827A0 /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDCA4B2AC48DDA008827A0 /* UserDefaultsManager.swift */; }; 2AD904D42AC1A12300CBB9E0 /* OnboardingFirst.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD904D32AC1A12300CBB9E0 /* OnboardingFirst.swift */; }; 2AD904D62AC1A26F00CBB9E0 /* OnboardingSecond.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD904D52AC1A26F00CBB9E0 /* OnboardingSecond.swift */; }; + 2AE5A1842AE080E700C030EB /* FiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE5A1832AE080E700C030EB /* FiltersViewController.swift */; }; 2AED5D322AA0FB4E0049ACD4 /* Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED5D312AA0FB4E0049ACD4 /* Tracker.swift */; }; 2AED5D352AA10A1D0049ACD4 /* UIView + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED5D342AA10A1D0049ACD4 /* UIView + Extension.swift */; }; 2AED5D372AA10E640049ACD4 /* Cell + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED5D362AA10E640049ACD4 /* Cell + Extension.swift */; }; @@ -48,18 +63,41 @@ 2AED5D552AA6564E0049ACD4 /* AddNewCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AED5D542AA6564E0049ACD4 /* AddNewCategoryViewController.swift */; }; 2AEFA3052AB8E00D00B15035 /* String + Extention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AEFA3042AB8E00D00B15035 /* String + Extention.swift */; }; 2AFF93B02AC8464B00E8695A /* CategoriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFF93AF2AC8464B00E8695A /* CategoriesViewModel.swift */; }; + 8D69DFB83ADE67EFAFE96C1F /* Pods_TrackerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86C2D6D744E0062AE7D7C07B /* Pods_TrackerTests.framework */; }; + A113249DEE53717BE5025414 /* Pods_Tracker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 127F24CCEF71914A30631371 /* Pods_Tracker.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 2A5383CF2ADACA310012598F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2A7FD85A2A97F104003ED0DD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2A7FD8612A97F104003ED0DD; + remoteInfo = Tracker; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 127F24CCEF71914A30631371 /* Pods_Tracker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tracker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2A0B39AD2AB63F7C00276B79 /* WeekdaysMarshalling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekdaysMarshalling.swift; sourceTree = ""; }; 2A0B39AF2AB7157B00276B79 /* TrackerCategoryStoreUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerCategoryStoreUpdate.swift; sourceTree = ""; }; 2A12A2B32A9A2AF10024B3E4 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 2A12A2BB2A9A74200024B3E4 /* StatisticViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticViewController.swift; sourceTree = ""; }; 2A162FFA2AC8A8DE00BDBF79 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; + 2A1F65DF2ADB319000DB86D7 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; + 2A1F65E22ADB47E800DB86D7 /* AnalyticsServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsServiceProtocol.swift; sourceTree = ""; }; + 2A1F65E52ADB4FA700DB86D7 /* ApiKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiKeys.swift; sourceTree = ""; }; 2A28C1802ABE11AD0083CAA4 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; + 2A2AA81F2AD4909000B39896 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; + 2A2AA8212AD4909300B39896 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; 2A3B6B0D2AA8EC5E000E3B78 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; 2A3B6B0E2AA8EC87000E3B78 /* ru */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = ru; path = ru.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 2A4229132AD9AC4500ABDDA3 /* ImageAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAssets.swift; sourceTree = ""; }; + 2A4542F32AE008B80020A0D7 /* StatisticsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsViewModel.swift; sourceTree = ""; }; 2A4F9BA22AC0AB8200C46DA4 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; + 2A5383CB2ADACA310012598F /* TrackerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TrackerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A5383CD2ADACA310012598F /* TrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerTests.swift; sourceTree = ""; }; + 2A5383DB2ADAD8020012598F /* ColoursTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoursTheme.swift; sourceTree = ""; }; 2A68040A2AB358B60067DB79 /* TrackerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerStore.swift; sourceTree = ""; }; 2A68040C2AB358CD0067DB79 /* TrackerCategoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerCategoryStore.swift; sourceTree = ""; }; 2A68040E2AB358DB0067DB79 /* TrackerRecordStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerRecordStore.swift; sourceTree = ""; }; @@ -72,11 +110,17 @@ 2A7FD86E2A97F107003ED0DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2A7FD8732A97F107003ED0DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2A7FD8792A97FA75003ED0DD /* TrackerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerCell.swift; sourceTree = ""; }; + 2A9D48532AD566220058AF33 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 2A9D48552AD566260058AF33 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 2A9D48562AD5A9A20058AF33 /* LocalizableKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizableKeys.swift; sourceTree = ""; }; + 2A9D48582AD5AB440058AF33 /* WeekDays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekDays.swift; sourceTree = ""; }; + 2A9D485E2AD5B6500058AF33 /* WeekDay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekDay.swift; sourceTree = ""; }; 2AAAAD972AA724F100E6C6CE /* TimetableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimetableViewController.swift; sourceTree = ""; }; 2AAAAD992AA72D2100E6C6CE /* TimetableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimetableCell.swift; sourceTree = ""; }; 2ACDCA4B2AC48DDA008827A0 /* UserDefaultsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsManager.swift; sourceTree = ""; }; 2AD904D32AC1A12300CBB9E0 /* OnboardingFirst.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFirst.swift; sourceTree = ""; }; 2AD904D52AC1A26F00CBB9E0 /* OnboardingSecond.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSecond.swift; sourceTree = ""; }; + 2AE5A1832AE080E700C030EB /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = ""; }; 2AED5D312AA0FB4E0049ACD4 /* Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tracker.swift; sourceTree = ""; }; 2AED5D342AA10A1D0049ACD4 /* UIView + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView + Extension.swift"; sourceTree = ""; }; 2AED5D362AA10E640049ACD4 /* Cell + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cell + Extension.swift"; sourceTree = ""; }; @@ -95,13 +139,29 @@ 2AED5D542AA6564E0049ACD4 /* AddNewCategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddNewCategoryViewController.swift; sourceTree = ""; }; 2AEFA3042AB8E00D00B15035 /* String + Extention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String + Extention.swift"; sourceTree = ""; }; 2AFF93AF2AC8464B00E8695A /* CategoriesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoriesViewModel.swift; sourceTree = ""; }; + 3CCA146E031A00F301EF515C /* Pods-TrackerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TrackerTests.debug.xcconfig"; path = "Target Support Files/Pods-TrackerTests/Pods-TrackerTests.debug.xcconfig"; sourceTree = ""; }; + 58A5C9B19ED5AACD8CDBF939 /* Pods-TrackerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TrackerTests.release.xcconfig"; path = "Target Support Files/Pods-TrackerTests/Pods-TrackerTests.release.xcconfig"; sourceTree = ""; }; + 86C2D6D744E0062AE7D7C07B /* Pods_TrackerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TrackerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 88337A2DED128664985FB635 /* Pods-Tracker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tracker.debug.xcconfig"; path = "Target Support Files/Pods-Tracker/Pods-Tracker.debug.xcconfig"; sourceTree = ""; }; + CA619D95CA1A2DA1198E5109 /* Pods-Tracker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tracker.release.xcconfig"; path = "Target Support Files/Pods-Tracker/Pods-Tracker.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2A5383C82ADACA310012598F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A5383D62ADACADC0012598F /* InlineSnapshotTesting in Frameworks */, + 2A5383D82ADACADC0012598F /* SnapshotTesting in Frameworks */, + 8D69DFB83ADE67EFAFE96C1F /* Pods_TrackerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2A7FD85F2A97F104003ED0DD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A113249DEE53717BE5025414 /* Pods_Tracker.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -152,8 +212,9 @@ 2A06A8222AC20D3100FFB324 /* Onboarding */, 2A12A2B22A9A2AD90024B3E4 /* TabBarController */, 2A12A2B52A9A2AFF0024B3E4 /* Tracker */, - 2A12A2BA2A9A73F50024B3E4 /* Statistic */, + 2AE5A1822AE080AD00C030EB /* Filters */, 2AED5D382AA212E20049ACD4 /* NewTrackers */, + 2A12A2BA2A9A73F50024B3E4 /* Statistic */, ); path = Controllers; sourceTree = ""; @@ -161,6 +222,7 @@ 2A12A2B92A9A636C0024B3E4 /* Resources */ = { isa = PBXGroup; children = ( + 2A4229132AD9AC4500ABDDA3 /* ImageAssets.swift */, 2A7FD86E2A97F107003ED0DD /* Assets.xcassets */, 2A7FD8702A97F107003ED0DD /* LaunchScreen.storyboard */, 2A7FD8732A97F107003ED0DD /* Info.plist */, @@ -172,10 +234,46 @@ isa = PBXGroup; children = ( 2A12A2BB2A9A74200024B3E4 /* StatisticViewController.swift */, + 2A4542F32AE008B80020A0D7 /* StatisticsViewModel.swift */, ); path = Statistic; sourceTree = ""; }; + 2A1F65E12ADB47A400DB86D7 /* Service */ = { + isa = PBXGroup; + children = ( + 2A1F65DF2ADB319000DB86D7 /* AnalyticsService.swift */, + 2A1F65E22ADB47E800DB86D7 /* AnalyticsServiceProtocol.swift */, + ); + path = Service; + sourceTree = ""; + }; + 2A1F65E42ADB4F7900DB86D7 /* ApiKey */ = { + isa = PBXGroup; + children = ( + 2A1F65E52ADB4FA700DB86D7 /* ApiKeys.swift */, + ); + path = ApiKey; + sourceTree = ""; + }; + 2A2AA81B2AD4904500B39896 /* Localization */ = { + isa = PBXGroup; + children = ( + 2A9D48562AD5A9A20058AF33 /* LocalizableKeys.swift */, + 2A2AA8202AD4909000B39896 /* Localizable.stringsdict */, + 2A9D48542AD566220058AF33 /* Localizable.strings */, + ); + path = Localization; + sourceTree = ""; + }; + 2A5383CC2ADACA310012598F /* TrackerTests */ = { + isa = PBXGroup; + children = ( + 2A5383CD2ADACA310012598F /* TrackerTests.swift */, + ); + path = TrackerTests; + sourceTree = ""; + }; 2A6804082AB353820067DB79 /* CoreData */ = { isa = PBXGroup; children = ( @@ -199,7 +297,10 @@ isa = PBXGroup; children = ( 2A7FD8642A97F104003ED0DD /* Tracker */, + 2A5383CC2ADACA310012598F /* TrackerTests */, 2A7FD8632A97F104003ED0DD /* Products */, + 51276C49501FAEB081777BC1 /* Pods */, + E96EA68F08A2E4A450E5D699 /* Frameworks */, ); sourceTree = ""; }; @@ -207,6 +308,7 @@ isa = PBXGroup; children = ( 2A7FD8622A97F104003ED0DD /* Tracker.app */, + 2A5383CB2ADACA310012598F /* TrackerTests.xctest */, ); name = Products; sourceTree = ""; @@ -216,10 +318,14 @@ children = ( 2A12A2B72A9A2B370024B3E4 /* Application */, 2AED5D302AA0FB340049ACD4 /* Models */, + 2A1F65E12ADB47A400DB86D7 /* Service */, + 2AED5D332AA109F00049ACD4 /* Extension */, 2ACDCA4A2AC48D89008827A0 /* Helpers */, + 2A1F65E42ADB4F7900DB86D7 /* ApiKey */, 2A6804092AB3588E0067DB79 /* Domain */, 2A6804082AB353820067DB79 /* CoreData */, 2A12A2B82A9A631C0024B3E4 /* Controllers */, + 2A2AA81B2AD4904500B39896 /* Localization */, 2A12A2B92A9A636C0024B3E4 /* Resources */, ); path = Tracker; @@ -237,15 +343,23 @@ 2ACDCA4A2AC48D89008827A0 /* Helpers */ = { isa = PBXGroup; children = ( - 2AED5D332AA109F00049ACD4 /* Extension */, 2A6804122AB38A7F0067DB79 /* UIColorMarshalling.swift */, 2A0B39AD2AB63F7C00276B79 /* WeekdaysMarshalling.swift */, 2ACDCA4B2AC48DDA008827A0 /* UserDefaultsManager.swift */, 2A162FFA2AC8A8DE00BDBF79 /* Observable.swift */, + 2A5383DB2ADAD8020012598F /* ColoursTheme.swift */, ); path = Helpers; sourceTree = ""; }; + 2AE5A1822AE080AD00C030EB /* Filters */ = { + isa = PBXGroup; + children = ( + 2AE5A1832AE080E700C030EB /* FiltersViewController.swift */, + ); + path = Filters; + sourceTree = ""; + }; 2AED5D302AA0FB340049ACD4 /* Models */ = { isa = PBXGroup; children = ( @@ -253,6 +367,7 @@ 2AED5D3B2AA21BB80049ACD4 /* TrackerCategory.swift */, 2AED5D3D2AA21C140049ACD4 /* TrackerRecord.swift */, 2AED5D412AA248270049ACD4 /* GeometryParams.swift */, + 2A9D485E2AD5B6500058AF33 /* WeekDay.swift */, ); path = Models; sourceTree = ""; @@ -265,6 +380,7 @@ 2AED5D362AA10E640049ACD4 /* Cell + Extension.swift */, 2AED5D4D2AA615990049ACD4 /* UIColor + Extension.swift */, 2AEFA3042AB8E00D00B15035 /* String + Extention.swift */, + 2A9D48582AD5AB440058AF33 /* WeekDays.swift */, ); path = Extension; sourceTree = ""; @@ -293,16 +409,61 @@ path = Categories; sourceTree = ""; }; + 51276C49501FAEB081777BC1 /* Pods */ = { + isa = PBXGroup; + children = ( + 88337A2DED128664985FB635 /* Pods-Tracker.debug.xcconfig */, + CA619D95CA1A2DA1198E5109 /* Pods-Tracker.release.xcconfig */, + 3CCA146E031A00F301EF515C /* Pods-TrackerTests.debug.xcconfig */, + 58A5C9B19ED5AACD8CDBF939 /* Pods-TrackerTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + E96EA68F08A2E4A450E5D699 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 127F24CCEF71914A30631371 /* Pods_Tracker.framework */, + 86C2D6D744E0062AE7D7C07B /* Pods_TrackerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 2A5383CA2ADACA310012598F /* TrackerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A5383D32ADACA310012598F /* Build configuration list for PBXNativeTarget "TrackerTests" */; + buildPhases = ( + 060DE908E44B6B556765F708 /* [CP] Check Pods Manifest.lock */, + 2A5383C72ADACA310012598F /* Sources */, + 2A5383C82ADACA310012598F /* Frameworks */, + 2A5383C92ADACA310012598F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2A5383D02ADACA310012598F /* PBXTargetDependency */, + ); + name = TrackerTests; + packageProductDependencies = ( + 2A5383D52ADACADC0012598F /* InlineSnapshotTesting */, + 2A5383D72ADACADC0012598F /* SnapshotTesting */, + ); + productName = TrackerTests; + productReference = 2A5383CB2ADACA310012598F /* TrackerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 2A7FD8612A97F104003ED0DD /* Tracker */ = { isa = PBXNativeTarget; buildConfigurationList = 2A7FD8762A97F107003ED0DD /* Build configuration list for PBXNativeTarget "Tracker" */; buildPhases = ( + 43D432358A47A8D1DCB3E951 /* [CP] Check Pods Manifest.lock */, 2A7FD85E2A97F104003ED0DD /* Sources */, 2A7FD85F2A97F104003ED0DD /* Frameworks */, 2A7FD8602A97F104003ED0DD /* Resources */, + CA732AB51E9F9E73934FB76B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -323,6 +484,10 @@ LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1500; TargetAttributes = { + 2A5383CA2ADACA310012598F = { + CreatedOnToolsVersion = 15.0; + TestTargetID = 2A7FD8612A97F104003ED0DD; + }; 2A7FD8612A97F104003ED0DD = { CreatedOnToolsVersion = 15.0; }; @@ -337,28 +502,113 @@ ru, ); mainGroup = 2A7FD8592A97F104003ED0DD; + packageReferences = ( + 2A5383D42ADACADC0012598F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + ); productRefGroup = 2A7FD8632A97F104003ED0DD /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2A7FD8612A97F104003ED0DD /* Tracker */, + 2A5383CA2ADACA310012598F /* TrackerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 2A5383C92ADACA310012598F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2A7FD8602A97F104003ED0DD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2A7FD8722A97F107003ED0DD /* LaunchScreen.storyboard in Resources */, + 2A9D48522AD566220058AF33 /* Localizable.strings in Resources */, 2A7FD86F2A97F107003ED0DD /* Assets.xcassets in Resources */, + 2A2AA81E2AD4909000B39896 /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 060DE908E44B6B556765F708 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TrackerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 43D432358A47A8D1DCB3E951 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Tracker-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CA732AB51E9F9E73934FB76B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Tracker/Pods-Tracker-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Tracker/Pods-Tracker-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tracker/Pods-Tracker-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ + 2A5383C72ADACA310012598F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A5383CE2ADACA310012598F /* TrackerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2A7FD85E2A97F104003ED0DD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -369,14 +619,20 @@ 2A0B39AE2AB63F7C00276B79 /* WeekdaysMarshalling.swift in Sources */, 2A7BD6A22AB0C14200048713 /* TrackerModel.xcdatamodeld in Sources */, 2A0B39B02AB7157B00276B79 /* TrackerCategoryStoreUpdate.swift in Sources */, + 2A1F65E32ADB47E800DB86D7 /* AnalyticsServiceProtocol.swift in Sources */, 2AAAAD9A2AA72D2100E6C6CE /* TimetableCell.swift in Sources */, 2A28C1812ABE11AD0083CAA4 /* UIViewController.swift in Sources */, 2AED5D372AA10E640049ACD4 /* Cell + Extension.swift in Sources */, 2A7FD8662A97F104003ED0DD /* AppDelegate.swift in Sources */, + 2A4542F42AE008B80020A0D7 /* StatisticsViewModel.swift in Sources */, 2AED5D4E2AA615990049ACD4 /* UIColor + Extension.swift in Sources */, + 2A1F65E02ADB319000DB86D7 /* AnalyticsService.swift in Sources */, + 2A5383DC2ADAD8020012598F /* ColoursTheme.swift in Sources */, 2A6804132AB38A7F0067DB79 /* UIColorMarshalling.swift in Sources */, + 2AE5A1842AE080E700C030EB /* FiltersViewController.swift in Sources */, 2A68040F2AB358DB0067DB79 /* TrackerRecordStore.swift in Sources */, 2AED5D552AA6564E0049ACD4 /* AddNewCategoryViewController.swift in Sources */, + 2A9D48572AD5A9A20058AF33 /* LocalizableKeys.swift in Sources */, 2AED5D3A2AA2135B0049ACD4 /* TrackersTypeViewController.swift in Sources */, 2A162FFB2AC8A8DE00BDBF79 /* Observable.swift in Sources */, 2AED5D3E2AA21C140049ACD4 /* TrackerRecord.swift in Sources */, @@ -385,6 +641,8 @@ 2AED5D532AA64C920049ACD4 /* CategoriesViewController.swift in Sources */, 2AD904D62AC1A26F00CBB9E0 /* OnboardingSecond.swift in Sources */, 2A12A2BC2A9A74200024B3E4 /* StatisticViewController.swift in Sources */, + 2A4229142AD9AC4500ABDDA3 /* ImageAssets.swift in Sources */, + 2A9D48592AD5AB440058AF33 /* WeekDays.swift in Sources */, 2AFF93B02AC8464B00E8695A /* CategoriesViewModel.swift in Sources */, 2AED5D352AA10A1D0049ACD4 /* UIView + Extension.swift in Sources */, 2AED5D442AA2599A0049ACD4 /* HeaderViewCell.swift in Sources */, @@ -394,8 +652,10 @@ 2A12A2B42A9A2AF10024B3E4 /* TabBarController.swift in Sources */, 2A68040D2AB358CD0067DB79 /* TrackerCategoryStore.swift in Sources */, 2A7FD8682A97F104003ED0DD /* SceneDelegate.swift in Sources */, + 2A1F65E62ADB4FA700DB86D7 /* ApiKeys.swift in Sources */, 2AED5D502AA643A10049ACD4 /* EmptyView.swift in Sources */, 2AED5D322AA0FB4E0049ACD4 /* Tracker.swift in Sources */, + 2A9D485F2AD5B6500058AF33 /* WeekDay.swift in Sources */, 2AED5D462AA273AD0049ACD4 /* NewTrackerViewController.swift in Sources */, 2AED5D4C2AA6148A0049ACD4 /* ColorsCollectionViewCell.swift in Sources */, 2A4F9BA32AC0AB8200C46DA4 /* OnboardingViewController.swift in Sources */, @@ -407,7 +667,24 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 2A5383D02ADACA310012598F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2A7FD8612A97F104003ED0DD /* Tracker */; + targetProxy = 2A5383CF2ADACA310012598F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ + 2A2AA8202AD4909000B39896 /* Localizable.stringsdict */ = { + isa = PBXVariantGroup; + children = ( + 2A2AA81F2AD4909000B39896 /* en */, + 2A2AA8212AD4909300B39896 /* ru */, + ); + name = Localizable.stringsdict; + sourceTree = ""; + }; 2A7FD8702A97F107003ED0DD /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -417,9 +694,64 @@ name = LaunchScreen.storyboard; sourceTree = ""; }; + 2A9D48542AD566220058AF33 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 2A9D48532AD566220058AF33 /* en */, + 2A9D48552AD566260058AF33 /* ru */, + ); + name = Localizable.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 2A5383D12ADACA310012598F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3CCA146E031A00F301EF515C /* Pods-TrackerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TLSJ5SA5W3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = crowru.TrackerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Tracker"; + }; + name = Debug; + }; + 2A5383D22ADACA310012598F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 58A5C9B19ED5AACD8CDBF939 /* Pods-TrackerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = TLSJ5SA5W3; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = crowru.TrackerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Tracker"; + }; + name = Release; + }; 2A7FD8742A97F107003ED0DD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -458,7 +790,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -523,7 +855,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -545,6 +877,7 @@ }; 2A7FD8772A97F107003ED0DD /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 88337A2DED128664985FB635 /* Pods-Tracker.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -577,6 +910,7 @@ }; 2A7FD8782A97F107003ED0DD /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = CA619D95CA1A2DA1198E5109 /* Pods-Tracker.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -610,6 +944,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2A5383D32ADACA310012598F /* Build configuration list for PBXNativeTarget "TrackerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A5383D12ADACA310012598F /* Debug */, + 2A5383D22ADACA310012598F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 2A7FD85D2A97F104003ED0DD /* Build configuration list for PBXProject "Tracker" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -630,6 +973,30 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + 2A5383D42ADACADC0012598F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.14.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2A5383D52ADACADC0012598F /* InlineSnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 2A5383D42ADACADC0012598F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = InlineSnapshotTesting; + }; + 2A5383D72ADACADC0012598F /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 2A5383D42ADACADC0012598F /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; +/* End XCSwiftPackageProductDependency section */ + /* Begin XCVersionGroup section */ 2A7BD6A02AB0C14200048713 /* TrackerModel.xcdatamodeld */ = { isa = XCVersionGroup; diff --git a/Tracker.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tracker.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..d2dfc75 --- /dev/null +++ b/Tracker.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "bb0ea08db8e73324fe6c3727f755ca41a23ff2f4", + "version" : "1.14.2" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", + "version" : "509.0.0" + } + } + ], + "version" : 2 +} diff --git a/Tracker.xcodeproj/xcshareddata/xcschemes/Tracker.xcscheme b/Tracker.xcodeproj/xcshareddata/xcschemes/Tracker.xcscheme new file mode 100644 index 0000000..27a38bd --- /dev/null +++ b/Tracker.xcodeproj/xcshareddata/xcschemes/Tracker.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tracker.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist b/Tracker.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist index 99f961b..488a80b 100644 --- a/Tracker.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Tracker.xcodeproj/xcuserdata/crowru.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,13 @@ 0 + SuppressBuildableAutocreation + + 2A7FD8612A97F104003ED0DD + + primary + + + diff --git a/Tracker.xcworkspace/contents.xcworkspacedata b/Tracker.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..0f1322d --- /dev/null +++ b/Tracker.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Tracker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Tracker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Tracker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Tracker.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tracker.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..d2dfc75 --- /dev/null +++ b/Tracker.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "bb0ea08db8e73324fe6c3727f755ca41a23ff2f4", + "version" : "1.14.2" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", + "version" : "509.0.0" + } + } + ], + "version" : 2 +} diff --git a/Tracker/ApiKey/ApiKeys.swift b/Tracker/ApiKey/ApiKeys.swift new file mode 100644 index 0000000..bda9676 --- /dev/null +++ b/Tracker/ApiKey/ApiKeys.swift @@ -0,0 +1,12 @@ +// +// ApiKeys.swift +// Tracker +// +// Created by Руслан on 15.10.2023. +// + +import Foundation + +struct ApiKeys { + static let apiKeyYMM: String? = "aa90b654-cdbc-47d9-879b-3e3dbf7ce616" +} diff --git a/Tracker/Application/AppDelegate.swift b/Tracker/Application/AppDelegate.swift index 4c90d99..080b45a 100644 --- a/Tracker/Application/AppDelegate.swift +++ b/Tracker/Application/AppDelegate.swift @@ -10,7 +10,7 @@ import CoreData @main class AppDelegate: UIResponder, UIApplicationDelegate { - + lazy var persistantContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "TrackerModel") container.loadPersistentStores { storeDescription, error in @@ -22,6 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + AnalyticsService.activateAnalytics() return true } diff --git a/Tracker/Controllers/Filters/FiltersViewController.swift b/Tracker/Controllers/Filters/FiltersViewController.swift new file mode 100644 index 0000000..dd8654d --- /dev/null +++ b/Tracker/Controllers/Filters/FiltersViewController.swift @@ -0,0 +1,106 @@ +// +// FiltersViewController.swift +// Tracker +// +// Created by Руслан on 19.10.2023. +// + +import UIKit + +protocol FiltersViewControllerProtocol: AnyObject { + func filterAllTrackers() + func filterTrackersForToday() + func filterCompletedTrackers() + func filterUnCompletedTrackers() +} + +final class FiltersViewController: UIViewController { + private let namesRows = [ + LocalizableKeys.filterLabelOne, + LocalizableKeys.filterLabelTwo, + LocalizableKeys.filterLabelThree, + LocalizableKeys.filterLabelFour + ] + + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: view.bounds, style: .insetGrouped) + tableView.delegate = self + tableView.dataSource = self + tableView.backgroundColor = ColoursTheme.blackDayWhiteDay + tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + return tableView + }() + + private var editingIndexFilter: IndexPath? { + didSet { + UserDefaultsManager.editingIndexFilter = editingIndexFilter?.row + } + } + + weak var delegate: FiltersViewControllerProtocol? + + // MARK: Life cycle + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(tableView) + dataIndexesCategories() + } + + // MARK: Analytics + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + AnalyticsService.openScreenReport(screen: .main) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + AnalyticsService.closeScreenReport(screen: .main) + } + + // MARK: Methods + private func dataIndexesCategories() { + guard let row = UserDefaultsManager.editingIndexFilter else { return } + editingIndexFilter = IndexPath(row: row, section: 0) + } +} + +// MARK: - UITableViewDataSource +extension FiltersViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return namesRows.count + } + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 75 + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = namesRows[indexPath.row] + cell.backgroundColor = ColoursTheme.backgroundNightDay + cell.accessoryType = indexPath == editingIndexFilter ? .checkmark : .none + return cell + } +} + +// MARK: - UITableViewDelegate +extension FiltersViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let editingIndexPath = editingIndexFilter { + let previousSelectedCell = tableView.cellForRow(at: editingIndexPath) + previousSelectedCell?.accessoryType = .none + } + let cell = tableView.cellForRow(at: indexPath) + cell?.accessoryType = .checkmark + editingIndexFilter = indexPath + tableView.deselectRow(at: indexPath, animated: true) + dismiss(animated: true) + + switch indexPath.row { + case 0: delegate?.filterAllTrackers() + case 1: delegate?.filterTrackersForToday() + case 2: delegate?.filterCompletedTrackers() + case 3: delegate?.filterUnCompletedTrackers() + default: return + } + } +} diff --git a/Tracker/Controllers/NewTrackers/Categories/AddNewCategoryViewController.swift b/Tracker/Controllers/NewTrackers/Categories/AddNewCategoryViewController.swift index 6ed509d..37e5e50 100644 --- a/Tracker/Controllers/NewTrackers/Categories/AddNewCategoryViewController.swift +++ b/Tracker/Controllers/NewTrackers/Categories/AddNewCategoryViewController.swift @@ -17,8 +17,8 @@ final class AddNewCategoryViewController: UIViewController { let textField = UITextField() textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 30)) textField.leftViewMode = .always - textField.placeholder = "Введите название категории" - textField.backgroundColor = .ypBackgroundDay + textField.placeholder = LocalizableKeys.addNewCategoryTextField + textField.backgroundColor = ColoursTheme.backgroundNightDay textField.layer.cornerRadius = 16 textField.clearButtonMode = .whileEditing textField.clipsToBounds = true @@ -30,7 +30,7 @@ final class AddNewCategoryViewController: UIViewController { private lazy var doneButton: UIButton = { let button = UIButton() - button.setTitle("Готово", for: .normal) + button.setTitle(LocalizableKeys.addNewCategoryDoneButton, for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .yp_Gray button.layer.cornerRadius = 16 @@ -41,7 +41,7 @@ final class AddNewCategoryViewController: UIViewController { init() { super.init(nibName: nil, bundle: nil) - title = isEdit ? "Новая категория" : "Редактирование" + title = isEdit ? LocalizableKeys.addNewCategoryNew : LocalizableKeys.addNewCategoryEditing } required init?(coder: NSCoder) { @@ -75,8 +75,8 @@ final class AddNewCategoryViewController: UIViewController { private func editCategory() { if isEdit { textField.text = editText - doneButton.backgroundColor = .ypBlackDay - doneButton.setTitleColor(.white, for: .normal) + doneButton.backgroundColor = ColoursTheme.whiteDayBlackDay + doneButton.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) doneButton.isEnabled = true } } @@ -106,8 +106,8 @@ extension AddNewCategoryViewController: UITextFieldDelegate { doneButton.isEnabled = false return newText != " " } else { - doneButton.backgroundColor = .ypBlackDay - doneButton.setTitleColor(.white, for: .normal) + doneButton.backgroundColor = ColoursTheme.whiteDayBlackDay + doneButton.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) doneButton.isEnabled = true } return true @@ -124,7 +124,7 @@ extension AddNewCategoryViewController: UITextFieldDelegate { // MARK: - SetupViews extension AddNewCategoryViewController { private func setupViews() { - view.backgroundColor = .white + view.backgroundColor = ColoursTheme.blackDayWhiteDay view.addSubviews(textField, doneButton) NSLayoutConstraint.activate([ diff --git a/Tracker/Controllers/NewTrackers/Categories/CategoriesViewController.swift b/Tracker/Controllers/NewTrackers/Categories/CategoriesViewController.swift index 580b5b3..fadc49f 100644 --- a/Tracker/Controllers/NewTrackers/Categories/CategoriesViewController.swift +++ b/Tracker/Controllers/NewTrackers/Categories/CategoriesViewController.swift @@ -16,7 +16,7 @@ final class CategoriesViewController: UIViewController { style: .insetGrouped) tableView.register(UITableViewCell.self, forCellReuseIdentifier: "categoryCell") tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) - tableView.backgroundColor = .white + tableView.backgroundColor = ColoursTheme.blackDayWhiteDay tableView.dataSource = self tableView.delegate = self return tableView @@ -24,9 +24,10 @@ final class CategoriesViewController: UIViewController { private lazy var addCategoryButton: UIButton = { let button = UIButton() - button.setTitle("Добавить категорию", for: .normal) + button.setTitle(LocalizableKeys.addCategoryButton, for: .normal) button.setTitleColor(.white, for: .normal) - button.backgroundColor = .ypBlackDay + button.backgroundColor = ColoursTheme.whiteDayBlackDay + button.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) button.layer.cornerRadius = 16 button.addTarget(self, action: #selector(addNewCategory), for: .touchUpInside) return button @@ -58,7 +59,7 @@ final class CategoriesViewController: UIViewController { private func goToAddNewCategory(isEdit: Bool = false, text: String? = nil) { guard let addNewCategoryViewController = viewModel?.addNewCategory(isEdit: isEdit, text: text) else { return } let navigationController = UINavigationController(rootViewController: addNewCategoryViewController) - navigationController.navigationBar.barTintColor = .ypWhiteDay + navigationController.navigationBar.barTintColor = ColoursTheme.blackDayWhiteDay navigationController.navigationBar.shadowImage = UIImage() present(navigationController, animated: true) } @@ -66,13 +67,16 @@ final class CategoriesViewController: UIViewController { private func updateTableView() { guard let categories = viewModel?.categories.isEmpty else { return } if categories { - guard let image = UIImage(named: "errorImage") else { return } - let emptyView = EmptyView(frame: CGRect( - x: 0, - y: 0, - width: view.bounds.width, - height: view.bounds.height), image: image, - text: "Привычки и события можно\nобъединить по смыслу") + let emptyView = EmptyView( + frame: CGRect( + x: 0, + y: 0, + width: view.bounds.width, + height: view.bounds.height + ), + image: ImageAssets.trackerErrorImage, + text: LocalizableKeys.trackerViewStubCategory + ) tableView.backgroundView = emptyView } else { tableView.backgroundView = nil @@ -101,7 +105,6 @@ extension CategoriesViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath) cell.textLabel?.text = viewModel?.categories[indexPath.row].title - cell.backgroundColor = .ypBackgroundDay cell.accessoryType = indexPath == viewModel?.editingIndexPath ? .checkmark : .none return cell } @@ -134,13 +137,13 @@ extension CategoriesViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { viewModel?.editingIndexPath = indexPath - let editAction = UIAction(title: "Редактировать") { [weak self] _ in + let editAction = UIAction(title: LocalizableKeys.contextMenuCategoryEdit) { [weak self] _ in guard let self else { return } if let editText = viewModel?.changeCategoryText() { self.goToAddNewCategory(isEdit: true, text: editText) } } - let deleteAction = UIAction(title: "Удалить", attributes: .destructive) { [weak self] _ in + let deleteAction = UIAction(title: LocalizableKeys.contextMenuCategoryDelete, attributes: .destructive) { [weak self] _ in guard let self = self else { return } self.viewModel?.deleteCategories(indexPath: indexPath) } @@ -154,7 +157,7 @@ extension CategoriesViewController: UITableViewDelegate { // MARK: - SetupViews private extension CategoriesViewController { func setupViews() { - view.backgroundColor = .white + view.backgroundColor = ColoursTheme.blackDayWhiteDay view.addSubviews(tableView, addCategoryButton) NSLayoutConstraint.activate([ diff --git a/Tracker/Controllers/NewTrackers/ColorsCollectionViewCell.swift b/Tracker/Controllers/NewTrackers/ColorsCollectionViewCell.swift index 2be6f19..73b002a 100644 --- a/Tracker/Controllers/NewTrackers/ColorsCollectionViewCell.swift +++ b/Tracker/Controllers/NewTrackers/ColorsCollectionViewCell.swift @@ -23,6 +23,17 @@ final class ColorsCollectionViewCell: UICollectionViewCell { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func configure(isSelected: Bool, for colors: [UIColor]? = nil, at indexPath: IndexPath) { + if isSelected { + layer.borderWidth = 3 + layer.cornerRadius = 8 + layer.borderColor = UIColor.colorSelection[indexPath.row].withAlphaComponent(0.3).cgColor + } else { + layer.borderWidth = 0 + layer.borderColor = .none + } + } } //MARK: - SetupViews diff --git a/Tracker/Controllers/NewTrackers/NewTrackerViewController.swift b/Tracker/Controllers/NewTrackers/NewTrackerViewController.swift index 1a76088..0196b24 100644 --- a/Tracker/Controllers/NewTrackers/NewTrackerViewController.swift +++ b/Tracker/Controllers/NewTrackers/NewTrackerViewController.swift @@ -15,7 +15,7 @@ protocol NewTrackerViewControllerProtocol: AnyObject { final class NewTrackerViewController: UIViewController { - private let namesButton: [String] = ["Категория", "Расписание"] + private let trackerRecordStore = TrackerRecordStore() private let emojies = [ "🙂", "😻", "🌺", "🐶", "❤️", "😱", @@ -27,20 +27,20 @@ final class NewTrackerViewController: UIViewController { private let scrollView: UIScrollView = { let scrollView = UIScrollView() - scrollView.backgroundColor = .white + scrollView.backgroundColor = ColoursTheme.blackDayWhiteDay scrollView.isScrollEnabled = true return scrollView }() private let contentView: UIView = { let contentView = UIView() - contentView.backgroundColor = .white + contentView.backgroundColor = ColoursTheme.blackDayWhiteDay return contentView }() private let limitLabel: UILabel = { let label = UILabel() - label.text = "Ограничение 38 символов" + label.text = LocalizableKeys.limitLabel label.font = UIFont.systemFont(ofSize: 17) label.textColor = .yp_Red label.numberOfLines = 1 @@ -48,15 +48,24 @@ final class NewTrackerViewController: UIViewController { label.isHidden = true return label }() - + var onTrackerCreated: ((_ tracker: Tracker, _ titleCategory: String?) -> Void)? + var daysCount: Int? + var date: Date? + var dayButtonToggled: Bool? + var currentTracker: Tracker? + var editCategory: String? + lazy var isEdit: Bool = false + + private lazy var namesButton: [String] = [LocalizableKeys.newTrackerCategory, LocalizableKeys.newTrackerTimetable] + private lazy var textField: UITextField = { let textField = UITextField() textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: 30)) - textField.placeholder = "Введите название трекера" + textField.placeholder = LocalizableKeys.newTrackerTextField textField.leftViewMode = .always - textField.backgroundColor = .ypBackgroundDay + textField.backgroundColor = ColoursTheme.backgroundNightDay textField.layer.cornerRadius = 10 textField.clearButtonMode = .whileEditing textField.clipsToBounds = true @@ -70,7 +79,7 @@ final class NewTrackerViewController: UIViewController { style: .insetGrouped) tableView.register(UITableViewCell.self, forCellReuseIdentifier: SubtitledTableViewCell.identifier) tableView.register(SubtitledTableViewCell.self, forCellReuseIdentifier: SubtitledTableViewCell.identifier) - tableView.backgroundColor = .white + tableView.backgroundColor = ColoursTheme.blackDayWhiteDay tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 75 tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) @@ -90,7 +99,7 @@ final class NewTrackerViewController: UIViewController { collectionView.register(HeaderViewCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header") - collectionView.backgroundColor = .white + collectionView.backgroundColor = ColoursTheme.blackDayWhiteDay collectionView.allowsMultipleSelection = true collectionView.isScrollEnabled = false collectionView.dataSource = self @@ -110,11 +119,11 @@ final class NewTrackerViewController: UIViewController { private lazy var cancelButton: UIButton = { let button = UIButton() - button.setTitle("Отменить", for: .normal) + button.setTitle(LocalizableKeys.newTrackerCancelButton, for: .normal) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.yp_Red.cgColor button.setTitleColor(.yp_Red, for: .normal) - button.backgroundColor = .white + button.backgroundColor = ColoursTheme.blackDayWhiteDay button.layer.cornerRadius = 16 button.layer.masksToBounds = true button.addTarget(self, action: #selector(exitView), for: .touchUpInside) @@ -123,8 +132,7 @@ final class NewTrackerViewController: UIViewController { private lazy var createButton: UIButton = { let button = UIButton() - button.setTitle("Создать", for: .normal) - button.tintColor = .white + button.setTitle(LocalizableKeys.newTrackerCreateButton, for: .normal) button.backgroundColor = .yp_Gray button.layer.cornerRadius = 16 button.layer.masksToBounds = true @@ -143,6 +151,16 @@ final class NewTrackerViewController: UIViewController { return stackView }() + private lazy var daysButton: UIButton = { + let button = UIButton() + button.setTitleColor(ColoursTheme.whiteDayBlackDay, for: .normal) + button.backgroundColor = .clear + button.titleLabel?.font = UIFont.systemFont(ofSize: 32, weight: .bold) + button.addTarget(self, action: #selector(toogleDaysButton), for: .touchUpInside) + return button + }() + + private var daysbuttonConstraintToTextField = NSLayoutConstraint() private var collectionViewHeightContraint: NSLayoutConstraint! private var detailTextCategory: String? private var detailTextDays: [String]? @@ -156,7 +174,45 @@ final class NewTrackerViewController: UIViewController { setupView() setupConstraints() updateCollectionViewHeight() - + editTracker() + } + + private func editTracker() { + guard let currentTracker = currentTracker else { return } + if isEdit { + isEnabledDictionary["text"] = true + daysbuttonConstraintToTextField.constant = -40 + + updateDaysButton() + + textField.text = currentTracker.name + detailTextCategory = editCategory + detailTextDays = currentTracker.timetable + + if let emojiIndex = emojies.firstIndex(of: currentTracker.emojie) { + let emojieIndexPath = IndexPath(row: emojiIndex, section: 0) + collectionView.selectItem(at: emojieIndexPath, animated: false, scrollPosition: []) + collectionView(collectionView, didSelectItemAt: emojieIndexPath) + } + + if let colorIndex = colors.firstIndex(where: { UIColor.areAlmostEqual(color1: $0, + color2: currentTracker.color) }) { + let colorIndexPath = IndexPath(row: colorIndex, section: 1) + collectionView.selectItem(at: colorIndexPath, animated: false, scrollPosition: []) + collectionView(collectionView, didSelectItemAt: colorIndexPath) + } + + createButton.setTitle(NSLocalizedString("Save", comment: ""), for: .normal) + } else { + daysbuttonConstraintToTextField.constant = 0 + } + } + + private func updateDaysButton() { + guard let days = daysCount else { return } + let daysString = String.localizedStringWithFormat( + NSLocalizedString("numberOfTasks", comment: "Number of remaining tasks"), days) + daysButton.setTitle(daysString, for: .normal) } private func createButtonIsEnabled() { @@ -169,7 +225,8 @@ final class NewTrackerViewController: UIViewController { createButton.backgroundColor = .yp_Gray return } createButton.isEnabled = true - createButton.backgroundColor = .ypBlackDay + createButton.backgroundColor = ColoursTheme.whiteDayBlackDay + createButton.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) } private func dismissKeyboard() { @@ -177,26 +234,48 @@ final class NewTrackerViewController: UIViewController { } // MARK: Selectors + @objc + private func toogleDaysButton() { + guard let daysTracker = daysCount, + let date = date else { return } + dayButtonToggled?.toggle() + + if dayButtonToggled ?? false { + daysCount = daysTracker + 1 + try? trackerRecordStore.createTrackerRecord(from: TrackerRecord(id: currentTracker?.id ?? UUID(), date: date)) + } else { + daysCount = daysTracker - 1 + try? trackerRecordStore.deleteTrackerRecord(trackerRecord: TrackerRecord(id: currentTracker?.id ?? UUID(), date: date)) + } + updateDaysButton() + } + @objc private func createNewTracker() { + AnalyticsService.clickCreateTrackerReport() + guard let text = textField.text, let category = detailTextCategory else { return } guard let selectedEmojieIndexPath = isSelectedEmoji, let selectedColorIndexPath = isSelectedColor else { return } let emojie = emojies[selectedEmojieIndexPath.row] let color = colors[selectedColorIndexPath.row] - if UserDefaultsManager.showIrregularEvent ?? true { - let newTracker = Tracker(id: UUID(), name: text, color: color, emojie: emojie, timetable: nil) - onTrackerCreated?(newTracker, category) + let timetabel: [String]? = UserDefaultsManager.showIrregularEvent ?? true ? nil : detailTextDays + + if isEdit { + guard let currentTracker = currentTracker else { return } + let updatedTracker = Tracker(id: currentTracker.id, name: text, color: color, emojie: emojie, timetable: timetabel) + onTrackerCreated?(updatedTracker, category) } else { - guard let timetabel = detailTextDays else { return } let newTracker = Tracker(id: UUID(), name: text, color: color, emojie: emojie, timetable: timetabel) onTrackerCreated?(newTracker, category) } + self.view.window?.rootViewController?.dismiss(animated: true) } @objc private func exitView() { + AnalyticsService.clickExitViewNewTracker() dismiss(animated: true) } } @@ -245,7 +324,7 @@ extension NewTrackerViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SubtitledTableViewCell.identifier, for: indexPath) cell.textLabel?.text = namesButton[indexPath.row] - cell.backgroundColor = .ypBackgroundDay + cell.backgroundColor = ColoursTheme.backgroundNightDay cell.accessoryType = .disclosureIndicator guard let detailTextLabel = cell.detailTextLabel else { return cell } @@ -261,11 +340,11 @@ extension NewTrackerViewController: UITableViewDataSource { case 1: if let days = detailTextDays { if days.count == 7 { - detailTextLabel.text = "Каждый день" + detailTextLabel.text = LocalizableKeys.detailTextLabelEveryDay isEnabledDictionary["timetable"] = true createButtonIsEnabled() } else { - let orderedDays = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] + let orderedDays = LocalizableKeys.detailTextLabelOrderedDays.components(separatedBy: " ") let sortedDays = days.sorted { first, second in if let firstIndex = orderedDays.firstIndex(of: first), let secondIndex = orderedDays.firstIndex(of: second) { return firstIndex < secondIndex @@ -298,12 +377,12 @@ extension NewTrackerViewController: UITableViewDelegate { dismissKeyboard() switch indexPath.row { case 0: let viewController = CategoriesViewController() - chooseCategoryVC(viewController, "Категория") + chooseCategoryVC(viewController, LocalizableKeys.newTrackerCategory) let categoryViewModel = CategoriesViewModel() viewController.initialize(viewModel: categoryViewModel) categoryViewModel.delegate = self case 1: let viewController = TimetableViewController() - chooseCategoryVC(viewController, "Расписание") + chooseCategoryVC(viewController, LocalizableKeys.newTrackerTimetable) viewController.delegate = self default: break } @@ -313,7 +392,7 @@ extension NewTrackerViewController: UITableViewDelegate { let viewController = viewController viewController.title = title let navigationController = UINavigationController(rootViewController: viewController) - navigationController.navigationBar.barTintColor = .ypWhiteDay + navigationController.navigationBar.barTintColor = ColoursTheme.blackDayWhiteDay navigationController.navigationBar.shadowImage = UIImage() present(navigationController, animated: true) } @@ -337,7 +416,8 @@ extension NewTrackerViewController: UICollectionViewDataSource { ) as? EmojiCollectionViewCell else { return UICollectionViewCell()} cell.titleLabel.text = emojies[indexPath.row] - cell.backgroundColor = cell.isSelected ? .ypBackgroundDay : .clear + cell.backgroundColor = cell.isSelected ? .yp_LightGray : .clear + cell.layer.cornerRadius = 16 return cell } else if indexPath.section == 1 { @@ -348,6 +428,7 @@ extension NewTrackerViewController: UICollectionViewDataSource { cell.sizeToFit() cell.colorView.backgroundColor = colors[indexPath.row] + cell.configure(isSelected: cell.isSelected, for: colors, at: indexPath) return cell } @@ -424,11 +505,13 @@ extension NewTrackerViewController: UICollectionViewDelegate & UICollectionViewD if indexPath.section == 0 { guard let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: id, for: indexPath) as? HeaderViewCell else { return UICollectionReusableView()} - view.titleLabel.text = "Emoji" + view.titleLabel.text = LocalizableKeys.newTrackerEmoji + view.titleLabel.textColor = ColoursTheme.whiteDayBlackDay return view } else { guard let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: id, for: indexPath) as? HeaderViewCell else { return UICollectionReusableView()} - view.titleLabel.text = "Цвет" + view.titleLabel.text = LocalizableKeys.newTrackerColor + view.titleLabel.textColor = ColoursTheme.whiteDayBlackDay return view } } @@ -474,12 +557,13 @@ extension NewTrackerViewController: UIScrollViewDelegate { private extension NewTrackerViewController { func setupView() { _ = self.hideKeyboardWhenClicked - view.backgroundColor = .white + view.backgroundColor = ColoursTheme.blackDayWhiteDay view.addSubviews(scrollView) scrollView.addSubviews(contentView) scrollView.delegate = self - contentView.addSubviews(textFieldStackView, tableView, collectionView, buttonStackView) - + + daysbuttonConstraintToTextField = daysButton.bottomAnchor.constraint(equalTo: textFieldStackView.topAnchor, constant: 0) + contentView.addSubviews(daysButton, textFieldStackView, tableView, collectionView, buttonStackView) collectionViewHeightContraint = collectionView.heightAnchor.constraint(equalToConstant: 0) } @@ -496,12 +580,14 @@ private extension NewTrackerViewController { contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), - textFieldStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + daysButton.topAnchor.constraint(equalTo: contentView.topAnchor), + daysButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + daysbuttonConstraintToTextField, + textFieldStackView.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor, constant: 20), textFieldStackView.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor, constant: -20), textField.heightAnchor.constraint(equalToConstant: 75), - textField.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 24), textField.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor, constant: 16), textField.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor, constant: -16), diff --git a/Tracker/Controllers/NewTrackers/Timetable/TimetableCell.swift b/Tracker/Controllers/NewTrackers/Timetable/TimetableCell.swift index 2408322..64cc96c 100644 --- a/Tracker/Controllers/NewTrackers/Timetable/TimetableCell.swift +++ b/Tracker/Controllers/NewTrackers/Timetable/TimetableCell.swift @@ -35,24 +35,10 @@ final class TimetableCell: UITableViewCell { @objc private func switchTapped(_ sender: UISwitch) { if let daysOfWeek = textLabel?.text { - let shortDay = shortDays(for: daysOfWeek) + let shortDay = WeekDays[daysOfWeek] delegateCell?.didToogleSwitch(for: shortDay, isOn: sender.isOn) } } - - // MARK: Shorten the Days - private func shortDays(for day: String) -> String { - switch day { - case "Понедельник": return "Пн" - case "Вторник": return "Вт" - case "Среда": return "Ср" - case "Четверг": return "Чт" - case "Пятница": return "Пт" - case "Суббота": return "Сб" - case "Воскресенье": return "Вс" - default: return "" - } - } } private extension TimetableCell { diff --git a/Tracker/Controllers/NewTrackers/Timetable/TimetableViewController.swift b/Tracker/Controllers/NewTrackers/Timetable/TimetableViewController.swift index 9d7d028..3fe5d1a 100644 --- a/Tracker/Controllers/NewTrackers/Timetable/TimetableViewController.swift +++ b/Tracker/Controllers/NewTrackers/Timetable/TimetableViewController.swift @@ -7,17 +7,6 @@ import UIKit -private enum WeekDays: String, CaseIterable { - case monday = "Понедельник" - case tuesday = "Вторник" - case wednesday = "Среда" - case thursday = "Четверг" - case friday = "Пятница" - case saturday = "Суббота" - case sunday = "Воскресенье" - static let numberOfDays = WeekDays.allCases.count -} - final class TimetableViewController: UIViewController { weak var delegate: NewTrackerViewControllerProtocol? @@ -30,7 +19,7 @@ final class TimetableViewController: UIViewController { tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) tableView.rowHeight = 75 tableView.isScrollEnabled = true - tableView.backgroundColor = .white + tableView.backgroundColor = ColoursTheme.blackDayWhiteDay tableView.allowsSelection = false tableView.dataSource = self return tableView @@ -38,9 +27,9 @@ final class TimetableViewController: UIViewController { private lazy var doneButton: UIButton = { let button = UIButton() - button.setTitle("Готово", for: .normal) - button.setTitleColor(.white, for: .normal) - button.backgroundColor = .ypBlackDay + button.setTitle(LocalizableKeys.addNewCategoryDoneButton, for: .normal) + button.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) + button.backgroundColor = ColoursTheme.whiteDayBlackDay button.layer.cornerRadius = 16 button.addTarget(self, action: #selector(saveWeekDays), for: .touchUpInside) return button @@ -68,11 +57,11 @@ extension TimetableViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: TimetableCell.identifier, for: indexPath) as? TimetableCell else { return UITableViewCell() } - cell.textLabel?.text = WeekDays.allCases[indexPath.row].rawValue - cell.backgroundColor = .ypBackgroundDay + cell.textLabel?.text = WeekDays.localize(WeekDays.allCases[indexPath.row])() + cell.backgroundColor = ColoursTheme.backgroundNightDay cell.delegateCell = self timetableArray.forEach { day in - if day == "".shortDaysFromLong(for: (cell.textLabel?.text) ?? "") { + if day == WeekDays[cell.textLabel?.text ?? ""] { cell.switchDay.isOn = true didToogleSwitch(for: day, isOn: true) } @@ -91,7 +80,7 @@ extension TimetableViewController: TimetableCellDelegate { private extension TimetableViewController { func setupViews() { - view.backgroundColor = .white + view.backgroundColor = ColoursTheme.blackDayWhiteDay view.addSubviews(tableView, doneButton) } diff --git a/Tracker/Controllers/NewTrackers/TrackersTypeViewController.swift b/Tracker/Controllers/NewTrackers/TrackersTypeViewController.swift index ab348fc..6829722 100644 --- a/Tracker/Controllers/NewTrackers/TrackersTypeViewController.swift +++ b/Tracker/Controllers/NewTrackers/TrackersTypeViewController.swift @@ -8,17 +8,16 @@ import UIKit final class TrackersTypeViewController: UIViewController { - private lazy var habitButton: UIButton = { let button = UIButton() - button.setTitle("Привычка", for: .normal) + button.setTitle(LocalizableKeys.chooseTrackerButton, for: .normal) button.addTarget(self, action: #selector(addNewHabit), for: .touchUpInside) return button }() private lazy var irregularEventButton: UIButton = { let button = UIButton() - button.setTitle("Нерегулярное событие", for: .normal) + button.setTitle(LocalizableKeys.showIrregularEventButton, for: .normal) button.addTarget(self, action: #selector(addIreggularEvent), for: .touchUpInside) return button }() @@ -40,7 +39,7 @@ final class TrackersTypeViewController: UIViewController { self.delegate?.createTracker(tracker, titleCategory: titleCotegory) } let navigationController = UINavigationController(rootViewController: newTrackerVC) - navigationController.navigationBar.barTintColor = .ypWhiteDay + navigationController.navigationBar.barTintColor = ColoursTheme.blackDayWhiteDay navigationController.navigationBar.shadowImage = UIImage() present(navigationController, animated: true) } @@ -48,25 +47,27 @@ final class TrackersTypeViewController: UIViewController { // MARK: Selectors @objc private func addNewHabit() { - showTrackers(false, "Новая привычка") + AnalyticsService.clickHabitReport() + showTrackers(false, LocalizableKeys.showTrackerButton) UserDefaultsManager.timetableArray = [] } @objc private func addIreggularEvent() { - showTrackers(true, "Новое нерегулярное событие") + AnalyticsService.clickIreggularEventReport() + showTrackers(true, LocalizableKeys.showIrregularEventButton) } } // MARK: - SetupViews private extension TrackersTypeViewController { func setupViews() { - view.backgroundColor = .white + view.backgroundColor = ColoursTheme.blackDayWhiteDay view.addSubviews(habitButton, irregularEventButton) [habitButton, irregularEventButton].forEach { $0.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) - $0.backgroundColor = .ypBlackDay - $0.tintColor = .white + $0.backgroundColor = ColoursTheme.whiteDayBlackDay + $0.setTitleColor(ColoursTheme.blackDayWhiteDay, for: .normal) $0.layer.cornerRadius = 16 $0.layer.masksToBounds = true } diff --git a/Tracker/Controllers/Onboarding/OnboardingFirst.swift b/Tracker/Controllers/Onboarding/OnboardingFirst.swift index 97acf32..22e19cf 100644 --- a/Tracker/Controllers/Onboarding/OnboardingFirst.swift +++ b/Tracker/Controllers/Onboarding/OnboardingFirst.swift @@ -10,12 +10,12 @@ import UIKit final class OnboardingFirst: UIViewController { private let backgroundImage: UIImageView = { let imageView = UIImageView() - imageView.image = UIImage(named: "firstBackground") + imageView.image = ImageAssets.onboardingFirstBackground return imageView }() private let labelOnboarding: UILabel = { let label = UILabel() - label.text = "Отслеживайте только то, что хотите" + label.text = LocalizableKeys.labelOnboardingFirst label.font = UIFont.systemFont(ofSize: 32, weight: .bold) label.textColor = .ypBlackDay label.numberOfLines = 2 @@ -25,7 +25,7 @@ final class OnboardingFirst: UIViewController { private lazy var tapButton: UIButton = { let button = UIButton() button.backgroundColor = .ypBlackDay - button.setTitle("Вот это технологии!", for: .normal) + button.setTitle(LocalizableKeys.buttonOnboarding, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) button.setTitleColor(.ypWhiteDay, for: .normal) button.layer.cornerRadius = 16 diff --git a/Tracker/Controllers/Onboarding/OnboardingSecond.swift b/Tracker/Controllers/Onboarding/OnboardingSecond.swift index 47af6a0..78ecc06 100644 --- a/Tracker/Controllers/Onboarding/OnboardingSecond.swift +++ b/Tracker/Controllers/Onboarding/OnboardingSecond.swift @@ -10,12 +10,12 @@ import UIKit final class OnboardingSecond: UIViewController { private let backgroundImage: UIImageView = { let imageView = UIImageView() - imageView.image = UIImage(named: "secondBackground") + imageView.image = ImageAssets.onboardingSecondBackground return imageView }() private let labelOnboarding: UILabel = { let label = UILabel() - label.text = "Даже если это не литры воды и йога" + label.text = LocalizableKeys.labelOnboardingSecond label.font = UIFont.systemFont(ofSize: 32, weight: .bold) label.textColor = .ypBlackDay label.numberOfLines = 2 @@ -25,7 +25,7 @@ final class OnboardingSecond: UIViewController { private lazy var tapButton: UIButton = { let button = UIButton() button.backgroundColor = .ypBlackDay - button.setTitle("Вот это технологии!", for: .normal) + button.setTitle(LocalizableKeys.buttonOnboarding, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) button.setTitleColor(.ypWhiteDay, for: .normal) button.layer.cornerRadius = 16 diff --git a/Tracker/Controllers/Onboarding/OnboardingViewController.swift b/Tracker/Controllers/Onboarding/OnboardingViewController.swift index b8fbefa..27a8f03 100644 --- a/Tracker/Controllers/Onboarding/OnboardingViewController.swift +++ b/Tracker/Controllers/Onboarding/OnboardingViewController.swift @@ -86,7 +86,6 @@ final class OnboardingViewController: UIPageViewController { } } } - } // MARK: - UIPageViewControllerDataSource diff --git a/Tracker/Controllers/Statistic/StatisticViewController.swift b/Tracker/Controllers/Statistic/StatisticViewController.swift index fba784b..d4c4b82 100644 --- a/Tracker/Controllers/Statistic/StatisticViewController.swift +++ b/Tracker/Controllers/Statistic/StatisticViewController.swift @@ -7,52 +7,124 @@ import UIKit -final class StatisticViewController: UIViewController { +final class StatisticsViewController: UIViewController { - private let errorImageView: UIImageView = { - let image = UIImage(named: "statisticErrorImage") - let imageView = UIImageView(image: image) - return imageView + private lazy var trackersCompletedView: UIView = { + let view = UIView() + view.backgroundColor = ColoursTheme.blackDayWhiteDay + view.layer.cornerRadius = 16 + countTrackersLabel.text = viewModel?.completedTrackers.count.description + trackersCompletedLabel.text = viewModel?.trackersCompletedText() + view.addSubviews(countTrackersLabel, trackersCompletedLabel) + return view }() - - private let errorLabel: UILabel = { + private lazy var countTrackersLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 34, weight: .bold) + label.textColor = ColoursTheme.whiteDayBlackDay + return label + }() + private lazy var trackersCompletedLabel: UILabel = { let label = UILabel() - label.text = "Анализировать пока нечего" - label.textColor = .ypBlackDay label.font = UIFont.systemFont(ofSize: 12, weight: .medium) - label.numberOfLines = 1 + label.textColor = ColoursTheme.whiteDayBlackDay return label }() + private lazy var gradientLayer: CAGradientLayer = { + let gradientLayer = CAGradientLayer() + gradientLayer.colors = [ + UIColor.gradientRed.cgColor, + UIColor.gradientGreen.cgColor, + UIColor.gradientBlue.cgColor + ] + gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5) + gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5) + return gradientLayer + }() + + // MARK: ViewModel + var viewModel: StatisticsViewModel? + + // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() - setupViews() - setupAllConstraints() + showBackgroundView() + } + func initialize(viewModel: StatisticsViewModel) { + self.viewModel = viewModel + bind() } - private func setupNavigationBar() { - navigationItem.title = "Статистика" - navigationController?.navigationBar.prefersLargeTitles = true + // MARK: Binding + private func bind() { + viewModel?.$completedTrackers.bind(action: { [weak self] _ in + self?.showBackgroundView() + }) + } + + // MARK: Update completed trackers + private func updateCompletedTrackers() { + countTrackersLabel.text = viewModel?.completedTrackers.count.description + trackersCompletedLabel.text = viewModel?.trackersCompletedText() + } + + private func showBackgroundView() { + guard let completedTrackers = viewModel?.completedTrackers else { return } + if completedTrackers.isEmpty { + emptyView(ImageAssets.statisticErrorImage, LocalizableKeys.statisticsErrorLabel) + } else { + emptyView(nil, "") + setupViewsConstraints() + updateCompletedTrackers() + } + } + private func emptyView(_ image: UIImage?, _ text: String) { + let emptyViewStub = EmptyView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height), image: image, text: text) + view = emptyViewStub + } + + // MARK: GradientLayer methods + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + setupGradientBorder(to: trackersCompletedView, gradientLayer: gradientLayer) + } + private func setupGradientBorder(to view: UIView, cornerRadius: CGFloat = 16, borderWidth: CGFloat = 2.0, gradientLayer: CAGradientLayer) { + gradientLayer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height) + let shapeLayer = CAShapeLayer() + let path = UIBezierPath(roundedRect: view.bounds, cornerRadius: cornerRadius) + shapeLayer.path = path.cgPath + shapeLayer.lineWidth = borderWidth + shapeLayer.strokeColor = UIColor.black.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + gradientLayer.mask = shapeLayer + view.layer.addSublayer(gradientLayer) } } -private extension StatisticViewController { - func setupViews() { - view.backgroundColor = .ypWhiteDay - view.addSubviews(errorImageView, errorLabel) +// MARK: - Setup views and constraints +private extension StatisticsViewController { + func setupNavigationBar() { + navigationItem.title = LocalizableKeys.statisticsTabBarItem + navigationController?.navigationBar.prefersLargeTitles = true } - - func setupAllConstraints() { + func setupViewsConstraints() { + view.backgroundColor = ColoursTheme.blackDayWhiteDay + view.addSubviews(trackersCompletedView) + trackersCompletedView.addSubviews(countTrackersLabel, trackersCompletedLabel) + NSLayoutConstraint.activate([ - errorImageView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), - errorImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor), - errorImageView.heightAnchor.constraint(equalToConstant: 80), - errorImageView.widthAnchor.constraint(equalToConstant: 80), + trackersCompletedView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 70), + trackersCompletedView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + trackersCompletedView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + trackersCompletedView.heightAnchor.constraint(equalToConstant: view.bounds.width * 90/343), + + countTrackersLabel.topAnchor.constraint(equalTo: trackersCompletedView.topAnchor, constant: 15), + countTrackersLabel.leadingAnchor.constraint(equalTo: trackersCompletedView.leadingAnchor, constant: 12), - errorLabel.topAnchor.constraint(equalTo: errorImageView.bottomAnchor, constant: 8), - errorLabel.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor), - errorLabel.heightAnchor.constraint(equalToConstant: 18), + trackersCompletedLabel.topAnchor.constraint(equalTo: countTrackersLabel.bottomAnchor, constant: 15), + trackersCompletedLabel.leadingAnchor.constraint(equalTo: trackersCompletedView.leadingAnchor, constant: 12) ]) } } diff --git a/Tracker/Controllers/Statistic/StatisticsViewModel.swift b/Tracker/Controllers/Statistic/StatisticsViewModel.swift new file mode 100644 index 0000000..3439d6c --- /dev/null +++ b/Tracker/Controllers/Statistic/StatisticsViewModel.swift @@ -0,0 +1,45 @@ +// +// StatisticsViewModel.swift +// Tracker +// +// Created by Руслан on 18.10.2023. +// + +import Foundation + +protocol StatisticViewControllerProtocol: AnyObject { + var completedTrackers: [TrackerRecord] { get set } +} + +final class StatisticsViewModel: StatisticViewControllerProtocol { + // MARK: Public Properties + @Observable var completedTrackers: [TrackerRecord] = [] + + // MARK: Private properties + private let trackerRecordStore = TrackerRecordStore() + private let statisticsKey = "statisticsTrackersCompleted" + + // MARK: Initialisation + init() { + completedTrackers = trackerRecordStore.trackerRecords + } + + // MARK: Methods + func trackersCompletedText() -> String { + String.localizedStringWithFormat( + NSLocalizedString(statisticsKey, comment: "Localization for text"), completedTrackers.count) + } + func bestPeriod() -> String { + let ids = completedTrackers.map { $0.id } + var countDict = [UUID: Int]() + var maxCount = 0 + ids.forEach { id in + countDict[id] == nil ? (countDict[id] = 1) : (countDict[id]! += 1) + + } + for (_, value) in countDict where value > maxCount { + maxCount = value + } + return maxCount.description + } +} diff --git a/Tracker/Controllers/TabBarController/TabBarController.swift b/Tracker/Controllers/TabBarController/TabBarController.swift index f55eb3c..e992668 100644 --- a/Tracker/Controllers/TabBarController/TabBarController.swift +++ b/Tracker/Controllers/TabBarController/TabBarController.swift @@ -8,19 +8,24 @@ import UIKit final class TabBarController: UITabBarController { - - let mainTrackerViewController = UINavigationController(rootViewController: TrackerViewController()) - let statisticViewController = UINavigationController(rootViewController: StatisticViewController()) - override func viewDidLoad() { super.viewDidLoad() tabBar.tintColor = .yp_Blue - generateTabBar() + + let trackerViewController = TrackerViewController() + let statisticViewController = StatisticsViewController() + let statisticViewModel = StatisticsViewModel() + statisticViewController.initialize(viewModel: statisticViewModel) + trackerViewController.delegateStatistic = statisticViewModel + let mainViewController = UINavigationController(rootViewController: trackerViewController) + let secondViewController = UINavigationController(rootViewController: statisticViewController) + + generateTabBar(mainViewController, secondViewController) if #available(iOS 13.0, *) { let tabBarAppearance: UITabBarAppearance = UITabBarAppearance() tabBarAppearance.configureWithDefaultBackground() - tabBarAppearance.backgroundColor = .ypWhiteDay + tabBarAppearance.backgroundColor = .clear UITabBar.appearance().standardAppearance = tabBarAppearance if #available(iOS 15.0, *) { UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance @@ -28,19 +33,19 @@ final class TabBarController: UITabBarController { } } - private func generateTabBar() { - mainTrackerViewController.tabBarItem = UITabBarItem( - title: "Трекеры", - image: UIImage(named: "trackersIcon"), + private func generateTabBar(_ main: UINavigationController, _ second: UINavigationController) { + main.tabBarItem = UITabBarItem( + title: LocalizableKeys.trackersTabBarItem, + image: ImageAssets.tabBarTrackersIcon, selectedImage: nil ) - statisticViewController.tabBarItem = UITabBarItem( - title: "Статистика", - image: UIImage(named: "statisticIcon"), + second.tabBarItem = UITabBarItem( + title: LocalizableKeys.statisticsTabBarItem, + image: ImageAssets.tabBarStatisticIcon, selectedImage: nil ) - self.viewControllers = [mainTrackerViewController, statisticViewController] + self.viewControllers = [main, second] } } diff --git a/Tracker/Controllers/Tracker/EmptyView.swift b/Tracker/Controllers/Tracker/EmptyView.swift index 3575dff..fa91c83 100644 --- a/Tracker/Controllers/Tracker/EmptyView.swift +++ b/Tracker/Controllers/Tracker/EmptyView.swift @@ -9,7 +9,7 @@ import UIKit final class EmptyView: UIView { private lazy var placeholderImage: UIImageView = { - let image = UIImage(named: "errorImage") + let image = UIImage() let imageView = UIImageView(image: image) return imageView }() @@ -37,7 +37,7 @@ final class EmptyView: UIView { // MARK: - Layouts private extension EmptyView { func setupViews() { - self.backgroundColor = .white + self.backgroundColor = ColoursTheme.blackDayWhiteDay addSubviews(placeholderImage, textLabel) diff --git a/Tracker/Controllers/Tracker/HeaderViewCell.swift b/Tracker/Controllers/Tracker/HeaderViewCell.swift index 7ae830b..6a3d5a4 100644 --- a/Tracker/Controllers/Tracker/HeaderViewCell.swift +++ b/Tracker/Controllers/Tracker/HeaderViewCell.swift @@ -11,7 +11,7 @@ final class HeaderViewCell: UICollectionReusableView { let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 19, weight: .bold) - label.textColor = .black + label.textColor = ColoursTheme.whiteDayBlackDay return label }() diff --git a/Tracker/Controllers/Tracker/TrackerCell.swift b/Tracker/Controllers/Tracker/TrackerCell.swift index 7a96dc3..31e073d 100644 --- a/Tracker/Controllers/Tracker/TrackerCell.swift +++ b/Tracker/Controllers/Tracker/TrackerCell.swift @@ -14,20 +14,14 @@ protocol TrackerCellDelegate: AnyObject { final class TrackerCell: UICollectionViewCell { - weak var delegate: TrackerCellDelegate? - private var isCompletedToday = false - private var trackerId: UUID? - private var indexPath: IndexPath? - - override init(frame: CGRect) { - super.init(frame: frame) - setupViews() - setupConstraints() - } + private let pinnedImage: UIImageView = { + let imageView = UIImageView() + imageView.image = ImageAssets.pinned + imageView.isHidden = true + return imageView + }() - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + weak var delegate: TrackerCellDelegate? lazy var colorView: UIView = { let view = UIView() @@ -57,7 +51,7 @@ final class TrackerCell: UICollectionViewCell { private lazy var counterLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 12, weight: .medium) - label.textColor = .ypBlackDay + label.textColor = ColoursTheme.whiteDayBlackDay return label }() @@ -71,48 +65,20 @@ final class TrackerCell: UICollectionViewCell { return button }() - private let pinnedImage: UIImageView = { - let imageView = UIImageView() - imageView.image = UIImage(named: "Pinned") - imageView.isHidden = true - return imageView - }() + private var isCompletedToday = false + private var trackerId: UUID? + private var indexPath: IndexPath? + private var isPinnedToday = false - private func setupViews() { - self.backgroundColor = .clear - contentView.addSubviews(colorView, counterLabel, plusButton) - colorView.addSubviews(emojiLabel, textLabel, pinnedImage) + // MARK: Life cycle + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + setupConstraints() } - private func setupConstraints() { - NSLayoutConstraint.activate([ - colorView.heightAnchor.constraint(equalToConstant: contentView.bounds.width * 90/167), - colorView.topAnchor.constraint(equalTo: topAnchor), - colorView.leadingAnchor.constraint(equalTo: leadingAnchor), - colorView.trailingAnchor.constraint(equalTo: trailingAnchor), - - emojiLabel.topAnchor.constraint(equalTo: colorView.topAnchor, constant: 12), - emojiLabel.leadingAnchor.constraint(equalTo: colorView.leadingAnchor, constant: 12), - emojiLabel.heightAnchor.constraint(equalToConstant: 24), - emojiLabel.widthAnchor.constraint(equalToConstant: 24), - - textLabel.leadingAnchor.constraint(equalTo: colorView.leadingAnchor, constant: 12), - textLabel.trailingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: -12), - textLabel.bottomAnchor.constraint(equalTo: colorView.bottomAnchor, constant: -12), - - counterLabel.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 16), - counterLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), - - plusButton.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 8), - plusButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12), - plusButton.heightAnchor.constraint(equalToConstant: 34), - plusButton.widthAnchor.constraint(equalToConstant: 34), - - pinnedImage.topAnchor.constraint(equalTo: colorView.topAnchor, constant: 12), - pinnedImage.trailingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: -12), - pinnedImage.heightAnchor.constraint(equalToConstant: 24), - pinnedImage.widthAnchor.constraint(equalToConstant: 24) - ]) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } func pinnedImageEnabled(yes: Bool) { @@ -133,22 +99,21 @@ final class TrackerCell: UICollectionViewCell { plusButton.backgroundColor = tracker.color } + func setupPinned(tracker: Tracker, isPinnedToday: Bool, indexPath: IndexPath) { + self.isPinnedToday = isPinnedToday + self.trackerId = tracker.id + self.indexPath = indexPath + pinnedImageEnabled(yes: isPinnedToday) + } + private func updateCounterText(days: Int) { - let lastDigit = days % 10 - let lastTwoDigits = days % 100 - - switch lastDigit { - case 1 where lastTwoDigits != 11: - counterLabel.text = "\(days) день" - case 2...4 where !(12...14 ~= lastTwoDigits): - counterLabel.text = "\(days) дня" - default: - counterLabel.text = "\(days) дней" - } + let tasksString = String.localizedStringWithFormat( + NSLocalizedString("numberOfTasks", comment: "Number of remaining tasks"), days) + counterLabel.text = tasksString } private func updatePlusButton(trackerCompleted: Bool) { - let image: UIImage = (trackerCompleted ? UIImage(systemName: "checkmark") : UIImage(systemName: "plus"))! + let image: UIImage = (trackerCompleted ? ImageAssets.systemCheckmark : ImageAssets.systemPlus)! plusButton.setImage(image, for: .normal) let buttonOpacity: Float = trackerCompleted ? 0.3 : 1 plusButton.layer.opacity = buttonOpacity @@ -165,3 +130,43 @@ final class TrackerCell: UICollectionViewCell { } } } + +// MARK: - Layouts +private extension TrackerCell { + func setupViews() { + self.backgroundColor = .clear + contentView.addSubviews(colorView, counterLabel, plusButton) + colorView.addSubviews(emojiLabel, textLabel, pinnedImage) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + colorView.heightAnchor.constraint(equalToConstant: contentView.bounds.width * 90/167), + colorView.topAnchor.constraint(equalTo: topAnchor), + colorView.leadingAnchor.constraint(equalTo: leadingAnchor), + colorView.trailingAnchor.constraint(equalTo: trailingAnchor), + + emojiLabel.topAnchor.constraint(equalTo: colorView.topAnchor, constant: 12), + emojiLabel.leadingAnchor.constraint(equalTo: colorView.leadingAnchor, constant: 12), + emojiLabel.heightAnchor.constraint(equalToConstant: 24), + emojiLabel.widthAnchor.constraint(equalToConstant: 24), + + textLabel.leadingAnchor.constraint(equalTo: colorView.leadingAnchor, constant: 12), + textLabel.trailingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: -12), + textLabel.bottomAnchor.constraint(equalTo: colorView.bottomAnchor, constant: -12), + + counterLabel.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 16), + counterLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), + + plusButton.topAnchor.constraint(equalTo: colorView.bottomAnchor, constant: 8), + plusButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12), + plusButton.heightAnchor.constraint(equalToConstant: 34), + plusButton.widthAnchor.constraint(equalToConstant: 34), + + pinnedImage.topAnchor.constraint(equalTo: colorView.topAnchor, constant: 12), + pinnedImage.trailingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: -12), + pinnedImage.heightAnchor.constraint(equalToConstant: 24), + pinnedImage.widthAnchor.constraint(equalToConstant: 24) + ]) + } +} diff --git a/Tracker/Controllers/Tracker/TrackerViewController.swift b/Tracker/Controllers/Tracker/TrackerViewController.swift index 19906f6..14999a1 100644 --- a/Tracker/Controllers/Tracker/TrackerViewController.swift +++ b/Tracker/Controllers/Tracker/TrackerViewController.swift @@ -21,22 +21,27 @@ final class TrackerViewController: UIViewController { // MARK: Geometric parameters collectionView private let params = GeometryParams(cellCount: 2, leftInset: 16, rightInset: 16, cellSpacing: 9) - private let noFoundImage = UIImage(named: "noFound") - private let errorImage = UIImage(named: "errorImage") - private let trackerStore = TrackerStore() private let trackerCategoryStore = TrackerCategoryStore() private let trackerRecordStore = TrackerRecordStore() private let refreshControl = UIRefreshControl() - + private var currentDate: Date { return datePicker.date } + + private var isCompleteSelectedTracker: [UUID: Bool] = [:] + + private var filterAllTrackersIsTrue = false + + private(set) var pinnedCategories: [TrackerCategory] = [] + + weak var delegateStatistic: StatisticViewControllerProtocol? private lazy var collectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.register(TrackerCell.self, forCellWithReuseIdentifier: TrackerCell.identifier) collectionView.register(HeaderViewCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header") - collectionView.backgroundColor = .white + collectionView.backgroundColor = ColoursTheme.blackDayWhiteDay collectionView.allowsMultipleSelection = false collectionView.delegate = self collectionView.dataSource = self @@ -47,13 +52,29 @@ final class TrackerViewController: UIViewController { private lazy var datePicker: UIDatePicker = { let picker = UIDatePicker() picker.preferredDatePickerStyle = .compact + picker.locale = .autoupdatingCurrent picker.datePickerMode = .date - picker.tintColor = .systemBlue + picker.tintColor = .yp_Blue picker.date = Date() + picker.subviews[0].backgroundColor = ColoursTheme.whiteLightGray + picker.subviews[0].layer.cornerRadius = 8 + picker.overrideUserInterfaceStyle = .light picker.addTarget(self, action: #selector(datePickerValueChanges), for: .valueChanged) return picker }() + private lazy var filterButton: UIButton = { + let button = UIButton() + button.backgroundColor = .yp_Blue.withAlphaComponent(0.9) + button.setTitle(LocalizableKeys.filterButton, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .medium) + button.setTitleColor(.ypWhiteDay, for: .normal) + button.layer.cornerRadius = 16 + button.clipsToBounds = true + button.addTarget(self, action: #selector(addFilter), for: .touchUpInside) + return button + }() + private var pinTracker = false private lazy var weekDay = { @@ -78,6 +99,16 @@ final class TrackerViewController: UIViewController { filteredByDate(nil) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + AnalyticsService.openScreenReport(screen: .main) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + AnalyticsService.closeScreenReport(screen: .main) + } + private func refreshControlSetup() { refreshControl.addTarget(self, action: #selector(handleRefresh(_:)), for: .valueChanged) collectionView.refreshControl = refreshControl @@ -86,17 +117,23 @@ final class TrackerViewController: UIViewController { private func showBackgroundView(forCollection: Bool) { if visibleCategories.isEmpty { - let emptyView = EmptyView(frame: CGRect(x: 0, - y: 0, - width: view.bounds.width, - height: view.bounds.height), - image: forCollection ? errorImage : noFoundImage, - text: forCollection ? "Что будем отслеживать?" : "Ничего не найдено") + let emptyView = EmptyView( + frame: CGRect( + x: 0, + y: 0, + width: view.bounds.width, + height: view.bounds.height + ), + image: forCollection ? ImageAssets.trackerErrorImage : ImageAssets.trackerNoFoundImage, + text: forCollection ? LocalizableKeys.emptyErrorStub : LocalizableKeys.searchErrorStub + ) collectionView.backgroundView = emptyView collectionView.isScrollEnabled = false + filterButton.isHidden = true } else { collectionView.isScrollEnabled = true collectionView.backgroundView = nil + filterButton.isHidden = false } collectionView.reloadData() } @@ -104,10 +141,10 @@ final class TrackerViewController: UIViewController { private func setupNavigationBar() { if let navBar = navigationController?.navigationBar { navigationController?.navigationBar.prefersLargeTitles = true - navigationItem.title = "Трекеры" + navigationItem.title = LocalizableKeys.trackersNavigationItem - let leftButton = UIBarButtonItem(image: UIImage(named: "addButton"), style: .plain, target: self, action: #selector(addNewTracker)) - leftButton.tintColor = .ypBlackDay + let leftButton = UIBarButtonItem(image: ImageAssets.trackerAddButton, style: .plain, target: self, action: #selector(addNewTracker)) + leftButton.tintColor = ColoursTheme.whiteDayBlackDay navBar.topItem?.setLeftBarButton(leftButton, animated: false) let dateButton = UIBarButtonItem(customView: datePicker) @@ -116,6 +153,28 @@ final class TrackerViewController: UIViewController { } } + private func filteredByDate(_ text: String?) { + let filterWeekday = Calendar.current.component(.weekday, from: datePicker.date) + let filterText = (text ?? "").lowercased() + visibleCategories = categories.compactMap { category in + let trackers = category.trackers.filter { tracker in + let textCondition = filterText.isEmpty || tracker.name.lowercased().contains(filterText) + guard let timetable = tracker.timetable, !timetable.isEmpty else { + return textCondition + } + let dateCondition = timetable.contains { weekDay in + weekDay == WeekDays[filterWeekday] + } + return textCondition && dateCondition + } + if trackers.isEmpty { + return nil + } + return TrackerCategory(title: category.title, trackers: trackers) + } + (visibleCategories.isEmpty && !filterText.isEmpty) ? showBackgroundView(forCollection: false) : showBackgroundView(forCollection: true) + } + // MARK: Selectors @objc private func handleRefresh(_ sender: UIRefreshControl) { @@ -124,11 +183,13 @@ final class TrackerViewController: UIViewController { @objc private func addNewTracker() { + AnalyticsService.addTrackReport() + let trackersTypeViewController = TrackersTypeViewController() - trackersTypeViewController.title = "Создание трекера" + trackersTypeViewController.title = LocalizableKeys.chooseTypeOfTracker trackersTypeViewController.delegate = self let navigationController = UINavigationController(rootViewController: trackersTypeViewController) - navigationController.navigationBar.barTintColor = .white + navigationController.navigationBar.barTintColor = ColoursTheme.whiteDayBlackDay navigationController.navigationBar.shadowImage = UIImage() present(navigationController, animated: true) } @@ -139,26 +200,17 @@ final class TrackerViewController: UIViewController { dismiss(animated: true) } - private func filteredByDate(_ text: String?) { - let filterWeekday = Calendar.current.component(.weekday, from: datePicker.date) - let filterText = (text ?? "").lowercased() - visibleCategories = categories.compactMap { category in - let trackers = category.trackers.filter { tracker in - let textCondition = filterText.isEmpty || tracker.name.lowercased().contains(filterText) - guard let timetable = tracker.timetable, !timetable.isEmpty else { - return textCondition - } - let dateCondition = timetable.contains { weekDay in - weekDay == "".shortStringDayForInt(filterWeekday) - } - return textCondition && dateCondition - } - if trackers.isEmpty { - return nil - } - return TrackerCategory(title: category.title, trackers: trackers) - } - (visibleCategories.isEmpty && !filterText.isEmpty) ? showBackgroundView(forCollection: false) : showBackgroundView(forCollection: true) + @objc + private func addFilter() { + AnalyticsService.addFilterReport() + + let filterViewController = FiltersViewController() + filterViewController.delegate = self + filterViewController.title = LocalizableKeys.filterButton + let navigationController = UINavigationController(rootViewController: filterViewController) + navigationController.navigationBar.barTintColor = ColoursTheme.whiteDayBlackDay + navigationController.navigationBar.shadowImage = UIImage() + present(navigationController, animated: true) } } @@ -166,10 +218,16 @@ final class TrackerViewController: UIViewController { extension TrackerViewController: TrackerViewControllerDelegate { func createTracker(_ tracker: Tracker?, titleCategory: String?) { guard let newTracker = tracker, let newTitleCategory = titleCategory else { return } - do { - try trackerCategoryStore.createTrackerWithCategory(tracker: newTracker, with: newTitleCategory) - } catch { - self.showAlert("Невозможно создать трекер") + let trackerExists = categories.flatMap { $0.trackers }.contains { $0.id == tracker?.id } + + if trackerExists { + try? trackerStore.updateTracker(with: newTracker) + } else { + do { + try trackerCategoryStore.createTrackerWithCategory(tracker: newTracker, with: newTitleCategory) + } catch { + self.showAlert(LocalizableKeys.createTrackerAlert) + } } categories = trackerCategoryStore.categories filteredByDate(nil) @@ -181,7 +239,7 @@ extension TrackerViewController: TrackerCellDelegate { func completedTracker(id: UUID, at indexPath: IndexPath) { let trackerRecord = TrackerRecord(id: id, date: datePicker.date) if datePicker.date > Date() { - self.showAlert("Нельзя отмечать трекеры для будущих дат") + self.showAlert(LocalizableKeys.completedTrackersAlert) } else { completedTrackers.append(trackerRecord) do { @@ -190,17 +248,16 @@ extension TrackerViewController: TrackerCellDelegate { assertionFailure("Enabled to add \(trackerRecord)") } } + delegateStatistic?.completedTrackers = completedTrackers collectionView.reloadItems(at: [indexPath]) } func uncompletedTracker(id: UUID, at indexPath: IndexPath) { let filteredTrackerRecord = completedTrackers.filter { trackerRecord in isSameTrackerRecord(trackerRecord, id: id) } - print(filteredTrackerRecord) completedTrackers.removeAll { trackerRecord in isSameTrackerRecord(trackerRecord, id: id) } - print(completedTrackers) filteredTrackerRecord.forEach { trackerRecord in do { try trackerRecordStore.deleteTrackerRecord(trackerRecord: trackerRecord) @@ -208,11 +265,82 @@ extension TrackerViewController: TrackerCellDelegate { assertionFailure("Enabled to delete \(trackerRecord)") } } - print(trackerRecordStore.trackerRecords) + delegateStatistic?.completedTrackers = completedTrackers collectionView.reloadItems(at: [indexPath]) } } +// MARK: - FilterViewControllerProtocol +extension TrackerViewController: FiltersViewControllerProtocol { + + func filterAllTrackers() { + filterShowAllTrackers() + showBackgroundView(forCollection: true) + filterAllTrackersIsTrue = true + } + func filterTrackersForToday() { + filteredByDate(nil) + } + func filterCompletedTrackers() { + filteredByDate(nil) + filterCompletedTracker(date: datePicker.date) + } + func filterUnCompletedTrackers() { + filteredByDate(nil) + filterUncompletedTracker(date: datePicker.date) + } + + // MARK: Filter methods + func filterShowAllTrackers() { + categories.forEach { trackerCategory in + if trackerCategory.trackers.isEmpty { + do { + try trackerCategoryStore.deleteCategory(with: trackerCategory.title) + } catch { + assertionFailure("Enabled to delete \(trackerCategory)") + } + } + } + visibleCategories = categories + } + + func filterCompletedTracker(date: Date) { + visibleCategories = visibleCategories.compactMap { category in + let trackers = category.trackers.filter { tracker in + isTrackersCompletedToday(id: tracker.id, date: date) + } + if trackers.isEmpty { return nil } + return TrackerCategory(title: category.title, trackers: trackers) + } + if visibleCategories.isEmpty { + showBackgroundView(forCollection: true) + } + } + + func filterUncompletedTracker(date: Date) { + visibleCategories = visibleCategories.compactMap { category in + let trackers = category.trackers.filter { tracker in + !isTrackersCompletedToday(id: tracker.id, date: date) + } + if trackers.isEmpty { return nil } + return TrackerCategory(title: category.title, trackers: trackers) + } + if visibleCategories.isEmpty { + showBackgroundView(forCollection: true) + } + } + + func isTrackersCompletedToday(id: UUID, date: Date) -> Bool { + completedTrackers.contains { trackerRecord in + isSameTrackersRecord(trackerRecord, id: id, date: date) + } + } + func isSameTrackersRecord(_ record: TrackerRecord, id: UUID, date: Date) -> Bool { + let isSameDay = Calendar.current.isDate(record.date, inSameDayAs: date) + return record.id == id && isSameDay + } +} + // MARK: - UICollectionViewDataSource extension TrackerViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { @@ -233,7 +361,7 @@ extension TrackerViewController: UICollectionViewDataSource { let completedDays = completedTrackers.filter { $0.id == tracker.id }.count cell.setupCell(tracker: tracker, isCompletedToday: isTrackerCompletedToday, completedDays: completedDays, indexPath: indexPath) - + _ = pinnedTrackerCell(cell: cell, indexPath: indexPath, date: datePicker.date) cell.delegate = self return cell } @@ -291,22 +419,30 @@ extension TrackerViewController: UICollectionViewDelegate & UICollectionViewDele point: CGPoint) -> UIContextMenuConfiguration? { guard indexPaths.count > 0 else { return nil } let indexPath = indexPaths[0] + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TrackerCell.identifier, for: indexPath) as? TrackerCell else { return nil } + let pinned = pinnedTrackerCell(cell: cell, indexPath: indexPath, date: datePicker.date) + pinTracker = !pinned + let menu = UIMenu( children: [ - UIAction(title: pinTracker ? "Открепить" : "Закрепить") { [weak self] _ in + UIAction(title: pinTracker ? LocalizableKeys.pinTracker : LocalizableKeys.unpinTracker) { [weak self] _ in guard let self else { return } + AnalyticsService.clickRecordTrackReport() if self.pinTracker { - self.makeUnpin(indexPath: indexPath) - } else { self.makePin(indexPath: indexPath) + } else { + self.makeUnpin(indexPath: indexPath) } }, - UIAction(title: "Редактировать") { [weak self] _ in + UIAction(title: LocalizableKeys.editTracker) { [weak self] _ in guard let self else { return } + AnalyticsService.editTrackReport() self.makeEdit(indexPath: indexPath) }, - UIAction(title: "Удалить", image: nil, identifier: nil, discoverabilityTitle: nil, attributes: .destructive) {[weak self] _ in + UIAction(title: LocalizableKeys.deleteTracker, image: nil, identifier: nil, discoverabilityTitle: nil, attributes: .destructive) {[weak self] _ in guard let self else { return } + AnalyticsService.deleteTrackReport() self.makeDelete(indexPath: indexPath)} ]) @@ -317,24 +453,201 @@ extension TrackerViewController: UICollectionViewDelegate & UICollectionViewDele return configuration } private func makePin(indexPath: IndexPath) { - pinTracker = true - let cell = collectionView.cellForItem(at: indexPath) as? TrackerCell - cell?.pinnedImageEnabled(yes: pinTracker) + pinOfTracker(indexPath: indexPath) + filteredByDate(nil) } private func makeUnpin(indexPath: IndexPath) { - pinTracker = false - let cell = collectionView.cellForItem(at: indexPath) as? TrackerCell - cell?.pinnedImageEnabled(yes: pinTracker) + unpinOfTracker(indexPath: indexPath) + filteredByDate(nil) } private func makeEdit(indexPath: IndexPath) { - let _ = collectionView.cellForItem(at: indexPath) as? TrackerCell - //cell?.backgroundColor = .red + let tracker = visibleCategories[indexPath.section].trackers[indexPath.row] + let category = visibleCategories[indexPath.section].title + let daysCount = completedTrackers.filter { $0.id == tracker.id }.count + + let editViewController = NewTrackerViewController() + + if tracker.timetable?.isEmpty == true { + let editEvent = LocalizableKeys.editingIrregularEvent + editViewController.title = editEvent + UserDefaultsManager.showIrregularEvent = true + } else { + let editHabit = LocalizableKeys.editingHabits + editViewController.title = editHabit + UserDefaultsManager.showIrregularEvent = false + } + + editViewController.isEdit = true + editViewController.currentTracker = tracker + editViewController.editCategory = category + editViewController.dayButtonToggled = isTrackerCompletedToday(id: tracker.id) + editViewController.daysCount = daysCount + editViewController.date = currentDate + editViewController.onTrackerCreated = { [weak self] tracker, category in + guard let self = self else { return } + self.createTracker(tracker, titleCategory: category) + self.isCompletedTracker() + self.delegateStatistic?.completedTrackers = completedTrackers + } + + let navigationController = UINavigationController(rootViewController: editViewController) + navigationController.navigationBar.barTintColor = ColoursTheme.blackDayWhiteDay + + navigationController.navigationBar.shadowImage = UIImage() + present(navigationController, animated: true) } private func makeDelete(indexPath: IndexPath) { + showDeleteAlert(indexPath: indexPath) + + } + + private func showDeleteAlert(indexPath: IndexPath) { + let alert = UIAlertController(title: nil, message: LocalizableKeys.showDeleteAlert, preferredStyle: .actionSheet) + let deleteAction = UIAlertAction(title: LocalizableKeys.deleteTracker, style: .destructive) { [weak self] _ in + guard let self else { return } + self.deletingOfTracker(indexPath: indexPath) + if self.filterAllTrackersIsTrue { + self.showBackgroundView(forCollection: true) + } else { + self.filteredByDate(nil) + filterAllTrackersIsTrue = false + } + } + let cencelAction = UIAlertAction(title: LocalizableKeys.searchBarCancel, style: .cancel) + alert.addAction(deleteAction) + alert.addAction(cencelAction) + self.present(alert, animated: true) + } + + // MARK: Pinned trackers cell + func pinnedTrackerCell(cell: TrackerCell, indexPath: IndexPath, date: Date) -> Bool { + let searchTracker = visibleCategories[indexPath.section].trackers[indexPath.row] + let pinnedTrackers = pinnedCategories.map { trackerCategory in + trackerCategory.trackers + } + let isTrackerPinned = pinnedTrackers.compactMap { trackersArray in + trackersArray.first { tracker in + tracker.id == searchTracker.id + } + } + var pinned = false + isTrackerPinned.isEmpty ? (pinned = false) : (pinned = true) + cell.setupPinned(tracker: searchTracker, isPinnedToday: pinned, indexPath: indexPath) + return pinned + } + + // MARK: Pin of tracker + func pinOfTracker(indexPath: IndexPath) { + let pinnedTracker = visibleCategories[indexPath.section].trackers[indexPath.row] + pinnedCategories.append(TrackerCategory(title: visibleCategories[indexPath.section].title, trackers: [pinnedTracker])) + trackerStore.deleteTracker(tracker: pinnedTracker) + categories = trackerCategoryStore.categories + let newTitleCategory = LocalizableKeys.pinnedTrackers + let newCategory = TrackerCategory(title: newTitleCategory, trackers: [pinnedTracker]) + if categories.contains(where: { $0.title == newCategory.title }) { + guard let index = categories.firstIndex(where: { $0.title == newCategory.title }) else { return } + let oldCategory = categories[index] + let updatedTrackers = oldCategory.trackers + newCategory.trackers + let updatedTrackerCategory = TrackerCategory(title: newCategory.title, trackers: updatedTrackers) + categories[index] = updatedTrackerCategory + } else { + categories.insert(newCategory, at: 0) + } + do { + try trackerCategoryStore.createTrackerWithCategory(tracker: pinnedTracker, with: newTitleCategory) + } catch { + assertionFailure("Enabled to add \(pinnedTracker)") + } + } + + // MARK: unpin of tracker + func unpinOfTracker(indexPath: IndexPath) { + let pinnedTracker = visibleCategories[indexPath.section].trackers[indexPath.row] + + guard let unpinnedTracker = pinnedCategories.compactMap({ trackerCategory in + trackerCategory.trackers.first { tracker in + tracker.id == pinnedTracker.id + } + }).first else { return } + + guard let titleCategory = pinnedCategories.filter({ trackerCategory in + trackerCategory.trackers.contains { tracker in + tracker.id == pinnedTracker.id + } + }).first?.title else { return } + + let newCategory = TrackerCategory(title: titleCategory, trackers: [unpinnedTracker]) + if categories.contains(where: { $0.title == newCategory.title }) { + guard let index = categories.firstIndex(where: { $0.title == newCategory.title }) else { return } + let oldCategory = categories[index] + let updatedTrackers = oldCategory.trackers + newCategory.trackers + let updatedTrackerCategory = TrackerCategory(title: newCategory.title, trackers: updatedTrackers) + categories[index] = updatedTrackerCategory + } else { + categories.append(newCategory) + } + do { + try trackerCategoryStore.createTrackerWithCategory(tracker: unpinnedTracker, with: titleCategory) + } catch { + assertionFailure("Enabled to add \(unpinnedTracker)") + } + trackerStore.deleteTracker(tracker: pinnedTracker) + categories = trackerCategoryStore.categories + + pinnedCategories = pinnedCategories.compactMap { trackerCategory in + let trackers = trackerCategory.trackers.filter { tracker in + tracker.id != pinnedTracker.id + } + if trackers.isEmpty { + return nil + } + return TrackerCategory(title: trackerCategory.title, trackers: trackers) + } + } + + // MARK: Deleting of tracker + func deletingOfTracker(indexPath: IndexPath) { let searchTracker = visibleCategories[indexPath.section].trackers[indexPath.row] trackerStore.deleteTracker(tracker: searchTracker) + trackerCategoryStore.categories.forEach { trackerCategory in + if trackerCategory.trackers.isEmpty { + do { + try trackerCategoryStore.deleteCategory(with: trackerCategory.title) + } catch { + assertionFailure("Enabled to delete \(trackerCategory)") + } + } + } categories = trackerCategoryStore.categories - filteredByDate(nil) + visibleCategories = categories + + completedTrackers = completedTrackers.filter { + if $0.id == searchTracker.id { + do { + try trackerRecordStore.deleteTrackerRecord(trackerRecord: $0) + } catch { + assertionFailure("Enabled to delete \($0)") + } + } + return $0.id != searchTracker.id + } + delegateStatistic?.completedTrackers = completedTrackers + } + + private func isCompletedTracker() { + visibleCategories.forEach { category in + category.trackers.forEach { tracker in + let isCompletedToday = completedTrackers.contains { recordTracker in + recordTracker.id == tracker.id && areDatesEqualIgnoringTime(date1: recordTracker.date, date2: currentDate) + } + isCompleteSelectedTracker[tracker.id] = isCompletedToday + } + } + } + + private func areDatesEqualIgnoringTime(date1: Date, date2: Date) -> Bool { + let calendar = Calendar.current + return calendar.isDate(date1, equalTo: date2, toGranularity: .day) } // MARK: UICollectionViewDelegateFlowLayout @@ -390,15 +703,15 @@ extension TrackerViewController: UISearchBarDelegate { searchController = UISearchController(searchResultsController: nil) searchController?.searchBar.delegate = self searchController?.obscuresBackgroundDuringPresentation = false - searchController?.searchBar.placeholder = "Поиск" + searchController?.searchBar.placeholder = LocalizableKeys.searchBarPlaceholder navigationItem.searchController = searchController let attributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: UIColor.ypBlackDay, + .foregroundColor: ColoursTheme.whiteDayBlackDay, ] UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(attributes, for: .normal) - UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = "Отмена" + UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).title = LocalizableKeys.searchBarCancel definesPresentationContext = true } } @@ -411,7 +724,11 @@ private extension TrackerViewController { filteredCategoriesByDate = categories completedTrackers = trackerRecordStore.trackerRecords - view.addSubviews(collectionView) + pinnedCategories = categories.filter { trackerCategory in + trackerCategory.title == LocalizableKeys.pinnedTrackers + } + + view.addSubviews(collectionView, filterButton) } func setupConstraints() { @@ -420,7 +737,11 @@ private extension TrackerViewController { collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - datePicker.widthAnchor.constraint(equalToConstant: 120) + + filterButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100), + filterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + filterButton.widthAnchor.constraint(equalToConstant: 114), + filterButton.heightAnchor.constraint(equalToConstant: 50) ]) } } diff --git a/Tracker/Domain/TrackerStore.swift b/Tracker/Domain/TrackerStore.swift index 3a4413c..4776109 100644 --- a/Tracker/Domain/TrackerStore.swift +++ b/Tracker/Domain/TrackerStore.swift @@ -13,7 +13,7 @@ enum TrackerStoreError: Error { } final class TrackerStore: NSObject { - private let uiCollorMarshalling = UIColorMarshalling() + private let uiColorMarshalling = UIColorMarshalling() private let weekdaysMarshalling = WeekdaysMarshalling() private let context: NSManagedObjectContext @@ -67,7 +67,7 @@ final class TrackerStore: NSObject { return Tracker( id: id, name: name, - color: uiCollorMarshalling.color(from: color), + color: uiColorMarshalling.color(from: color), emojie: emojie, timetable: weekdaysMarshalling.makeWeekDayArrayFromString(timetable) ) @@ -87,12 +87,34 @@ final class TrackerStore: NSObject { let trackerCoreData = TrackerCoreData(context: context) trackerCoreData.trackerID = tracker.id trackerCoreData.name = tracker.name - trackerCoreData.color = uiCollorMarshalling.hexString(from: tracker.color) + trackerCoreData.color = uiColorMarshalling.hexString(from: tracker.color) trackerCoreData.emojie = tracker.emojie trackerCoreData.timetable = weekdaysMarshalling.makeStringFromArray(tracker.timetable ?? []) return trackerCoreData } + func updateTracker(with tracker: Tracker) throws { + let request = NSFetchRequest(entityName: "TrackerCoreData") + request.predicate = NSPredicate(format: "%K == %@", #keyPath(TrackerCoreData.trackerID), tracker.id.uuidString) + + do { + let fetchedTrackers = try context.fetch(request) + if let trackerToUpdate = fetchedTrackers.first { + trackerToUpdate.name = tracker.name + trackerToUpdate.color = uiColorMarshalling.hexString(from: tracker.color) + trackerToUpdate.emojie = tracker.emojie + trackerToUpdate.timetable = weekdaysMarshalling.makeStringFromArray(tracker.timetable ?? []) + + try context.save() + } else { + assertionFailure("Failed to find tracker with UUID: \(tracker.id)") + } + } catch { + assertionFailure("Failed to fetch and update tracker: \(error)") + throw error + } + } + func deleteTracker(tracker: Tracker) { let request = NSFetchRequest(entityName: "TrackerCoreData") request.predicate = NSPredicate(format: "%K == %@", #keyPath(TrackerCoreData.trackerID), tracker.id.uuidString) diff --git a/Tracker/Helpers/Extension/Cell + Extension.swift b/Tracker/Extension/Cell + Extension.swift similarity index 100% rename from Tracker/Helpers/Extension/Cell + Extension.swift rename to Tracker/Extension/Cell + Extension.swift diff --git a/Tracker/Extension/String + Extention.swift b/Tracker/Extension/String + Extention.swift new file mode 100644 index 0000000..0671504 --- /dev/null +++ b/Tracker/Extension/String + Extention.swift @@ -0,0 +1,17 @@ +// +// String + Extention.swift +// Tracker +// +// Created by Руслан on 18.09.2023. +// + +import Foundation + +extension String { + func localised() -> String { + NSLocalizedString(self, + tableName: "Localizable", + value: self, + comment: self) + } +} diff --git a/Tracker/Helpers/Extension/UIColor + Extension.swift b/Tracker/Extension/UIColor + Extension.swift similarity index 80% rename from Tracker/Helpers/Extension/UIColor + Extension.swift rename to Tracker/Extension/UIColor + Extension.swift index 4ca56d9..71e258b 100644 --- a/Tracker/Helpers/Extension/UIColor + Extension.swift +++ b/Tracker/Extension/UIColor + Extension.swift @@ -18,6 +18,9 @@ extension UIColor { static var yp_Red: UIColor { UIColor(named: "ypRed") ?? #colorLiteral(red: 0.9607843137, green: 0.4196078431, blue: 0.4235294118, alpha: 1) } static var ypWhiteDay: UIColor { UIColor(named: "White [day]") ?? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } static var ypWhiteNight: UIColor { UIColor(named: "White [night]") ?? #colorLiteral(red: 0.1019607843, green: 0.1058823529, blue: 0.1333333333, alpha: 1) } + static var ypGradientRed: UIColor { UIColor(named: "gradientRed") ?? UIColor.systemRed} + static var ypGradientGreen: UIColor { UIColor(named: "gradientGreen") ?? UIColor.systemGreen} + static var ypGradientBlue: UIColor { UIColor(named: "gradientBlue") ?? UIColor.systemBlue} static let colorSelection: [UIColor] = [ UIColor(named: "Color selection 1") ?? #colorLiteral(red: 1, green: 0.3956416845, blue: 0.3553284407, alpha: 1), UIColor(named: "Color selection 2") ?? #colorLiteral(red: 1, green: 0.606235683, blue: 0.1476774216, alpha: 1), @@ -38,4 +41,17 @@ extension UIColor { UIColor(named: "Color selection 17") ?? #colorLiteral(red: 0.6243798137, green: 0.5432854891, blue: 0.9222726226, alpha: 1), UIColor(named: "Color selection 18") ?? #colorLiteral(red: 0.1919171214, green: 0.8337991834, blue: 0.4192006886, alpha: 1) ] + + static func areAlmostEqual(color1: UIColor, color2: UIColor, tolerance: CGFloat = 0.01) -> Bool { + var red1: CGFloat = 0, green1: CGFloat = 0, blue1: CGFloat = 0, alpha1: CGFloat = 0 + var red2: CGFloat = 0, green2: CGFloat = 0, blue2: CGFloat = 0, alpha2: CGFloat = 0 + + color1.getRed(&red1, green: &green1, blue: &blue1, alpha: &alpha1) + color2.getRed(&red2, green: &green2, blue: &blue2, alpha: &alpha2) + + return abs(red1 - red2) <= tolerance && + abs(green1 - green2) <= tolerance && + abs(blue1 - blue2) <= tolerance && + abs(alpha1 - alpha2) <= tolerance + } } diff --git a/Tracker/Helpers/Extension/UIView + Extension.swift b/Tracker/Extension/UIView + Extension.swift similarity index 100% rename from Tracker/Helpers/Extension/UIView + Extension.swift rename to Tracker/Extension/UIView + Extension.swift diff --git a/Tracker/Helpers/Extension/UIViewController.swift b/Tracker/Extension/UIViewController.swift similarity index 100% rename from Tracker/Helpers/Extension/UIViewController.swift rename to Tracker/Extension/UIViewController.swift diff --git a/Tracker/Extension/WeekDays.swift b/Tracker/Extension/WeekDays.swift new file mode 100644 index 0000000..80c1164 --- /dev/null +++ b/Tracker/Extension/WeekDays.swift @@ -0,0 +1,41 @@ +// +// WeekDays.swift +// Tracker +// +// Created by Руслан on 10.10.2023. +// + +import Foundation + +extension WeekDays { + // MARK: Short String day from Int + static subscript(index: Int) -> String { + switch index { + case 1: "Sun".localised() + case 2: "Mon".localised() + case 3: "Tue".localised() + case 4: "Wed".localised() + case 5: "Thu".localised() + case 6: "Fri".localised() + case 7: "Sat".localised() + default: "\(assert(indexIsValid(index: index), "Index out of range"))" + } + } + + // MARK: Short days from long + static subscript(index: String) -> String { + switch index { + case WeekDays.monday.localize(): "Mon".localised() + case WeekDays.tuesday.localize(): "Tue".localised() + case WeekDays.wednesday.localize(): "Wed".localised() + case WeekDays.thursday.localize(): "Thu".localised() + case WeekDays.friday.localize(): "Fri".localised() + case WeekDays.saturday.localize(): "Sat".localised() + case WeekDays.sunday.localize(): "Sun".localised() + default: "\(assert(indexIsValid(index: 0), "Index out of range"))" + } + } + static func indexIsValid(index: Int) -> Bool { + index > 0 && index <= 7 + } +} diff --git a/Tracker/Helpers/ColoursTheme.swift b/Tracker/Helpers/ColoursTheme.swift new file mode 100644 index 0000000..1a257df --- /dev/null +++ b/Tracker/Helpers/ColoursTheme.swift @@ -0,0 +1,27 @@ +// +// ColoursTheme.swift +// Tracker +// +// Created by Руслан on 14.10.2023. +// + +import UIKit + +enum ColoursTheme { + static var blackDayWhiteDay: UIColor = UIColor { (traits) -> UIColor in + let isDarkMode = traits.userInterfaceStyle == .dark + return isDarkMode ? UIColor.ypBlackDay : UIColor.ypWhiteDay + } + static var whiteDayBlackDay = UIColor { (traits) -> UIColor in + let isDarkMode = traits.userInterfaceStyle == .dark + return isDarkMode ? UIColor.ypWhiteDay : UIColor.ypBlackDay + } + static var whiteLightGray = UIColor { (traits) -> UIColor in + let isDarkMode = traits.userInterfaceStyle == .dark + return isDarkMode ? UIColor.ypWhiteDay : UIColor.yp_LightGray + } + static var backgroundNightDay = UIColor { (traits) -> UIColor in + let isDarkMode = traits.userInterfaceStyle == .dark + return isDarkMode ? UIColor.ypBackgroundNight: UIColor.ypBackgroundDay + } +} diff --git a/Tracker/Helpers/Extension/String + Extention.swift b/Tracker/Helpers/Extension/String + Extention.swift deleted file mode 100644 index e1dbd55..0000000 --- a/Tracker/Helpers/Extension/String + Extention.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// String + Extention.swift -// Tracker -// -// Created by Руслан on 18.09.2023. -// - -import Foundation - -extension String { - func shortStringDayForInt(_ weekDay: Int) -> String { - switch weekDay { - case 1: return "Вс" - case 2: return "Пн" - case 3: return "Вт" - case 4: return "Ср" - case 5: return "Чт" - case 6: return "Пт" - case 7: return "Сб" - default: break - } - return "" - } - func shortDaysFromLong(for day: String) -> String { - switch day { - case "Понедельник": return "Пн" - case "Вторник": return "Вт" - case "Среда": return "Ср" - case "Четверг": return "Чт" - case "Пятница": return "Пт" - case "Суббота": return "Сб" - case "Воскресенье": return "Вс" - default: return "" - } - } -} diff --git a/Tracker/Helpers/UserDefaultsManager.swift b/Tracker/Helpers/UserDefaultsManager.swift index 39e2d81..c456dfa 100644 --- a/Tracker/Helpers/UserDefaultsManager.swift +++ b/Tracker/Helpers/UserDefaultsManager.swift @@ -27,4 +27,6 @@ final class UserDefaultsManager { @Defaults<[String]>(key: "categoriesArray") static var categoriesArray @Defaults<[String]>(key: "timetableArray") static var timetableArray @Defaults(key: "editingIndexPath") static var editingIndexPath + @Defaults(key: "completedTrackers") static var completedTrackers + @Defaults(key: "editingIndexFilter") static var editingIndexFilter } diff --git a/Tracker/Helpers/WeekdaysMarshalling.swift b/Tracker/Helpers/WeekdaysMarshalling.swift index 7628142..67cdb9e 100644 --- a/Tracker/Helpers/WeekdaysMarshalling.swift +++ b/Tracker/Helpers/WeekdaysMarshalling.swift @@ -8,7 +8,15 @@ import Foundation final class WeekdaysMarshalling { - private let weekdays: [String] = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] + private let weekdays: [String] = [ + "Mon".localised(), + "Tue".localised(), + "Wed".localised(), + "Thu".localised(), + "Fri".localised(), + "Sat".localised(), + "Sun".localised() + ] func makeStringFromArray(_ timetable: [String]) -> String { var string = "" diff --git a/Tracker/Localization/LocalizableKeys.swift b/Tracker/Localization/LocalizableKeys.swift new file mode 100644 index 0000000..1b4a3ba --- /dev/null +++ b/Tracker/Localization/LocalizableKeys.swift @@ -0,0 +1,89 @@ +// +// LocalizableKeys.swift +// Tracker +// +// Created by Руслан on 10.10.2023. +// + +import Foundation + +enum LocalizableKeys { + static let labelOnboardingFirst = "labelOnboardingFirst".localised() + static let labelOnboardingSecond = "labelOnboardingSecond".localised() + static let buttonOnboarding = "buttonOnboarding".localised() + + static let trackersTabBarItem = "trackersTabBarItem".localised() + static let statisticsTabBarItem = "statisticsTabBarItem".localised() + + static let trackersNavigationItem = "trackersNavigationItem".localised() + + static let chooseTypeOfTracker = "chooseTypeOfTracker".localised() + + static let pinTracker = "pinTracker".localised() + static let unpinTracker = "unpinTracker".localised() + static let editTracker = "editTracker".localised() + static let deleteTracker = "deleteTracker".localised() + + static let searchBarPlaceholder = "searchBarPlaceholder".localised() + static let searchBarCancel = "searchBarCancel".localised() + + static let createTrackerAlert = "createTrackerAlert".localised() + + static let searchErrorStub = "searchErrorStub".localised() + static let emptyErrorStub = "emptyErrorStub".localised() + + static let completedTrackersAlert = "completedTrackersAlert".localised() + + static let chooseTrackerButton = "chooseTrackerButton".localised() + static let chooseIrregularEventButton = "chooseIrregularEventButton".localised() + + static let showTrackerButton = "showTrackerButton".localised() + static let showIrregularEventButton = "showIrregularEventButton".localised() + + static let editingHabits = "editingHabits".localised() + static let editingIrregularEvent = "editingIrregularEvent".localised() + + static let newTrackerCategory = "newTrackerCategory".localised() + static let newTrackerTimetable = "newTrackerTimetable".localised() + + static let limitLabel = "newTrackerlimitLabel".localised() + static let newTrackerTextField = "newTrackerTextField".localised() + + static let newTrackerCancelButton = "newTrackerCancelButton".localised() + static let newTrackerCreateButton = "newTrackerCreateButton".localised() + + static let detailTextLabelEveryDay = "detailTextLabelEveryDay".localised() + static let detailTextLabelOrderedDays = "detailTextLabelOrderedDays".localised() + + static let newTrackerEmoji = "newTrackerEmoji".localised() + static let newTrackerColor = "newTrackerColor".localised() + + static let addCategoryButton = "addCategoryButton".localised() + static let trackerViewStubCategory = "trackerViewStubCategory".localised() + + static let contextMenuCategoryEdit = "contextMenuCategoryEdit".localised() + static let contextMenuCategoryDelete = "contextMenuCategoryDelete".localised() + + static let addNewCategoryTextField = "addNewCategoryTextField".localised() + static let addNewCategoryDoneButton = "addNewCategoryDoneButton".localised() + + static let addNewCategoryNew = "addNewCategoryNew".localised() + static let addNewCategoryEditing = "addNewCategoryEditing".localised() + + static let statisticsErrorLabel = "statisticsErrorLabel".localised() + + static let showAlertTitle = "showAlertTitle".localised() + static let showAlertOk = "showAlertOk".localised() + + static let statisticsTrackersCompleted = "statisticsTrackersCompleted".localised() + + static let filterButton = "filterButton".localised() + static let filterLabelOne = "filterTitleLabelOne".localised() + static let filterLabelTwo = "filterTitleLabelTwo".localised() + static let filterLabelThree = "filterTitleLabelThree".localised() + static let filterLabelFour = "filterTitleLabelFour".localised() + + static let showDeleteAlert = "showDeleteAlert".localised() + + static let pinnedTrackers = "pinnedTrackers".localised() +} diff --git a/Tracker/Localization/en.lproj/Localizable.strings b/Tracker/Localization/en.lproj/Localizable.strings new file mode 100644 index 0000000..5092f20 --- /dev/null +++ b/Tracker/Localization/en.lproj/Localizable.strings @@ -0,0 +1,102 @@ +/* + Localizable.strings + Tracker + + Created by Руслан on 10.10.2023. + +*/ + +"labelOnboardingFirst" = "Track only what you want"; +"labelOnboardingSecond" = "Even if it's not gallons of water and yoga"; +"buttonOnboarding" = "These are technologies!"; + +"trackersTabBarItem" = "Trackers"; +"statisticsTabBarItem" = "Statistics"; + +"trackersNavigationItem" = "Trackers"; + +"chooseTypeOfTracker" = "Creating a tracker"; + +"pinTracker" = "Pin"; +"unpinTracker" = "Unpin"; +"editTracker" = "Edit"; +"deleteTracker" = "Delete"; + +"searchBarPlaceholder" = "Search"; +"searchBarCancel" = "Cancel"; + +"createTrackerAlert" = "Unable to create a tracker"; + +"searchErrorStub" = "Nothing found"; +"emptyErrorStub" = "What will we track?"; + +"completedTrackersAlert" = "You can't mark trackers for future dates"; + +"chooseTrackerButton" = "Tracker"; +"chooseIrregularEventButton" = "Irregular event"; +"editingHabits" = "Editing habits"; +"editingIrregularEvent" = "Editing Irregular Event"; + +"showTrackerButton" = "A new tracker"; +"showIrregularEventButton" = "New irregular event"; + +"newTrackerCategory" = "Category"; +"newTrackerTimetable" = "Timetable"; + +"newTrackerlimitLabel" = "Limit of 38 characters"; + +"newTrackerTextField" = "Enter the name of the tracker"; + +"newTrackerCancelButton" = "Cancel"; +"newTrackerCreateButton" = "Create"; + +"detailTextLabelEveryDay" = "Every day"; +"detailTextLabelOrderedDays" = "Mon Tue Wed Thu Fri Sat Sun"; + +"newTrackerEmoji" = "Emoji"; +"newTrackerColor" = "Colour"; + +"addCategoryButton" = "Add a category"; + +"trackerViewStubCategory" = "Habits and events can be\ncombined in meaning"; + +"contextMenuCategoryEdit" = "Edit"; +"contextMenuCategoryDelete" = "Delete"; + +"addNewCategoryTextField" = "Enter the category name"; +"addNewCategoryDoneButton" = "Done"; + +"addNewCategoryNew" = "New category"; +"addNewCategoryEditing" = "Editing"; + +"statisticsErrorLabel" = "There is nothing to analyze yet"; + +"showDeleteAlert" = "Are you sure you want to delete the tracker?"; + +"showAlertTitle" = "Error"; +"showAlertOk" = "Ok"; + +"filterButton" = "Filters"; +"filterTitleLabelOne" = "All trackers"; +"filterTitleLabelTwo" = "Trackers for today"; +"filterTitleLabelThree" = "Completed"; +"filterTitleLabelFour" = "Uncompleted"; + +"pinnedTrackers" = "Pinned"; + +"Monday" = "Monday"; +"Tuesday" = "Tuesday"; +"Wednesday" = "Wednesday"; +"Thursday" = "Thursday"; +"Friday" = "Friday"; +"Saturday" = "Saturday"; +"Sunday" = "Sunday"; + +"Sun" = "Sun"; +"Mon" = "Mon"; +"Tue" = "Tue"; +"Wed" = "Wed"; +"Thu" = "Thu"; +"Fri" = "Fri"; +"Sat" = "Sat"; + diff --git a/Tracker/Localization/en.lproj/Localizable.stringsdict b/Tracker/Localization/en.lproj/Localizable.stringsdict new file mode 100644 index 0000000..bff3dfb --- /dev/null +++ b/Tracker/Localization/en.lproj/Localizable.stringsdict @@ -0,0 +1,39 @@ + + + + + numberOfTasks + + NSStringLocalizedFormatKey + %#@tasks@ + tasks + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + 1 day + other + %d days + + + + statisticsTrackersCompleted + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tracker completed + other + Trackers completed + + + + diff --git a/Tracker/Localization/ru.lproj/Localizable.strings b/Tracker/Localization/ru.lproj/Localizable.strings new file mode 100644 index 0000000..8d661ff --- /dev/null +++ b/Tracker/Localization/ru.lproj/Localizable.strings @@ -0,0 +1,103 @@ +/* + Localizable.strings + Tracker + + Created by Руслан on 10.10.2023. + +*/ + +"labelOnboardingFirst" = "Отслеживайте только то, что хотите"; +"labelOnboardingSecond" = "Даже если это не литры воды и йога"; +"buttonOnboarding" = "Вот это технологии!"; + +"trackersTabBarItem" = "Трекеры"; +"statisticsTabBarItem" = "Статистика"; + +"trackersNavigationItem" = "Трекеры"; + +"chooseTypeOfTracker" = "Создание трекера"; + +"pinTracker" = "Закрепить"; +"unpinTracker" = "Открепить"; +"editTracker" = "Редактировать"; +"deleteTracker" = "Удалить"; + +"searchBarPlaceholder" = "Поиск"; +"searchBarCancel" = "Отменить"; + +"createTrackerAlert" = "Невозможно создать трекер"; + +"searchErrorStub" = "Ничего не найдено"; +"emptyErrorStub" = "Что будем отслеживать?"; + +"completedTrackersAlert" = "Нельзя отмечать трекеры для будущих дат"; + +"chooseTrackerButton" = "Привычка"; +"chooseIrregularEventButton" = "Нерегулярное событие"; +"editingHabits" = "Редактирование привычки"; +"editingIrregularEvent" = "Редактирование нерегулярного события"; + +"showTrackerButton" = "Новая привычка"; +"showIrregularEventButton" = "Новое нерегулярное событие"; + +"newTrackerCategory" = "Категория"; +"newTrackerTimetable" = "Расписание"; + +"newTrackerlimitLabel" = "Ограничение 38 символов"; + +"newTrackerTextField" = "Введите название трекера"; + +"newTrackerCancelButton" = "Отменить"; +"newTrackerCreateButton" = "Создать"; + +"detailTextLabelEveryDay" = "Каждый день"; +"detailTextLabelOrderedDays" = "Пн Вт Ср Чт Пт Сб Вс"; + +"newTrackerEmoji" = "Эмоджи"; +"newTrackerColor" = "Цвет"; + +"addCategoryButton" = "Добавить категорию"; + +"trackerViewStubCategory" = "Привычки и события можно\nобъединить по смыслу"; + +"contextMenuCategoryEdit" = "Редактировать"; +"contextMenuCategoryDelete" = "Удалить"; + +"addNewCategoryTextField" = "Введите название категории"; +"addNewCategoryDoneButton" = "Готово"; + +"addNewCategoryNew" = "Новая категория"; +"addNewCategoryEditing" = "Редактирование"; + +"statisticsErrorLabel" = "Анализировать пока нечего"; + +"showDeleteAlert" = "Уверены что хотите удалить трекер?"; + +"showAlertTitle" = "Ошибка"; +"showAlertOk" = "Хорошо"; + +"filterButton" = "Фильтры"; +"filterTitleLabelOne" = "Все трекеры"; +"filterTitleLabelTwo" = "Трекеры на сегодня"; +"filterTitleLabelThree" = "Завершенные"; +"filterTitleLabelFour" = "Не завершенные"; + +"pinnedTrackers" = "Закреплённые"; + +"Monday" = "Понедельник"; +"Tuesday" = "Вторник"; +"Wednesday" = "Среда"; +"Thursday" = "Четверг"; +"Friday" = "Пятница"; +"Saturday" = "Суббота"; +"Sunday" = "Воскресенье"; + +"Sun" = "Вс"; +"Mon" = "Пн"; +"Tue" = "Вт"; +"Wed" = "Ср"; +"Thu" = "Чт"; +"Fri" = "Пт"; +"Sat" = "Сб"; + + diff --git a/Tracker/Localization/ru.lproj/Localizable.stringsdict b/Tracker/Localization/ru.lproj/Localizable.stringsdict new file mode 100644 index 0000000..f2b7cc9 --- /dev/null +++ b/Tracker/Localization/ru.lproj/Localizable.stringsdict @@ -0,0 +1,49 @@ + + + + + numberOfTasks + + NSStringLocalizedFormatKey + %#@tasks@ + tasks + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d день + few + %d дня + many + %d дней + other + %d дня + + + + statisticsTrackersCompleted + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Трекеров завершено + one + Трекер завершен + few + Трекера завершено + many + Трекеров завершено + other + Трекеров завершено + + + + diff --git a/Tracker/Models/WeekDay.swift b/Tracker/Models/WeekDay.swift new file mode 100644 index 0000000..7d14ba2 --- /dev/null +++ b/Tracker/Models/WeekDay.swift @@ -0,0 +1,23 @@ +// +// WeekDay.swift +// Tracker +// +// Created by Руслан on 10.10.2023. +// + +import Foundation + +enum WeekDays: String, CaseIterable { + case monday = "Monday" + case tuesday = "Tuesday" + case wednesday = "Wednesday" + case thursday = "Thursday" + case friday = "Friday" + case saturday = "Saturday" + case sunday = "Sunday" + + func localize() -> String { + return self.rawValue.localised() + } + static let numberOfDays = WeekDays.allCases.count +} diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/Contents.json b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/Contents.json rename to Tracker/Resources/Assets.xcassets/Colors/GradientColor/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientBlue.colorset/Contents.json b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientBlue.colorset/Contents.json new file mode 100644 index 0000000..faded56 --- /dev/null +++ b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.482", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientGreen.colorset/Contents.json b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientGreen.colorset/Contents.json new file mode 100644 index 0000000..3194dc1 --- /dev/null +++ b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.616", + "green" : "0.902", + "red" : "0.275" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientRed.colorset/Contents.json b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientRed.colorset/Contents.json new file mode 100644 index 0000000..0d504cd --- /dev/null +++ b/Tracker/Resources/Assets.xcassets/Colors/GradientColor/gradientRed.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.286", + "green" : "0.298", + "red" : "0.992" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/Contents.json b/Tracker/Resources/Assets.xcassets/Images/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/Contents.json b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/TabBarController/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/firstBackground.imageset/1.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/firstBackground.imageset/1.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1.png diff --git a/Tracker/Resources/Assets.xcassets/firstBackground.imageset/1@2x.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/firstBackground.imageset/1@2x.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1@2x.png diff --git a/Tracker/Resources/Assets.xcassets/firstBackground.imageset/1@3x.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/firstBackground.imageset/1@3x.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/1@3x.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/firstBackground.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/secondBackground.imageset/2.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/secondBackground.imageset/2.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2.png diff --git a/Tracker/Resources/Assets.xcassets/secondBackground.imageset/2@2x.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/secondBackground.imageset/2@2x.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2@2x.png diff --git a/Tracker/Resources/Assets.xcassets/secondBackground.imageset/2@3x.png b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/secondBackground.imageset/2@3x.png rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/2@3x.png diff --git a/Tracker/Resources/Assets.xcassets/secondBackground.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/secondBackground.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/OnboardingVC/secondBackground.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/Images/TabBarController/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TabBarController/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Tracker/Resources/Assets.xcassets/Images/TabBarController/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3.png diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3@2x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3@2x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3@2x.png diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3@3x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/3@3x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/3@3x.png diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticErrorImage.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticErrorImage.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28.png diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28@2x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28@2x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28@2x.png diff --git a/Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28@3x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/StatisticVC/statisticIcon.imageset/ic 28x28@3x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/statisticIcon.imageset/ic 28x28@3x.png diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28.png diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28@2x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28@2x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28@2x.png diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28@3x.png b/Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/TabBarController/trackersIcon.imageset/ic 28x28@3x.png rename to Tracker/Resources/Assets.xcassets/Images/TabBarController/trackersIcon.imageset/ic 28x28@3x.png diff --git a/Tracker/Resources/Assets.xcassets/Images/TrackerVC/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus.png b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus.png rename to Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus@2x.png b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus@2x.png rename to Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus@2x.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus@3x.png b/Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/addButton.imageset/plus@3x.png rename to Tracker/Resources/Assets.xcassets/Images/TrackerVC/addButton.imageset/plus@3x.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1.png b/Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1.png rename to Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1@2x.png b/Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1@2x.png rename to Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1@2x.png diff --git a/Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1@3x.png b/Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/MainTrackerVC/errorImage.imageset/1@3x.png rename to Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/1@3x.png diff --git a/Tracker/Resources/Assets.xcassets/firstBackground.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/firstBackground.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/errorImage.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/logo.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/logo.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/logo.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/logo.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/logo.imageset/Logo.svg b/Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo.svg similarity index 100% rename from Tracker/Resources/Assets.xcassets/logo.imageset/Logo.svg rename to Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo.svg diff --git a/Tracker/Resources/Assets.xcassets/logo.imageset/Logo@2x.png b/Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/logo.imageset/Logo@2x.png rename to Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo@2x.png diff --git a/Tracker/Resources/Assets.xcassets/logo.imageset/Logo@3x.png b/Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/logo.imageset/Logo@3x.png rename to Tracker/Resources/Assets.xcassets/Images/logo.imageset/Logo@3x.png diff --git a/Tracker/Resources/Assets.xcassets/noFound.imageset/2@1x.png b/Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@1x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/noFound.imageset/2@1x.png rename to Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@1x.png diff --git a/Tracker/Resources/Assets.xcassets/noFound.imageset/2@2x.png b/Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/noFound.imageset/2@2x.png rename to Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@2x.png diff --git a/Tracker/Resources/Assets.xcassets/noFound.imageset/2@3x.png b/Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/noFound.imageset/2@3x.png rename to Tracker/Resources/Assets.xcassets/Images/noFound.imageset/2@3x.png diff --git a/Tracker/Resources/Assets.xcassets/noFound.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/noFound.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/noFound.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/noFound.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/Pinned.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/Images/pinned.imageset/Contents.json similarity index 100% rename from Tracker/Resources/Assets.xcassets/Pinned.imageset/Contents.json rename to Tracker/Resources/Assets.xcassets/Images/pinned.imageset/Contents.json diff --git a/Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24.png b/Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24.png rename to Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24.png diff --git a/Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24@2x.png b/Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24@2x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24@2x.png rename to Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24@2x.png diff --git a/Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24@3x.png b/Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24@3x.png similarity index 100% rename from Tracker/Resources/Assets.xcassets/Pinned.imageset/ic 24x24@3x.png rename to Tracker/Resources/Assets.xcassets/Images/pinned.imageset/ic 24x24@3x.png diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Contents.json b/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Contents.json deleted file mode 100644 index 553bb5c..0000000 --- a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "Tab Bar Item.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "Tab Bar Item@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "Tab Bar Item@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item.png b/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item.png deleted file mode 100644 index b323dbe..0000000 Binary files a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item.png and /dev/null differ diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@2x.png b/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@2x.png deleted file mode 100644 index 6583bee..0000000 Binary files a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@2x.png and /dev/null differ diff --git a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@3x.png b/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@3x.png deleted file mode 100644 index 45fa3fa..0000000 Binary files a/Tracker/Resources/Assets.xcassets/TabBarController/Tab Bar Item.imageset/Tab Bar Item@3x.png and /dev/null differ diff --git a/Tracker/Resources/ImageAssets.swift b/Tracker/Resources/ImageAssets.swift new file mode 100644 index 0000000..5eaa363 --- /dev/null +++ b/Tracker/Resources/ImageAssets.swift @@ -0,0 +1,22 @@ +// +// ImageAssets.swift +// Tracker +// +// Created by Руслан on 13.10.2023. +// + +import UIKit + +struct ImageAssets { + static let statisticErrorImage = UIImage(named: "statisticErrorImage") + static let onboardingFirstBackground = UIImage(named: "firstBackground") + static let onboardingSecondBackground = UIImage(named: "secondBackground") + static let tabBarTrackersIcon = UIImage(named: "trackersIcon") + static let tabBarStatisticIcon = UIImage(named: "statisticIcon") + static let trackerNoFoundImage = UIImage(named: "noFound") + static let trackerErrorImage = UIImage(named: "errorImage") + static let trackerAddButton = UIImage(named: "addButton") + static let pinned = UIImage(named: "pinned") + static let systemCheckmark = UIImage(systemName: "checkmark") + static let systemPlus = UIImage(systemName: "plus") +} diff --git a/Tracker/Service/AnalyticsService.swift b/Tracker/Service/AnalyticsService.swift new file mode 100644 index 0000000..59d8f32 --- /dev/null +++ b/Tracker/Service/AnalyticsService.swift @@ -0,0 +1,87 @@ +// +// AnalyticsService.swift +// Tracker +// +// Created by Руслан on 14.10.2023. +// + +import Foundation +import YandexMobileMetrica + +enum ScreenName: String { + case main = "Main" + case statistics = "Statistics" +} + +struct AnalyticsService: AnalyticsServiceProtocol { + // MARK: Events + private enum Event: String { + case open + case close + case click + } + + // MARK: Parameters + private static let parametersScreenItem: [[AnyHashable: String]] = [ + ["screen": "Main", "item": "add_tracker"], + ["screen": "Main", "item": "filter"], + ["screen": "Main", "item": "edit"], + ["screen": "Main", "item": "delete"], + ["screen": "Main", "item": "tracker"], + ["screen": "TrackersType", "item": "add_tracker"], + ["screen": "TrackersType", "item": "add_ireggularEvent"], + ["screen": "NewTracker", "item": "create_tracker"], + ["screen": "NewTracker", "item": "exit_view"] + ] + + // MARK: Activate analitics + static func activateAnalytics() { + // MARK: Register your app in YandexMobileMetrica and add there an apiKey + guard let configuration = YMMYandexMetricaConfiguration.init( + apiKey: ApiKeys.apiKeyYMM ?? "" + ) else { return } + YMMYandexMetrica.activate(with: configuration) + } + + // MARK: Report + private static func report(event: String, params: [AnyHashable: String]) { + YMMYandexMetrica.reportEvent(event, parameters: params) { error in + print("REPORT ERROR %@", error.localizedDescription) + } + } + + // MARK: Functions + static func openScreenReport(screen: ScreenName) { + report(event: Event.open.rawValue, params: ["screen": "\(screen)"]) + } + static func closeScreenReport(screen: ScreenName) { + report(event: Event.close.rawValue, params: ["screen": "\(screen)"]) + } + static func addTrackReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[0]) + } + static func addFilterReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[1]) + } + static func editTrackReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[2]) + } + static func deleteTrackReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[3]) + } + static func clickRecordTrackReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[4]) + } + static func clickHabitReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[5]) + } + static func clickIreggularEventReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[6]) + } + static func clickCreateTrackerReport() { + report(event: Event.click.rawValue, params: parametersScreenItem[7]) + } + static func clickExitViewNewTracker() { + report(event: Event.click.rawValue, params: parametersScreenItem[8]) + } +} diff --git a/Tracker/Service/AnalyticsServiceProtocol.swift b/Tracker/Service/AnalyticsServiceProtocol.swift new file mode 100644 index 0000000..cf70fee --- /dev/null +++ b/Tracker/Service/AnalyticsServiceProtocol.swift @@ -0,0 +1,23 @@ +// +// AnalyticsServiceProtocol.swift +// Tracker +// +// Created by Руслан on 15.10.2023. +// + +import Foundation + +protocol AnalyticsServiceProtocol: Any { + static func activateAnalytics() + static func openScreenReport(screen: ScreenName) + static func closeScreenReport(screen: ScreenName) + static func addTrackReport() + static func editTrackReport() + static func deleteTrackReport() + static func addFilterReport() + static func clickRecordTrackReport() + static func clickHabitReport() + static func clickIreggularEventReport() + static func clickCreateTrackerReport() + static func clickExitViewNewTracker() +} diff --git a/TrackerTests/TrackerTests.swift b/TrackerTests/TrackerTests.swift new file mode 100644 index 0000000..ea6bc38 --- /dev/null +++ b/TrackerTests/TrackerTests.swift @@ -0,0 +1,87 @@ +// +// TrackerTests.swift +// TrackerTests +// +// Created by Руслан on 14.10.2023. +// + +import XCTest +import SnapshotTesting +@testable import Tracker + +final class TrackerTests: XCTestCase { + // MARK: You can reset all results + let reset = false + + // MARK: Snapshot tests - TabBarController(Main screen) + func testTabBarControllerDarkTheme() { + let viewController = TabBarController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testTabBarControllerLightTheme() { + let viewController = TabBarController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } + + // MARK: Snapshot tests - TrackersViewController + func testTrackersViewControllerDarkTheme() { + let viewController = TrackerViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testTrackersViewControllerLightTheme() { + let viewController = TrackerViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } + + // MARK: Snapshot tests - ChooseTypeOfTracker + func testChooseTypeOfTrackerDarkTheme() { + let viewController = TrackersTypeViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testChooseTypeOfTrackerLightTheme() { + let viewController = TrackersTypeViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } + + // MARK: Snapshot tests - NewTrackerViewController + func testNewTrackerViewControllerDarkTheme() { + let viewController = NewTrackerViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testNewTrackerViewControllerLightTheme() { + let viewController = NewTrackerViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } + + // MARK: Snapshot tests - CategoriesViewController + func testCategoriesViewControllerDarkTheme() { + let viewController = CategoriesViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testCategoriesViewControllerLightTheme() { + let viewController = CategoriesViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } + + // MARK: Snapshot tests - TimetableViewController + func testTimetableViewControllerDarkTheme() { + let viewController = TimetableViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .dark)), record: reset) + } + func testTimetableViewControllerLightTheme() { + let viewController = TimetableViewController() + sleep(1) + assertSnapshot(of: viewController, as: .image(traits: UITraitCollection(userInterfaceStyle: .light)), record: reset) + } +} diff --git a/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerDarkTheme.1.png new file mode 100644 index 0000000..7c64065 Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerLightTheme.1.png new file mode 100644 index 0000000..921a97a Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testCategoriesViewControllerLightTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerDarkTheme.1.png new file mode 100644 index 0000000..b5ed4c2 Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerLightTheme.1.png new file mode 100644 index 0000000..2dad7b9 Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testChooseTypeOfTrackerLightTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerDarkTheme.1.png new file mode 100644 index 0000000..d0dcefb Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerLightTheme.1.png new file mode 100644 index 0000000..3891ad4 Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testNewTrackerViewControllerLightTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerDarkTheme.1.png new file mode 100644 index 0000000..bbd122f Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerLightTheme.1.png new file mode 100644 index 0000000..411611c Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTabBarControllerLightTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerDarkTheme.1.png new file mode 100644 index 0000000..e44965a Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerLightTheme.1.png new file mode 100644 index 0000000..451acc4 Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTimetableViewControllerLightTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerDarkTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerDarkTheme.1.png new file mode 100644 index 0000000..7b8a0de Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerDarkTheme.1.png differ diff --git a/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerLightTheme.1.png b/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerLightTheme.1.png new file mode 100644 index 0000000..9a2abff Binary files /dev/null and b/TrackerTests/__Snapshots__/TrackerTests/testTrackersViewControllerLightTheme.1.png differ