diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ee9aec4d..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 08b1a429..884fec72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,14 @@ +### see: https://github.com/github/gitignore # example templates + +################# +## Apple macOS ## +################# +.DS_Store + +## SourceTree "Iconr" which is really "Icon\r" +## or add 0a 49 63 6e 6e 0d 0d 0a in binhex editor. The extra 0a brackets the 'Icon\r\r' +Icon? + ## Build generated build/ DerivedData/ @@ -64,9 +75,13 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots <<<<<<< HEAD -fastlane/test_output -README.md -DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Contents.json +fastlane/test_output +README.md +DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Contents.json ======= fastlane/test_output >>>>>>> swift4 + +## places for local-only notes. +*__LOCAL + diff --git a/DailyDozen/.DS_Store b/DailyDozen/.DS_Store deleted file mode 100644 index 8bea7fe7..00000000 Binary files a/DailyDozen/.DS_Store and /dev/null differ diff --git a/DailyDozen/.swiftlint.yml b/DailyDozen/.swiftlint.yml index b4b90a46..2bcbf26f 100644 --- a/DailyDozen/.swiftlint.yml +++ b/DailyDozen/.swiftlint.yml @@ -1,12 +1,36 @@ +disabled_rules: + - line_length + - trailing_comma + - trailing_whitespace + - closure_parameter_position + + # - file_length + # - function_body_length + # - todo + # - type_body_length + +excluded: # files and directories + - Pods + +included: # files and directories + +opt_in_rules: + # - conditional_returns_on_newline + +############################ +### Rule Detail Settings ### +############################ + identifier_name: min_length: # not possible to disable this partial rule, so set it to zero warning: 0 error: 0 -disabled_rules: - - file_length - - line_length - - function_body_length - - todo - - type_body_length -excluded: - - Pods \ No newline at end of file + # examples: id, dx + +large_tuple: + warning: 3 + error: 3 + +# line_length: # default 120 characters +# warning: 120 +# error: 133 diff --git a/DailyDozen/DailyDozen.xcodeproj/project.pbxproj b/DailyDozen/DailyDozen.xcodeproj/project.pbxproj index fedefddc..85323d5c 100644 --- a/DailyDozen/DailyDozen.xcodeproj/project.pbxproj +++ b/DailyDozen/DailyDozen.xcodeproj/project.pbxproj @@ -7,126 +7,309 @@ objects = { /* Begin PBXBuildFile section */ + 4904AF84239CB99600E271EC /* WeightEntryPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4904AF83239CB99600E271EC /* WeightEntryPagerViewController.swift */; }; + 4916451423EE4C410077731D /* TweakDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4916451323EE4C410077731D /* TweakDetailViewModel.swift */; }; + 4916451623EE4D360077731D /* TweakTextsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4916451523EE4D360077731D /* TweakTextsProvider.swift */; }; + 4916451823EE4E440077731D /* TweakDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4916451723EE4E440077731D /* TweakDetailViewController.swift */; }; + 4916451A23EE51310077731D /* TweakDetailDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4916451923EE51310077731D /* TweakDetailDataProvider.swift */; }; + 4916451D23EE53610077731D /* TweakDetailData.json in Resources */ = {isa = PBXBuildFile; fileRef = 4916451F23EE53610077731D /* TweakDetailData.json */; }; + 492474A323DB677A00D7D918 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 492474A123DB677A00D7D918 /* InfoPlist.strings */; }; + 492A1E702409B3FA00F6C37C /* TweakDetailActivityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492A1E6F2409B3FA00F6C37C /* TweakDetailActivityCell.swift */; }; + 492A1E722409B41000F6C37C /* TweakDetailDescriptionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492A1E712409B41000F6C37C /* TweakDetailDescriptionCell.swift */; }; + 4930360724760CBD00746D49 /* DatabaseMaintainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4930360624760CBD00746D49 /* DatabaseMaintainer.swift */; }; + 4933A74B23EE267600CE4631 /* DozeTextsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4933A74A23EE267500CE4631 /* DozeTextsProvider.swift */; }; + 4933A74D23EE28C500CE4631 /* DozeDetailInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4933A74C23EE28C500CE4631 /* DozeDetailInfo.swift */; }; + 4933A75023EE2BC800CE4631 /* DozeDetailData.json in Resources */ = {isa = PBXBuildFile; fileRef = 4933A75223EE2BC800CE4631 /* DozeDetailData.json */; }; + 4933A75523EE39CC00CE4631 /* DozeDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4933A75423EE39CC00CE4631 /* DozeDetailViewModel.swift */; }; + 4933A75723EE49BB00CE4631 /* TweakDetailInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4933A75623EE49BB00CE4631 /* TweakDetailInfo.swift */; }; + 493A8B9A235E93DA00A5009A /* SettingsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493A8B99235E93DA00A5009A /* SettingsKeys.swift */; }; + 493A9BD323CE47A000F679BE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BD523CE47A000F679BE /* Localizable.strings */; }; + 493A9BD723CE4EED00F679BE /* DozeDetailLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BD923CE4EED00F679BE /* DozeDetailLayout.storyboard */; }; + 493A9BDA23CE4F0200F679BE /* TweakDetailLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BDC23CE4F0200F679BE /* TweakDetailLayout.storyboard */; }; + 493A9BDD23CE4F1400F679BE /* DozeDetailSizeHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BDF23CE4F1400F679BE /* DozeDetailSizeHeader.xib */; }; + 493A9BE023CE4F1C00F679BE /* TweakDetailActivityHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BE223CE4F1C00F679BE /* TweakDetailActivityHeader.xib */; }; + 493A9BE323CE4F2D00F679BE /* DozeDetailTypeHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BE523CE4F2D00F679BE /* DozeDetailTypeHeader.xib */; }; + 493A9BE623CE4F3700F679BE /* TweakDetailDescriptionHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BE823CE4F3700F679BE /* TweakDetailDescriptionHeader.xib */; }; + 493A9BEC23CE612A00F679BE /* ItemHistoryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BEE23CE612A00F679BE /* ItemHistoryLayout.storyboard */; }; + 493A9BF023CE61BE00F679BE /* InfoMenuMainLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BF223CE61BE00F679BE /* InfoMenuMainLayout.storyboard */; }; + 493A9BF323CE61E700F679BE /* DozeEntryPagerLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BF523CE61E700F679BE /* DozeEntryPagerLayout.storyboard */; }; + 493A9BF623CE61F100F679BE /* DozeEntryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BF823CE61F100F679BE /* DozeEntryLayout.storyboard */; }; + 493A9BF923CE61FD00F679BE /* DozeEntryExtrasHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BFB23CE61FD00F679BE /* DozeEntryExtrasHeader.xib */; }; + 493A9BFC23CE623700F679BE /* DozeHistoryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9BFE23CE623700F679BE /* DozeHistoryLayout.storyboard */; }; + 493A9C0023CE627B00F679BE /* SettingsLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C0223CE627B00F679BE /* SettingsLayout.storyboard */; }; + 493A9C0323CE629100F679BE /* TweakEntryPagerLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C0523CE629100F679BE /* TweakEntryPagerLayout.storyboard */; }; + 493A9C0623CE629900F679BE /* TweakEntryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C0823CE629900F679BE /* TweakEntryLayout.storyboard */; }; + 493A9C0A23CE62BC00F679BE /* TweakHistoryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C0C23CE62BC00F679BE /* TweakHistoryLayout.storyboard */; }; + 493A9C0E23CE631600F679BE /* WeightEntryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C1023CE631600F679BE /* WeightEntryLayout.storyboard */; }; + 493A9C1123CE631F00F679BE /* WeightEntryPagerLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C1323CE631F00F679BE /* WeightEntryPagerLayout.storyboard */; }; + 493A9C1523CE634400F679BE /* WeightHistoryLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 493A9C1723CE634400F679BE /* WeightHistoryLayout.storyboard */; }; + 4941D72E237E13DC00ED84B5 /* RealmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D726237E13DB00ED84B5 /* RealmManager.swift */; }; + 4941D72F237E13DC00ED84B5 /* DataCountRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D728237E13DC00ED84B5 /* DataCountRecord.swift */; }; + 4941D730237E13DC00ED84B5 /* DataCountType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D729237E13DC00ED84B5 /* DataCountType.swift */; }; + 4941D731237E13DC00ED84B5 /* DataWeightRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D72A237E13DC00ED84B5 /* DataWeightRecord.swift */; }; + 4941D732237E13DC00ED84B5 /* DataWeightType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D72B237E13DC00ED84B5 /* DataWeightType.swift */; }; + 4941D733237E13DC00ED84B5 /* RealmProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D72C237E13DC00ED84B5 /* RealmProvider.swift */; }; + 4941D735237E266300ED84B5 /* DataCountAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941D734237E266300ED84B5 /* DataCountAttributes.swift */; }; + 4943720D2487702300632053 /* HealthSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4943720C2487702300632053 /* HealthSynchronizer.swift */; }; + 4944B99223AD7A1A008DFA30 /* ChartDataEntryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4944B99123AD7A1A008DFA30 /* ChartDataEntryExtension.swift */; }; + 495020DA2399DC780092D6F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 495020D82399DC780092D6F5 /* LaunchScreen.storyboard */; }; + 4955013A23CD453200C72FB3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4955013723CD453200C72FB3 /* Assets.xcassets */; }; + 495DAAEB23A06C330036077C /* WeightReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495DAAEA23A06C330036077C /* WeightReport.swift */; }; + 495FA4212453EEF200DE3E58 /* LogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495FA4202453EEF200DE3E58 /* LogService.swift */; }; + 4964604623AC524700FBD976 /* PopupPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4964604523AC524700FBD976 /* PopupPickerView.swift */; }; + 4966D7FB237DE9D4004F3A20 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 4966D7FA237DE9D3004F3A20 /* Resources */; }; + 496917DD238716BD00A9A743 /* RealmManagerLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496917DC238716BD00A9A743 /* RealmManagerLegacy.swift */; }; + 497390A923A5941900492D92 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497390A823A5941900492D92 /* MainViewController.swift */; }; + 497390AC23A5956900492D92 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 497390AA23A5956900492D92 /* Main.storyboard */; }; + 497390B023A5D4F900492D92 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497390AD23A5D4F800492D92 /* SettingsViewController.swift */; }; + 497390B123A5D4F900492D92 /* SettingsReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497390AE23A5D4F900492D92 /* SettingsReminderViewController.swift */; }; + 497A8F0A24748E88001BEDCA /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497A8F0924748E88001BEDCA /* SettingsManager.swift */; }; + 497A8F0C2474AFC8001BEDCA /* DataWeightValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497A8F0B2474AFC8001BEDCA /* DataWeightValues.swift */; }; + 497B137223CD72E600BC2488 /* InfoMenuAboutLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 497B137423CD72E600BC2488 /* InfoMenuAboutLayout.storyboard */; }; + 497F91B2240990B600464E4E /* TweakDetailSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497F91B1240990B600464E4E /* TweakDetailSections.swift */; }; + 4981708F2391B1790033882C /* TweakEntryStateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981708E2391B1790033882C /* TweakEntryStateCell.swift */; }; + 498170912391B19E0033882C /* TweakEntryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498170902391B19E0033882C /* TweakEntryTableViewCell.swift */; }; + 498170952391B6AB0033882C /* TweakEntrySections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498170942391B6AB0033882C /* TweakEntrySections.swift */; }; + 4981709B2391B85B0033882C /* TweakHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981709A2391B85B0033882C /* TweakHistoryViewController.swift */; }; + 4981709D2391B89E0033882C /* TweakHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981709C2391B89E0033882C /* TweakHistoryViewModel.swift */; }; + 498170A123920DD60033882C /* TweakEntryPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498170A023920DD60033882C /* TweakEntryPagerViewController.swift */; }; + 49821BAA2383727D006F166E /* DailyTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49821BA92383727D006F166E /* DailyTracker.swift */; }; + 498A2279237CD81F007AF5FB /* DailyDozenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498A2278237CD81F007AF5FB /* DailyDozenTests.swift */; }; + 498A2287237CD88A007AF5FB /* DailyDozenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498A2286237CD88A007AF5FB /* DailyDozenUITests.swift */; }; + 498D76A423709F9D00B5DF06 /* UtilityTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498D76A323709F9D00B5DF06 /* UtilityTableViewController.swift */; }; + 498F7244239D8B5C004E501B /* WeightHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498F7243239D8B5C004E501B /* WeightHistoryViewModel.swift */; }; + 498F7246239D90E2004E501B /* WeightEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498F7245239D90E2004E501B /* WeightEntryViewController.swift */; }; + 498F724B239E013E004E501B /* FirstLaunch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 498F7249239E013E004E501B /* FirstLaunch.storyboard */; }; + 498F724D239E08C0004E501B /* FirstLaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498F724C239E08C0004E501B /* FirstLaunchViewController.swift */; }; + 498F724F239E2EA3004E501B /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 498F724E239E2EA3004E501B /* HealthKit.framework */; }; + 498F7251239E3007004E501B /* HealthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 498F7250239E3007004E501B /* HealthManager.swift */; }; + 499B82FB239CD4D300E4AD19 /* WeightHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499B82FA239CD4D300E4AD19 /* WeightHistoryViewController.swift */; }; + 49B93E502477361100144C80 /* DatabaseBuiltInTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B93E4F2477361100144C80 /* DatabaseBuiltInTest.swift */; }; + 49C646CB236F4A46000DA8DC /* UtilityLayout.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 49C646CA236F4A46000DA8DC /* UtilityLayout.storyboard */; }; + 49D6448C245CEC3300814AD5 /* HealthWeightRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D6448B245CEC3300814AD5 /* HealthWeightRecord.swift */; }; + 49D9B07B240DCA1F000E9822 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49D9B07A240DCA1F000E9822 /* UIViewExtension.swift */; }; + 49D9B07C240DD08F000E9822 /* DozeDetailSizeUnitHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 492A1E75240A054C00F6C37C /* DozeDetailSizeUnitHeader.xib */; }; + 49D9B07D240DD09F000E9822 /* TweakDetailActivityUnitHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 492A1E7A240A05D600F6C37C /* TweakDetailActivityUnitHeader.xib */; }; + 49DB104D238E255D00E77E52 /* DozeEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DB104C238E255D00E77E52 /* DozeEntryViewModel.swift */; }; + 49DB104F238E257D00E77E52 /* TweakEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DB104E238E257D00E77E52 /* TweakEntryViewModel.swift */; }; + 49F0377B233AD9B600FFD2EE /* UIButtonCheckbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F0377A233AD9B600FFD2EE /* UIButtonCheckbox.swift */; }; + 49F8D1DC23905B70003E139F /* TweakEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F8D1DB23905B70003E139F /* TweakEntryViewController.swift */; }; + 49F8D1DE23906B29003E139F /* TweakEntryDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F8D1DD23906B29003E139F /* TweakEntryDataProvider.swift */; }; + BD68BC5B2FB6D632F16CE9CD /* Pods_DailyDozenTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 715D60F634E9BCD998C66B77 /* Pods_DailyDozenTests.framework */; }; BED5BB460CE3AEE8CF585899 /* Pods_DailyDozen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F514690D725E67AD54735EB9 /* Pods_DailyDozen.framework */; }; CF12918F1FC42DD300D1C17E /* DateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF12918E1FC42DD300D1C17E /* DateCell.swift */; }; - CF1393111FE0046800DD8679 /* Reminder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF1393101FE0046700DD8679 /* Reminder.storyboard */; }; - CF1393151FE0080900DD8679 /* ReminderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF1393141FE0080900DD8679 /* ReminderViewController.swift */; }; - CF17C8A71FAA019600367BF8 /* DetailsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF17C8A61FAA019600367BF8 /* DetailsSection.swift */; }; - CF22F07A1FA88D2200F25B70 /* DetailsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF22F0791FA88D2200F25B70 /* DetailsDataProvider.swift */; }; - CF22F07F1FA8ACE000F25B70 /* Details.plist in Resources */ = {isa = PBXBuildFile; fileRef = CF22F07E1FA8ACE000F25B70 /* Details.plist */; }; + CF17C8A71FAA019600367BF8 /* DozeDetailSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF17C8A61FAA019600367BF8 /* DozeDetailSections.swift */; }; + CF22F07A1FA88D2200F25B70 /* DozeDetailDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF22F0791FA88D2200F25B70 /* DozeDetailDataProvider.swift */; }; CF2680021FB477AD004A6200 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF2680011FB477AD004A6200 /* Date.swift */; }; - CF2FA9941FA87B2E003508F8 /* Details.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF2FA9931FA87B2E003508F8 /* Details.storyboard */; }; - CF2FA9961FA87F0C003508F8 /* DetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF2FA9951FA87F0C003508F8 /* DetailsViewController.swift */; }; + CF2FA9961FA87F0C003508F8 /* DozeDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF2FA9951FA87F0C003508F8 /* DozeDetailViewController.swift */; }; CF4902951FD91412000EAB68 /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4902941FD91412000EAB68 /* ChartView.swift */; }; CF4902971FD917D4000EAB68 /* ControlPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4902961FD917D4000EAB68 /* ControlPanel.swift */; }; - CF49029B1FD91F01000EAB68 /* ServingsHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF49029A1FD91F01000EAB68 /* ServingsHistoryViewModel.swift */; }; - CF4DECF71F9DF2A500DA094B /* ServingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4DECF61F9DF2A500DA094B /* ServingsCell.swift */; }; - CF4DECF91F9DF82900DA094B /* ServingsDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4DECF81F9DF82900DA094B /* ServingsDataProvider.swift */; }; + CF49029B1FD91F01000EAB68 /* DozeHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF49029A1FD91F01000EAB68 /* DozeHistoryViewModel.swift */; }; + CF4DECF71F9DF2A500DA094B /* DozeEntryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4DECF61F9DF2A500DA094B /* DozeEntryTableViewCell.swift */; }; + CF4DECF91F9DF82900DA094B /* DozeEntryDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4DECF81F9DF82900DA094B /* DozeEntryDataProvider.swift */; }; CF55BC4F2020450D00003D0D /* AlertBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF55BC4E2020450D00003D0D /* AlertBuilder.swift */; }; - CF5B28B11FA0A2C500D57A48 /* StateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF5B28B01FA0A2C500D57A48 /* StateCell.swift */; }; - CF64DF271FB5BAC400B4D124 /* SizesHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF64DF261FB5BAC400B4D124 /* SizesHeader.xib */; }; - CF64DF2C1FB5CD5100B4D124 /* TypesHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF64DF2B1FB5CD5100B4D124 /* TypesHeader.xib */; }; - CF64DF311FB5DFCF00B4D124 /* SizesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF64DF301FB5DFCF00B4D124 /* SizesCell.swift */; }; - CF64DF331FB5E03C00B4D124 /* TypesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF64DF321FB5E03C00B4D124 /* TypesCell.swift */; }; - CF6E6D351FB1B4FE00B177E7 /* Detail.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6E6D341FB1B4FE00B177E7 /* Detail.swift */; }; - CF6E6D391FB1B8E300B177E7 /* TextsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6E6D381FB1B8E300B177E7 /* TextsProvider.swift */; }; - CF6E6D3C1FB1BEC400B177E7 /* DetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF6E6D3B1FB1BEC400B177E7 /* DetailViewModel.swift */; }; - CF9051512022F71B00F4C1F2 /* dr_greger.png in Resources */ = {isa = PBXBuildFile; fileRef = CF9051502022F71B00F4C1F2 /* dr_greger.png */; }; + CF5B28B11FA0A2C500D57A48 /* DozeEntryStateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF5B28B01FA0A2C500D57A48 /* DozeEntryStateCell.swift */; }; + CF64DF311FB5DFCF00B4D124 /* DozeDetailSizeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF64DF301FB5DFCF00B4D124 /* DozeDetailSizeCell.swift */; }; + CF64DF331FB5E03C00B4D124 /* DozeDetailTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF64DF321FB5E03C00B4D124 /* DozeDetailTypeCell.swift */; }; CF987C281F9E157300D4893E /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF987C271F9E157300D4893E /* Item.swift */; }; - CF9E03201FBD9FA80084BC88 /* ItemHistory.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF9E031F1FBD9FA80084BC88 /* ItemHistory.storyboard */; }; CF9E03231FBD9FDE0084BC88 /* ItemHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9E03221FBD9FDE0084BC88 /* ItemHistoryViewController.swift */; }; CF9EC1C0202196B100C7B3CA /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9EC1BF202196B100C7B3CA /* Fonts.swift */; }; CF9EC1C32021D37000C7B3CA /* MenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9EC1C22021D37000C7B3CA /* MenuItem.swift */; }; - CFA3E8F91FC58D460092199D /* ServingsHistory.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFA3E8F81FC58D460092199D /* ServingsHistory.storyboard */; }; - CFA3E8FC1FC58E6B0092199D /* ServingsHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA3E8FB1FC58E6B0092199D /* ServingsHistoryViewController.swift */; }; - CFA9CDF21FBAF653000468CA /* VitaminsHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = CFA9CDF11FBAF653000468CA /* VitaminsHeader.xib */; }; + CFA3E8FC1FC58E6B0092199D /* DozeHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFA3E8FB1FC58E6B0092199D /* DozeHistoryViewController.swift */; }; CFAAB3721F9F3BC600B24748 /* Doze.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFAAB3711F9F3BC600B24748 /* Doze.swift */; }; - CFAAB3741F9F3E1200B24748 /* RealmConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFAAB3731F9F3E1200B24748 /* RealmConfig.swift */; }; CFAAB3771F9F3FDF00B24748 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFAAB3761F9F3FDF00B24748 /* URL.swift */; }; - CFAAB37A1F9F4A8000B24748 /* DozeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFAAB3791F9F4A8000B24748 /* DozeViewModel.swift */; }; CFB5568C1FD180C3003B5813 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB5568B1FD180C3003B5813 /* Report.swift */; }; - CFBEE2301FDE8D1E005C0F45 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFBEE22F1FDE8D1E005C0F45 /* About.storyboard */; }; - CFBEE2321FDE963D005C0F45 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBEE2311FDE963D005C0F45 /* AboutViewController.swift */; }; + CFBEE2321FDE963D005C0F45 /* InfoMenuAboutTableVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBEE2311FDE963D005C0F45 /* InfoMenuAboutTableVC.swift */; }; CFBEE2341FDE9A57005C0F45 /* RoundedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBEE2331FDE9A57005C0F45 /* RoundedView.swift */; }; - CFC060931FBC57C800A901A5 /* ServingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC060921FBC57C800A901A5 /* ServingsSection.swift */; }; - CFC4A84D1F975A3900B5AC75 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A84C1F975A3900B5AC75 /* MenuViewController.swift */; }; - CFC4A84F1F9761FF00B5AC75 /* PagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A84E1F9761FF00B5AC75 /* PagerViewController.swift */; }; - CFC4A8541F9776A500B5AC75 /* ServingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A8531F9776A500B5AC75 /* ServingsViewController.swift */; }; - CFCE5FCB1F978007005C672B /* Servings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFCE5FCA1F978007005C672B /* Servings.storyboard */; }; + CFC060931FBC57C800A901A5 /* DozeEntrySections.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC060921FBC57C800A901A5 /* DozeEntrySections.swift */; }; + CFC4A84D1F975A3900B5AC75 /* InfoMenuMainTableVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A84C1F975A3900B5AC75 /* InfoMenuMainTableVC.swift */; }; + CFC4A84F1F9761FF00B5AC75 /* DozeEntryPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A84E1F9761FF00B5AC75 /* DozeEntryPagerViewController.swift */; }; + CFC4A8541F9776A500B5AC75 /* DozeEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4A8531F9776A500B5AC75 /* DozeEntryViewController.swift */; }; CFD1BA331FCD24920057F549 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFD1BA321FCD24920057F549 /* RoundedButton.swift */; }; - CFE667291F98CF860037D1A2 /* Pager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFE667281F98CF860037D1A2 /* Pager.storyboard */; }; CFE6983C1F974B9700CD7905 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE6983B1F974B9700CD7905 /* AppDelegate.swift */; }; - CFE6983E1F974B9700CD7905 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE6983D1F974B9700CD7905 /* MainViewController.swift */; }; - CFE698411F974B9700CD7905 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFE6983F1F974B9700CD7905 /* Main.storyboard */; }; - CFE698431F974B9700CD7905 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CFE698421F974B9700CD7905 /* Assets.xcassets */; }; - CFE698461F974B9700CD7905 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFE698441F974B9700CD7905 /* LaunchScreen.storyboard */; }; - CFE793571FA756E40084F075 /* RealmProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE793561FA756E40084F075 /* RealmProvider.swift */; }; + CFE793571FA756E40084F075 /* RealmProviderLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE793561FA756E40084F075 /* RealmProviderLegacy.swift */; }; CFECDFF8201EF7C1003E8572 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFECDFF7201EF7C1003E8572 /* Colors.swift */; }; - CFF117731F97530300BFE73A /* Menu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CFF117721F97530300BFE73A /* Menu.storyboard */; }; CFF72AFB1FB9A499001CC2E9 /* UnitsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFF72AFA1FB9A499001CC2E9 /* UnitsType.swift */; }; CFF72AFE1FB9BAAA001CC2E9 /* LinkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFF72AFD1FB9BAAA001CC2E9 /* LinkService.swift */; }; CFF72B001FB9BB70001CC2E9 /* LinkSettings.plist in Resources */ = {isa = PBXBuildFile; fileRef = CFF72AFF1FB9BB70001CC2E9 /* LinkSettings.plist */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 498A227B237CD81F007AF5FB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CFE698301F974B9700CD7905 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CFE698371F974B9700CD7905; + remoteInfo = DailyDozen; + }; + 498A2289237CD88A007AF5FB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CFE698301F974B9700CD7905 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CFE698371F974B9700CD7905; + remoteInfo = DailyDozen; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 2CB41DDE201AD844603D7163 /* Pods-DailyDozenTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyDozenTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DailyDozenTests/Pods-DailyDozenTests.debug.xcconfig"; sourceTree = ""; }; + 4904AF83239CB99600E271EC /* WeightEntryPagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightEntryPagerViewController.swift; sourceTree = ""; }; + 4916451323EE4C410077731D /* TweakDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailViewModel.swift; sourceTree = ""; }; + 4916451523EE4D360077731D /* TweakTextsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakTextsProvider.swift; sourceTree = ""; }; + 4916451723EE4E440077731D /* TweakDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailViewController.swift; sourceTree = ""; }; + 4916451923EE51310077731D /* TweakDetailDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailDataProvider.swift; sourceTree = ""; }; + 4916451E23EE53610077731D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = Base; path = Base.lproj/TweakDetailData.json; sourceTree = ""; }; + 4916452023EE53640077731D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = es; path = es.lproj/TweakDetailData.json; sourceTree = ""; }; + 492474A223DB677A00D7D918 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; + 492A1E6F2409B3FA00F6C37C /* TweakDetailActivityCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailActivityCell.swift; sourceTree = ""; }; + 492A1E712409B41000F6C37C /* TweakDetailDescriptionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailDescriptionCell.swift; sourceTree = ""; }; + 492A1E74240A054C00F6C37C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DozeDetailSizeUnitHeader.xib; sourceTree = ""; }; + 492A1E77240A055700F6C37C /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeDetailSizeUnitHeader.strings; sourceTree = ""; }; + 492A1E79240A05D600F6C37C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TweakDetailActivityUnitHeader.xib; sourceTree = ""; }; + 492A1E7C240A05FA00F6C37C /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakDetailActivityUnitHeader.strings; sourceTree = ""; }; + 4930360624760CBD00746D49 /* DatabaseMaintainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseMaintainer.swift; sourceTree = ""; }; + 4933A74A23EE267500CE4631 /* DozeTextsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeTextsProvider.swift; sourceTree = ""; }; + 4933A74C23EE28C500CE4631 /* DozeDetailInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailInfo.swift; sourceTree = ""; }; + 4933A75123EE2BC800CE4631 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = Base; path = Base.lproj/DozeDetailData.json; sourceTree = ""; }; + 4933A75323EE2BCB00CE4631 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = es; path = es.lproj/DozeDetailData.json; sourceTree = ""; }; + 4933A75423EE39CC00CE4631 /* DozeDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailViewModel.swift; sourceTree = ""; }; + 4933A75623EE49BB00CE4631 /* TweakDetailInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailInfo.swift; sourceTree = ""; }; + 493A8B99235E93DA00A5009A /* SettingsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeys.swift; sourceTree = ""; }; + 493A9BD423CE47A000F679BE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 493A9BD823CE4EED00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DozeDetailLayout.storyboard; sourceTree = ""; }; + 493A9BDB23CE4F0200F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TweakDetailLayout.storyboard; sourceTree = ""; }; + 493A9BDE23CE4F1400F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DozeDetailSizeHeader.xib; sourceTree = ""; }; + 493A9BE123CE4F1C00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TweakDetailActivityHeader.xib; sourceTree = ""; }; + 493A9BE423CE4F2D00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DozeDetailTypeHeader.xib; sourceTree = ""; }; + 493A9BE723CE4F3700F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TweakDetailDescriptionHeader.xib; sourceTree = ""; }; + 493A9BED23CE612A00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/ItemHistoryLayout.storyboard; sourceTree = ""; }; + 493A9BF123CE61BE00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/InfoMenuMainLayout.storyboard; sourceTree = ""; }; + 493A9BF423CE61E700F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DozeEntryPagerLayout.storyboard; sourceTree = ""; }; + 493A9BF723CE61F100F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DozeEntryLayout.storyboard; sourceTree = ""; }; + 493A9BFA23CE61FD00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DozeEntryExtrasHeader.xib; sourceTree = ""; }; + 493A9BFD23CE623700F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DozeHistoryLayout.storyboard; sourceTree = ""; }; + 493A9C0123CE627B00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/SettingsLayout.storyboard; sourceTree = ""; }; + 493A9C0423CE629100F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TweakEntryPagerLayout.storyboard; sourceTree = ""; }; + 493A9C0723CE629900F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TweakEntryLayout.storyboard; sourceTree = ""; }; + 493A9C0B23CE62BC00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TweakHistoryLayout.storyboard; sourceTree = ""; }; + 493A9C0F23CE631600F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/WeightEntryLayout.storyboard; sourceTree = ""; }; + 493A9C1223CE631F00F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/WeightEntryPagerLayout.storyboard; sourceTree = ""; }; + 493A9C1623CE634400F679BE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/WeightHistoryLayout.storyboard; sourceTree = ""; }; + 493A9C1C23CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoMenuAboutLayout.strings; sourceTree = ""; }; + 493A9C1D23CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/FirstLaunch.strings; sourceTree = ""; }; + 493A9C1E23CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; + 493A9C1F23CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeDetailLayout.strings; sourceTree = ""; }; + 493A9C2023CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakDetailLayout.strings; sourceTree = ""; }; + 493A9C2123CE6B7300F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeDetailSizeHeader.strings; sourceTree = ""; }; + 493A9C2223CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakDetailActivityHeader.strings; sourceTree = ""; }; + 493A9C2323CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeDetailTypeHeader.strings; sourceTree = ""; }; + 493A9C2423CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakDetailDescriptionHeader.strings; sourceTree = ""; }; + 493A9C2523CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/ItemHistoryLayout.strings; sourceTree = ""; }; + 493A9C2623CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoMenuMainLayout.strings; sourceTree = ""; }; + 493A9C2723CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeEntryPagerLayout.strings; sourceTree = ""; }; + 493A9C2823CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeEntryLayout.strings; sourceTree = ""; }; + 493A9C2923CE6B7400F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeEntryExtrasHeader.strings; sourceTree = ""; }; + 493A9C2A23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/DozeHistoryLayout.strings; sourceTree = ""; }; + 493A9C2B23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/SettingsLayout.strings; sourceTree = ""; }; + 493A9C2C23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakEntryPagerLayout.strings; sourceTree = ""; }; + 493A9C2D23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakEntryLayout.strings; sourceTree = ""; }; + 493A9C2E23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/TweakHistoryLayout.strings; sourceTree = ""; }; + 493A9C2F23CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/WeightEntryLayout.strings; sourceTree = ""; }; + 493A9C3023CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/WeightEntryPagerLayout.strings; sourceTree = ""; }; + 493A9C3123CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/WeightHistoryLayout.strings; sourceTree = ""; }; + 493A9C3223CE6B7500F679BE /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 4941D726237E13DB00ED84B5 /* RealmManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmManager.swift; sourceTree = ""; }; + 4941D728237E13DC00ED84B5 /* DataCountRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataCountRecord.swift; sourceTree = ""; }; + 4941D729237E13DC00ED84B5 /* DataCountType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataCountType.swift; sourceTree = ""; }; + 4941D72A237E13DC00ED84B5 /* DataWeightRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWeightRecord.swift; sourceTree = ""; }; + 4941D72B237E13DC00ED84B5 /* DataWeightType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataWeightType.swift; sourceTree = ""; }; + 4941D72C237E13DC00ED84B5 /* RealmProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmProvider.swift; sourceTree = ""; }; + 4941D734237E266300ED84B5 /* DataCountAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataCountAttributes.swift; sourceTree = ""; }; + 4943720C2487702300632053 /* HealthSynchronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthSynchronizer.swift; sourceTree = ""; }; + 4944B99123AD7A1A008DFA30 /* ChartDataEntryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartDataEntryExtension.swift; sourceTree = ""; }; + 495020D92399DC780092D6F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4955013723CD453200C72FB3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 495DAAEA23A06C330036077C /* WeightReport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeightReport.swift; sourceTree = ""; }; + 495FA4202453EEF200DE3E58 /* LogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogService.swift; sourceTree = ""; }; + 4964604523AC524700FBD976 /* PopupPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupPickerView.swift; sourceTree = ""; }; + 4966D7FA237DE9D3004F3A20 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; + 496917DC238716BD00A9A743 /* RealmManagerLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmManagerLegacy.swift; sourceTree = ""; }; + 497390A823A5941900492D92 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 497390AB23A5956900492D92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 497390AD23A5D4F800492D92 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 497390AE23A5D4F900492D92 /* SettingsReminderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReminderViewController.swift; sourceTree = ""; }; + 497A8F0924748E88001BEDCA /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = ""; }; + 497A8F0B2474AFC8001BEDCA /* DataWeightValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataWeightValues.swift; sourceTree = ""; }; + 497B137323CD72E600BC2488 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/InfoMenuAboutLayout.storyboard; sourceTree = ""; }; + 497F91B1240990B600464E4E /* TweakDetailSections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakDetailSections.swift; sourceTree = ""; }; + 4981708E2391B1790033882C /* TweakEntryStateCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntryStateCell.swift; sourceTree = ""; }; + 498170902391B19E0033882C /* TweakEntryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntryTableViewCell.swift; sourceTree = ""; }; + 498170942391B6AB0033882C /* TweakEntrySections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntrySections.swift; sourceTree = ""; }; + 4981709A2391B85B0033882C /* TweakHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakHistoryViewController.swift; sourceTree = ""; }; + 4981709C2391B89E0033882C /* TweakHistoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakHistoryViewModel.swift; sourceTree = ""; }; + 498170A023920DD60033882C /* TweakEntryPagerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntryPagerViewController.swift; sourceTree = ""; }; + 49821BA92383727D006F166E /* DailyTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyTracker.swift; sourceTree = ""; }; + 498A2276237CD81F007AF5FB /* DailyDozenTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DailyDozenTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 498A2278237CD81F007AF5FB /* DailyDozenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyDozenTests.swift; sourceTree = ""; }; + 498A227A237CD81F007AF5FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 498A2284237CD88A007AF5FB /* DailyDozenUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DailyDozenUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 498A2286237CD88A007AF5FB /* DailyDozenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyDozenUITests.swift; sourceTree = ""; }; + 498A2288237CD88A007AF5FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 498D76A323709F9D00B5DF06 /* UtilityTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityTableViewController.swift; sourceTree = ""; }; + 498F7243239D8B5C004E501B /* WeightHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightHistoryViewModel.swift; sourceTree = ""; }; + 498F7245239D90E2004E501B /* WeightEntryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightEntryViewController.swift; sourceTree = ""; }; + 498F724A239E013E004E501B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/FirstLaunch.storyboard; sourceTree = ""; }; + 498F724C239E08C0004E501B /* FirstLaunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstLaunchViewController.swift; sourceTree = ""; }; + 498F724E239E2EA3004E501B /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; + 498F7250239E3007004E501B /* HealthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthManager.swift; sourceTree = ""; }; + 499B82FA239CD4D300E4AD19 /* WeightHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightHistoryViewController.swift; sourceTree = ""; }; + 49B93E4F2477361100144C80 /* DatabaseBuiltInTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseBuiltInTest.swift; sourceTree = ""; }; + 49BB8C6A237373840047AFA4 /* DailyDozen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DailyDozen.entitlements; sourceTree = ""; }; + 49C646CA236F4A46000DA8DC /* UtilityLayout.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = UtilityLayout.storyboard; sourceTree = ""; }; + 49D6448B245CEC3300814AD5 /* HealthWeightRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthWeightRecord.swift; sourceTree = ""; }; + 49D9B07A240DCA1F000E9822 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = ""; }; + 49DB104C238E255D00E77E52 /* DozeEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryViewModel.swift; sourceTree = ""; }; + 49DB104E238E257D00E77E52 /* TweakEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweakEntryViewModel.swift; sourceTree = ""; }; + 49F0377A233AD9B600FFD2EE /* UIButtonCheckbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButtonCheckbox.swift; sourceTree = ""; }; + 49F8D1DB23905B70003E139F /* TweakEntryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntryViewController.swift; sourceTree = ""; }; + 49F8D1DD23906B29003E139F /* TweakEntryDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakEntryDataProvider.swift; sourceTree = ""; }; + 6ED3A55B48F7069CAE75156C /* Pods-DailyDozenTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyDozenTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DailyDozenTests/Pods-DailyDozenTests.release.xcconfig"; sourceTree = ""; }; + 715D60F634E9BCD998C66B77 /* Pods_DailyDozenTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DailyDozenTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7D4AA01331AD98C99ADB2BC0 /* Pods-DailyDozen.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DailyDozen.release.xcconfig"; path = "Pods/Target Support Files/Pods-DailyDozen/Pods-DailyDozen.release.xcconfig"; sourceTree = ""; }; CF12918E1FC42DD300D1C17E /* DateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCell.swift; sourceTree = ""; }; - CF1393101FE0046700DD8679 /* Reminder.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Reminder.storyboard; sourceTree = ""; }; - CF1393141FE0080900DD8679 /* ReminderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderViewController.swift; sourceTree = ""; }; - CF17C8A61FAA019600367BF8 /* DetailsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsSection.swift; sourceTree = ""; }; - CF22F0791FA88D2200F25B70 /* DetailsDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsDataProvider.swift; sourceTree = ""; }; - CF22F07E1FA8ACE000F25B70 /* Details.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Details.plist; sourceTree = ""; }; + CF17C8A61FAA019600367BF8 /* DozeDetailSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailSections.swift; sourceTree = ""; }; + CF22F0791FA88D2200F25B70 /* DozeDetailDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailDataProvider.swift; sourceTree = ""; }; CF2680011FB477AD004A6200 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; - CF2FA9931FA87B2E003508F8 /* Details.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Details.storyboard; sourceTree = ""; }; - CF2FA9951FA87F0C003508F8 /* DetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsViewController.swift; sourceTree = ""; }; + CF2FA9951FA87F0C003508F8 /* DozeDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailViewController.swift; sourceTree = ""; }; CF4902941FD91412000EAB68 /* ChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = ""; }; CF4902961FD917D4000EAB68 /* ControlPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlPanel.swift; sourceTree = ""; }; - CF49029A1FD91F01000EAB68 /* ServingsHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsHistoryViewModel.swift; sourceTree = ""; }; - CF4DECF61F9DF2A500DA094B /* ServingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsCell.swift; sourceTree = ""; }; - CF4DECF81F9DF82900DA094B /* ServingsDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsDataProvider.swift; sourceTree = ""; }; + CF49029A1FD91F01000EAB68 /* DozeHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeHistoryViewModel.swift; sourceTree = ""; }; + CF4DECF61F9DF2A500DA094B /* DozeEntryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryTableViewCell.swift; sourceTree = ""; }; + CF4DECF81F9DF82900DA094B /* DozeEntryDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryDataProvider.swift; sourceTree = ""; }; CF55BC4E2020450D00003D0D /* AlertBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertBuilder.swift; sourceTree = ""; }; - CF5B28B01FA0A2C500D57A48 /* StateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateCell.swift; sourceTree = ""; }; - CF64DF261FB5BAC400B4D124 /* SizesHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SizesHeader.xib; sourceTree = ""; }; - CF64DF2B1FB5CD5100B4D124 /* TypesHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TypesHeader.xib; sourceTree = ""; }; - CF64DF301FB5DFCF00B4D124 /* SizesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizesCell.swift; sourceTree = ""; }; - CF64DF321FB5E03C00B4D124 /* TypesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypesCell.swift; sourceTree = ""; }; - CF6E6D341FB1B4FE00B177E7 /* Detail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Detail.swift; sourceTree = ""; }; - CF6E6D381FB1B8E300B177E7 /* TextsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsProvider.swift; sourceTree = ""; }; - CF6E6D3B1FB1BEC400B177E7 /* DetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewModel.swift; sourceTree = ""; }; - CF9051502022F71B00F4C1F2 /* dr_greger.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = dr_greger.png; path = DailyDozen/App/Resources/Assets.xcassets/Images/dr_greger.imageset/dr_greger.png; sourceTree = SOURCE_ROOT; }; + CF5B28B01FA0A2C500D57A48 /* DozeEntryStateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryStateCell.swift; sourceTree = ""; }; + CF64DF301FB5DFCF00B4D124 /* DozeDetailSizeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailSizeCell.swift; sourceTree = ""; }; + CF64DF321FB5E03C00B4D124 /* DozeDetailTypeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeDetailTypeCell.swift; sourceTree = ""; }; CF987C271F9E157300D4893E /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; - CF9E031F1FBD9FA80084BC88 /* ItemHistory.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ItemHistory.storyboard; sourceTree = ""; }; CF9E03221FBD9FDE0084BC88 /* ItemHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemHistoryViewController.swift; sourceTree = ""; }; CF9EC1BF202196B100C7B3CA /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; CF9EC1C22021D37000C7B3CA /* MenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItem.swift; sourceTree = ""; }; - CFA3E8F81FC58D460092199D /* ServingsHistory.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ServingsHistory.storyboard; sourceTree = ""; }; - CFA3E8FB1FC58E6B0092199D /* ServingsHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsHistoryViewController.swift; sourceTree = ""; }; - CFA9CDF11FBAF653000468CA /* VitaminsHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VitaminsHeader.xib; sourceTree = ""; }; + CFA3E8FB1FC58E6B0092199D /* DozeHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeHistoryViewController.swift; sourceTree = ""; }; CFAAB3711F9F3BC600B24748 /* Doze.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Doze.swift; sourceTree = ""; }; - CFAAB3731F9F3E1200B24748 /* RealmConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmConfig.swift; sourceTree = ""; }; CFAAB3761F9F3FDF00B24748 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; - CFAAB3791F9F4A8000B24748 /* DozeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeViewModel.swift; sourceTree = ""; }; CFB5568B1FD180C3003B5813 /* Report.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Report.swift; sourceTree = ""; }; - CFBEE22F1FDE8D1E005C0F45 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = ""; }; - CFBEE2311FDE963D005C0F45 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; + CFBEE2311FDE963D005C0F45 /* InfoMenuAboutTableVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMenuAboutTableVC.swift; sourceTree = ""; }; CFBEE2331FDE9A57005C0F45 /* RoundedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedView.swift; sourceTree = ""; }; - CFC060921FBC57C800A901A5 /* ServingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsSection.swift; sourceTree = ""; }; - CFC4A84C1F975A3900B5AC75 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; - CFC4A84E1F9761FF00B5AC75 /* PagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerViewController.swift; sourceTree = ""; }; - CFC4A8531F9776A500B5AC75 /* ServingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServingsViewController.swift; sourceTree = ""; }; - CFCE5FCA1F978007005C672B /* Servings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Servings.storyboard; sourceTree = ""; }; + CFC060921FBC57C800A901A5 /* DozeEntrySections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntrySections.swift; sourceTree = ""; }; + CFC4A84C1F975A3900B5AC75 /* InfoMenuMainTableVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoMenuMainTableVC.swift; sourceTree = ""; }; + CFC4A84E1F9761FF00B5AC75 /* DozeEntryPagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryPagerViewController.swift; sourceTree = ""; }; + CFC4A8531F9776A500B5AC75 /* DozeEntryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DozeEntryViewController.swift; sourceTree = ""; }; CFD1BA321FCD24920057F549 /* RoundedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; }; - CFE667281F98CF860037D1A2 /* Pager.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Pager.storyboard; sourceTree = ""; }; CFE698381F974B9700CD7905 /* DailyDozen.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DailyDozen.app; sourceTree = BUILT_PRODUCTS_DIR; }; CFE6983B1F974B9700CD7905 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - CFE6983D1F974B9700CD7905 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - CFE698401F974B9700CD7905 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - CFE698421F974B9700CD7905 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - CFE698451F974B9700CD7905 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CFE698471F974B9700CD7905 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CFE793561FA756E40084F075 /* RealmProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmProvider.swift; sourceTree = ""; }; + CFE793561FA756E40084F075 /* RealmProviderLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmProviderLegacy.swift; sourceTree = ""; }; CFECDFF7201EF7C1003E8572 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; - CFF117721F97530300BFE73A /* Menu.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Menu.storyboard; sourceTree = ""; }; CFF72AFA1FB9A499001CC2E9 /* UnitsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitsType.swift; sourceTree = ""; }; CFF72AFD1FB9BAAA001CC2E9 /* LinkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkService.swift; sourceTree = ""; }; CFF72AFF1FB9BB70001CC2E9 /* LinkSettings.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = LinkSettings.plist; sourceTree = ""; }; @@ -135,321 +318,594 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 498A2273237CD81F007AF5FB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BD68BC5B2FB6D632F16CE9CD /* Pods_DailyDozenTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 498A2281237CD88A007AF5FB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; CFE698351F974B9700CD7905 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BED5BB460CE3AEE8CF585899 /* Pods_DailyDozen.framework in Frameworks */, + 498F724F239E2EA3004E501B /* HealthKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 68646D18FC1A63146B00CB7C /* Frameworks */ = { + 4904AF7E239CB86400E271EC /* WeightEntry */ = { isa = PBXGroup; children = ( - F514690D725E67AD54735EB9 /* Pods_DailyDozen.framework */, + 495D02E623F630E20006ABA5 /* Controllers */, + 493A9C0D23CE62E300F679BE /* Views */, ); - name = Frameworks; + path = WeightEntry; sourceTree = ""; }; - CF12918D1FC42DB000D1C17E /* Views */ = { + 490A01252410808800C3EEEA /* Controllers */ = { isa = PBXGroup; children = ( - CF12918E1FC42DD300D1C17E /* DateCell.swift */, + 497390AE23A5D4F900492D92 /* SettingsReminderViewController.swift */, + 497390AD23A5D4F800492D92 /* SettingsViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 490A0126241080D300C3EEEA /* InfoMenuMain */ = { + isa = PBXGroup; + children = ( + 490A01282410810800C3EEEA /* Controllers */, + 490A0127241080FA00C3EEEA /* Views */, + CF9EC1C22021D37000C7B3CA /* MenuItem.swift */, + ); + path = InfoMenuMain; + sourceTree = ""; + }; + 490A0127241080FA00C3EEEA /* Views */ = { + isa = PBXGroup; + children = ( + 493A9BF223CE61BE00F679BE /* InfoMenuMainLayout.storyboard */, ); path = Views; sourceTree = ""; }; - CF13930F1FE0042800DD8679 /* Reminder */ = { + 490A01282410810800C3EEEA /* Controllers */ = { isa = PBXGroup; children = ( - CF1393131FE007E400DD8679 /* Controllers */, - CF1393121FE0046C00DD8679 /* Storyboards */, + CFC4A84C1F975A3900B5AC75 /* InfoMenuMainTableVC.swift */, ); - path = Reminder; + path = Controllers; sourceTree = ""; }; - CF1393121FE0046C00DD8679 /* Storyboards */ = { + 490A01292410812300C3EEEA /* Views */ = { isa = PBXGroup; children = ( - CF1393101FE0046700DD8679 /* Reminder.storyboard */, + 497B137423CD72E600BC2488 /* InfoMenuAboutLayout.storyboard */, ); - path = Storyboards; + path = Views; sourceTree = ""; }; - CF1393131FE007E400DD8679 /* Controllers */ = { + 490A012A2410812D00C3EEEA /* Controllers */ = { isa = PBXGroup; children = ( - CF1393141FE0080900DD8679 /* ReminderViewController.swift */, + CFBEE2311FDE963D005C0F45 /* InfoMenuAboutTableVC.swift */, ); path = Controllers; sourceTree = ""; }; - CF17C8A51FAA016D00367BF8 /* SupportingFiles */ = { + 490CC2112414A295005505DC /* Models */ = { isa = PBXGroup; children = ( - CF17C8A61FAA019600367BF8 /* DetailsSection.swift */, - CFF72AFA1FB9A499001CC2E9 /* UnitsType.swift */, + 493A8B99235E93DA00A5009A /* SettingsKeys.swift */, + 497A8F0924748E88001BEDCA /* SettingsManager.swift */, ); - path = SupportingFiles; + path = Models; sourceTree = ""; }; - CF22F07D1FA8ACC300F25B70 /* Texts */ = { + 49237F022411B9CB006DE658 /* Views */ = { isa = PBXGroup; children = ( - CF6E6D381FB1B8E300B177E7 /* TextsProvider.swift */, - CF22F07E1FA8ACE000F25B70 /* Details.plist */, + 4964604523AC524700FBD976 /* PopupPickerView.swift */, + 49C646CA236F4A46000DA8DC /* UtilityLayout.storyboard */, ); - path = Texts; + path = Views; sourceTree = ""; }; - CF2FA9911FA87AB5003508F8 /* Details */ = { + 49237F032411B9EA006DE658 /* Controllers */ = { isa = PBXGroup; children = ( - CF6E6D321FB1B4DD00B177E7 /* Models */, - CF64DF2D1FB5DC8300B4D124 /* View */, - CF6E6D3A1FB1BE9500B177E7 /* ViewModels */, - CF2FA9971FA87F31003508F8 /* Controllers */, - CF2FA9921FA87AEC003508F8 /* Storyboards */, - CF17C8A51FAA016D00367BF8 /* SupportingFiles */, + 498D76A323709F9D00B5DF06 /* UtilityTableViewController.swift */, ); - path = Details; + path = Controllers; sourceTree = ""; }; - CF2FA9921FA87AEC003508F8 /* Storyboards */ = { + 492EDD0423F4EA40009DE0ED /* TweakDetail */ = { isa = PBXGroup; children = ( - CF2FA9931FA87B2E003508F8 /* Details.storyboard */, - CF64DF261FB5BAC400B4D124 /* SizesHeader.xib */, - CF64DF2B1FB5CD5100B4D124 /* TypesHeader.xib */, + 492EDD0523F4EAAB009DE0ED /* Controllers */, + 492EDD0623F4EAB4009DE0ED /* Models */, + 492EDD0823F4EADB009DE0ED /* Views */, ); - path = Storyboards; + path = TweakDetail; sourceTree = ""; }; - CF2FA9971FA87F31003508F8 /* Controllers */ = { + 492EDD0523F4EAAB009DE0ED /* Controllers */ = { isa = PBXGroup; children = ( - CF2FA9951FA87F0C003508F8 /* DetailsViewController.swift */, - CF22F0791FA88D2200F25B70 /* DetailsDataProvider.swift */, + 4916451723EE4E440077731D /* TweakDetailViewController.swift */, + 492A1E6F2409B3FA00F6C37C /* TweakDetailActivityCell.swift */, + 492A1E712409B41000F6C37C /* TweakDetailDescriptionCell.swift */, ); path = Controllers; sourceTree = ""; }; - CF4902931FD913E9000EAB68 /* Views */ = { + 492EDD0623F4EAB4009DE0ED /* Models */ = { isa = PBXGroup; children = ( - CF4902941FD91412000EAB68 /* ChartView.swift */, - CF4902961FD917D4000EAB68 /* ControlPanel.swift */, + 4916451923EE51310077731D /* TweakDetailDataProvider.swift */, + 4933A75623EE49BB00CE4631 /* TweakDetailInfo.swift */, + 4916451323EE4C410077731D /* TweakDetailViewModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + 492EDD0823F4EADB009DE0ED /* Views */ = { + isa = PBXGroup; + children = ( + 493A9BDC23CE4F0200F679BE /* TweakDetailLayout.storyboard */, + 493A9BE223CE4F1C00F679BE /* TweakDetailActivityHeader.xib */, + 492A1E7A240A05D600F6C37C /* TweakDetailActivityUnitHeader.xib */, + 493A9BE823CE4F3700F679BE /* TweakDetailDescriptionHeader.xib */, + 497F91B1240990B600464E4E /* TweakDetailSections.swift */, ); path = Views; sourceTree = ""; }; - CF4902981FD91EB9000EAB68 /* ViewModels */ = { + 493A9C0D23CE62E300F679BE /* Views */ = { isa = PBXGroup; children = ( - CF49029A1FD91F01000EAB68 /* ServingsHistoryViewModel.swift */, + 493A9C1023CE631600F679BE /* WeightEntryLayout.storyboard */, + 493A9C1323CE631F00F679BE /* WeightEntryPagerLayout.storyboard */, ); - path = ViewModels; + path = Views; sourceTree = ""; }; - CF4DECF51F9DF27800DA094B /* Views */ = { + 493A9C1423CE633600F679BE /* Views */ = { isa = PBXGroup; children = ( - CF4DECF61F9DF2A500DA094B /* ServingsCell.swift */, - CF5B28B01FA0A2C500D57A48 /* StateCell.swift */, + 493A9C1723CE634400F679BE /* WeightHistoryLayout.storyboard */, ); path = Views; sourceTree = ""; }; - CF64DF2D1FB5DC8300B4D124 /* View */ = { + 493A9C1823CE670B00F679BE /* LocalStrings */ = { isa = PBXGroup; children = ( - CF64DF301FB5DFCF00B4D124 /* SizesCell.swift */, - CF64DF321FB5E03C00B4D124 /* TypesCell.swift */, + 493A9BD523CE47A000F679BE /* Localizable.strings */, + 4933A75223EE2BC800CE4631 /* DozeDetailData.json */, + 4916451F23EE53610077731D /* TweakDetailData.json */, ); - path = View; + path = LocalStrings; sourceTree = ""; }; - CF6E6D321FB1B4DD00B177E7 /* Models */ = { + 4941D724237E121E00ED84B5 /* Database */ = { + isa = PBXGroup; + children = ( + 49B93E4F2477361100144C80 /* DatabaseBuiltInTest.swift */, + 4930360624760CBD00746D49 /* DatabaseMaintainer.swift */, + 4941D727237E13DC00ED84B5 /* DataRecords */, + CFAAB37B1F9F4B0D00B24748 /* RealmLegacy */, + 4941D726237E13DB00ED84B5 /* RealmManager.swift */, + 4941D72C237E13DC00ED84B5 /* RealmProvider.swift */, + ); + path = Database; + sourceTree = ""; + }; + 4941D727237E13DC00ED84B5 /* DataRecords */ = { + isa = PBXGroup; + children = ( + 49821BA92383727D006F166E /* DailyTracker.swift */, + 4941D728237E13DC00ED84B5 /* DataCountRecord.swift */, + 4941D729237E13DC00ED84B5 /* DataCountType.swift */, + 4941D734237E266300ED84B5 /* DataCountAttributes.swift */, + 4941D72A237E13DC00ED84B5 /* DataWeightRecord.swift */, + 4941D72B237E13DC00ED84B5 /* DataWeightType.swift */, + 497A8F0B2474AFC8001BEDCA /* DataWeightValues.swift */, + ); + path = DataRecords; + sourceTree = ""; + }; + 4946DEE0241068A30054AEBB /* Views */ = { + isa = PBXGroup; + children = ( + 493A9C0C23CE62BC00F679BE /* TweakHistoryLayout.storyboard */, + ); + path = Views; + sourceTree = ""; + }; + 4946DEE1241068BD0054AEBB /* Models */ = { isa = PBXGroup; children = ( - CF6E6D341FB1B4FE00B177E7 /* Detail.swift */, + 4981709C2391B89E0033882C /* TweakHistoryViewModel.swift */, ); path = Models; sourceTree = ""; }; - CF6E6D3A1FB1BE9500B177E7 /* ViewModels */ = { + 4946DEE2241068CD0054AEBB /* Controllers */ = { + isa = PBXGroup; + children = ( + 4981709A2391B85B0033882C /* TweakHistoryViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 4946DEE32410697A0054AEBB /* Views */ = { isa = PBXGroup; children = ( - CF6E6D3B1FB1BEC400B177E7 /* DetailViewModel.swift */, + CF12918E1FC42DD300D1C17E /* DateCell.swift */, + 493A9BEE23CE612A00F679BE /* ItemHistoryLayout.storyboard */, ); - path = ViewModels; + path = Views; sourceTree = ""; }; - CF90514F2022F01A00F4C1F2 /* Attachments */ = { + 4946DEE4241069930054AEBB /* Controllers */ = { isa = PBXGroup; children = ( - CF9051502022F71B00F4C1F2 /* dr_greger.png */, + CF9E03221FBD9FDE0084BC88 /* ItemHistoryViewController.swift */, ); - path = Attachments; + path = Controllers; sourceTree = ""; }; - CF987C261F9E149000D4893E /* Models */ = { + 4946DEE524106E430054AEBB /* Views */ = { isa = PBXGroup; children = ( - CF987C271F9E157300D4893E /* Item.swift */, - CFAAB3711F9F3BC600B24748 /* Doze.swift */, + 493A9C0223CE627B00F679BE /* SettingsLayout.storyboard */, + ); + path = Views; + sourceTree = ""; + }; + 4955013623CD453200C72FB3 /* Resources */ = { + isa = PBXGroup; + children = ( + 4955013723CD453200C72FB3 /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 495D02DF23F6286A0006ABA5 /* Views */ = { + isa = PBXGroup; + children = ( + 493A9BD923CE4EED00F679BE /* DozeDetailLayout.storyboard */, + 493A9BDF23CE4F1400F679BE /* DozeDetailSizeHeader.xib */, + 492A1E75240A054C00F6C37C /* DozeDetailSizeUnitHeader.xib */, + 493A9BE523CE4F2D00F679BE /* DozeDetailTypeHeader.xib */, + CF17C8A61FAA019600367BF8 /* DozeDetailSections.swift */, + ); + path = Views; + sourceTree = ""; + }; + 495D02E023F628AD0006ABA5 /* Views */ = { + isa = PBXGroup; + children = ( + 493A9BFE23CE623700F679BE /* DozeHistoryLayout.storyboard */, + CF4902941FD91412000EAB68 /* ChartView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 495D02E123F62D7A0006ABA5 /* Models */ = { + isa = PBXGroup; + children = ( + 498F7243239D8B5C004E501B /* WeightHistoryViewModel.swift */, + 495DAAEA23A06C330036077C /* WeightReport.swift */, ); path = Models; sourceTree = ""; }; - CF9E031D1FBD9F540084BC88 /* ItemHistory */ = { + 495D02E223F62DA10006ABA5 /* Controllers */ = { isa = PBXGroup; children = ( - CF12918D1FC42DB000D1C17E /* Views */, - CF9E03211FBD9FC50084BC88 /* Controllers */, - CF9E031E1FBD9F800084BC88 /* Storyboards */, + 499B82FA239CD4D300E4AD19 /* WeightHistoryViewController.swift */, ); - path = ItemHistory; + path = Controllers; sourceTree = ""; }; - CF9E031E1FBD9F800084BC88 /* Storyboards */ = { + 495D02E523F630C40006ABA5 /* WeightHealthKit */ = { isa = PBXGroup; children = ( - CF9E031F1FBD9FA80084BC88 /* ItemHistory.storyboard */, + 498F7250239E3007004E501B /* HealthManager.swift */, + 49D6448B245CEC3300814AD5 /* HealthWeightRecord.swift */, + 4943720C2487702300632053 /* HealthSynchronizer.swift */, ); - path = Storyboards; + path = WeightHealthKit; sourceTree = ""; }; - CF9E03211FBD9FC50084BC88 /* Controllers */ = { + 495D02E623F630E20006ABA5 /* Controllers */ = { isa = PBXGroup; children = ( - CF9E03221FBD9FDE0084BC88 /* ItemHistoryViewController.swift */, + 4904AF83239CB99600E271EC /* WeightEntryPagerViewController.swift */, + 498F7245239D90E2004E501B /* WeightEntryViewController.swift */, ); path = Controllers; sourceTree = ""; }; - CF9EC1C12021D30800C7B3CA /* SupportingFiles */ = { + 495D02E723F6321E0006ABA5 /* TweakSection */ = { isa = PBXGroup; children = ( - CF9EC1C22021D37000C7B3CA /* MenuItem.swift */, + 492EDD0423F4EA40009DE0ED /* TweakDetail */, + 49F8D1DA23905B2A003E139F /* TweakEntry */, + 498170992391B85B0033882C /* TweakHistory */, ); - path = SupportingFiles; + path = TweakSection; sourceTree = ""; }; - CFA3E8F61FC58CD90092199D /* ServingsHistory */ = { + 495D02E823F632350006ABA5 /* DozeSection */ = { isa = PBXGroup; children = ( - CFB5568A1FD18086003B5813 /* Models */, - CF4902981FD91EB9000EAB68 /* ViewModels */, - CF4902931FD913E9000EAB68 /* Views */, - CFA3E8FA1FC58E4F0092199D /* Controllers */, - CFA3E8F71FC58D160092199D /* Storyboards */, + CF2FA9911FA87AB5003508F8 /* DozeDetail */, + CFF117751F9753B700BFE73A /* DozeEntry */, + CFA3E8F61FC58CD90092199D /* DozeHistory */, ); - path = ServingsHistory; + path = DozeSection; sourceTree = ""; }; - CFA3E8F71FC58D160092199D /* Storyboards */ = { + 495D02E923F632720006ABA5 /* WeightSection */ = { isa = PBXGroup; children = ( - CFA3E8F81FC58D460092199D /* ServingsHistory.storyboard */, + 4904AF7E239CB86400E271EC /* WeightEntry */, + 495D02E523F630C40006ABA5 /* WeightHealthKit */, + 498F77C2239D7582003F970F /* WeightHistory */, ); - path = Storyboards; + path = WeightSection; sourceTree = ""; }; - CFA3E8FA1FC58E4F0092199D /* Controllers */ = { + 498170932391B33B0033882C /* Controllers */ = { isa = PBXGroup; children = ( - CFA3E8FB1FC58E6B0092199D /* ServingsHistoryViewController.swift */, + 498170A023920DD60033882C /* TweakEntryPagerViewController.swift */, + 4981708E2391B1790033882C /* TweakEntryStateCell.swift */, + 498170902391B19E0033882C /* TweakEntryTableViewCell.swift */, + 49F8D1DB23905B70003E139F /* TweakEntryViewController.swift */, ); path = Controllers; sourceTree = ""; }; - CFAAB3751F9F3FCD00B24748 /* Extensions */ = { + 498170962391B7200033882C /* Models */ = { isa = PBXGroup; children = ( - CFAAB3761F9F3FDF00B24748 /* URL.swift */, - CF2680011FB477AD004A6200 /* Date.swift */, - CFECDFF7201EF7C1003E8572 /* Colors.swift */, - CF9EC1BF202196B100C7B3CA /* Fonts.swift */, + 49F8D1DD23906B29003E139F /* TweakEntryDataProvider.swift */, + 498170942391B6AB0033882C /* TweakEntrySections.swift */, + 49DB104E238E257D00E77E52 /* TweakEntryViewModel.swift */, ); - path = Extensions; + path = Models; sourceTree = ""; }; - CFAAB3781F9F4A6000B24748 /* ViewModel */ = { + 498170992391B85B0033882C /* TweakHistory */ = { isa = PBXGroup; children = ( - CFAAB3791F9F4A8000B24748 /* DozeViewModel.swift */, + 4946DEE2241068CD0054AEBB /* Controllers */, + 4946DEE1241068BD0054AEBB /* Models */, + 4946DEE0241068A30054AEBB /* Views */, ); - path = ViewModel; + path = TweakHistory; sourceTree = ""; }; - CFAAB37B1F9F4B0D00B24748 /* Realm */ = { + 4981709E23920B090033882C /* Views */ = { isa = PBXGroup; children = ( - CFAAB3731F9F3E1200B24748 /* RealmConfig.swift */, - CFE793561FA756E40084F075 /* RealmProvider.swift */, + 493A9C0523CE629100F679BE /* TweakEntryPagerLayout.storyboard */, + 493A9C0823CE629900F679BE /* TweakEntryLayout.storyboard */, ); - path = Realm; + path = Views; sourceTree = ""; }; - CFB5568A1FD18086003B5813 /* Models */ = { + 498A2277237CD81F007AF5FB /* DailyDozenTests */ = { isa = PBXGroup; children = ( - CFB5568B1FD180C3003B5813 /* Report.swift */, + 498A2278237CD81F007AF5FB /* DailyDozenTests.swift */, + 498A227A237CD81F007AF5FB /* Info.plist */, + 4966D7FA237DE9D3004F3A20 /* Resources */, ); - path = Models; + path = DailyDozenTests; + sourceTree = ""; + }; + 498A2285237CD88A007AF5FB /* DailyDozenUITests */ = { + isa = PBXGroup; + children = ( + 498A2286237CD88A007AF5FB /* DailyDozenUITests.swift */, + 498A2288237CD88A007AF5FB /* Info.plist */, + ); + path = DailyDozenUITests; sourceTree = ""; }; - CFBEE22A1FDE8CBA005C0F45 /* About */ = { + 498F77C2239D7582003F970F /* WeightHistory */ = { isa = PBXGroup; children = ( - CFBEE22C1FDE8CE0005C0F45 /* Controllers */, - CFBEE22B1FDE8CD5005C0F45 /* Storyboards */, + 495D02E123F62D7A0006ABA5 /* Models */, + 495D02E223F62DA10006ABA5 /* Controllers */, + 493A9C1423CE633600F679BE /* Views */, ); - path = About; + path = WeightHistory; sourceTree = ""; }; - CFBEE22B1FDE8CD5005C0F45 /* Storyboards */ = { + 49C646C9236F4997000DA8DC /* Utility */ = { isa = PBXGroup; children = ( - CFBEE22F1FDE8D1E005C0F45 /* About.storyboard */, + 49237F032411B9EA006DE658 /* Controllers */, + 49237F022411B9CB006DE658 /* Views */, ); - path = Storyboards; + path = Utility; sourceTree = ""; }; - CFBEE22C1FDE8CE0005C0F45 /* Controllers */ = { + 49F8D1DA23905B2A003E139F /* TweakEntry */ = { isa = PBXGroup; children = ( - CFBEE2311FDE963D005C0F45 /* AboutViewController.swift */, + 498170932391B33B0033882C /* Controllers */, + 498170962391B7200033882C /* Models */, + 4981709E23920B090033882C /* Views */, + ); + path = TweakEntry; + sourceTree = ""; + }; + 68646D18FC1A63146B00CB7C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 498F724E239E2EA3004E501B /* HealthKit.framework */, + F514690D725E67AD54735EB9 /* Pods_DailyDozen.framework */, + 715D60F634E9BCD998C66B77 /* Pods_DailyDozenTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CF13930F1FE0042800DD8679 /* Settings */ = { + isa = PBXGroup; + children = ( + 490CC2112414A295005505DC /* Models */, + 490A01252410808800C3EEEA /* Controllers */, + 4946DEE524106E430054AEBB /* Views */, + ); + path = Settings; + sourceTree = ""; + }; + CF22F07D1FA8ACC300F25B70 /* Texts */ = { + isa = PBXGroup; + children = ( + 4933A74A23EE267500CE4631 /* DozeTextsProvider.swift */, + 4916451523EE4D360077731D /* TweakTextsProvider.swift */, + 493A9C1823CE670B00F679BE /* LocalStrings */, + ); + path = Texts; + sourceTree = ""; + }; + CF2FA9911FA87AB5003508F8 /* DozeDetail */ = { + isa = PBXGroup; + children = ( + CF64DF2D1FB5DC8300B4D124 /* Controllers */, + CF6E6D321FB1B4DD00B177E7 /* Models */, + 495D02DF23F6286A0006ABA5 /* Views */, + ); + path = DozeDetail; + sourceTree = ""; + }; + CF64DF2D1FB5DC8300B4D124 /* Controllers */ = { + isa = PBXGroup; + children = ( + CF64DF301FB5DFCF00B4D124 /* DozeDetailSizeCell.swift */, + CF64DF321FB5E03C00B4D124 /* DozeDetailTypeCell.swift */, + CF2FA9951FA87F0C003508F8 /* DozeDetailViewController.swift */, ); path = Controllers; sourceTree = ""; }; - CFC060911FBC579700A901A5 /* SupportingFiles */ = { + CF6E6D321FB1B4DD00B177E7 /* Models */ = { isa = PBXGroup; children = ( - CFC060921FBC57C800A901A5 /* ServingsSection.swift */, + CF22F0791FA88D2200F25B70 /* DozeDetailDataProvider.swift */, + 4933A74C23EE28C500CE4631 /* DozeDetailInfo.swift */, + 4933A75423EE39CC00CE4631 /* DozeDetailViewModel.swift */, ); - path = SupportingFiles; + path = Models; sourceTree = ""; }; - CFC4A84B1F975A0C00B5AC75 /* Controllers */ = { + CF987C261F9E149000D4893E /* Models */ = { + isa = PBXGroup; + children = ( + CF4DECF81F9DF82900DA094B /* DozeEntryDataProvider.swift */, + CFC060921FBC57C800A901A5 /* DozeEntrySections.swift */, + 49DB104C238E255D00E77E52 /* DozeEntryViewModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + CF9E031D1FBD9F540084BC88 /* ItemHistory */ = { + isa = PBXGroup; + children = ( + 4946DEE4241069930054AEBB /* Controllers */, + 4946DEE32410697A0054AEBB /* Views */, + ); + path = ItemHistory; + sourceTree = ""; + }; + CFA3E8F61FC58CD90092199D /* DozeHistory */ = { + isa = PBXGroup; + children = ( + CFA3E8FA1FC58E4F0092199D /* Controllers */, + CFB5568A1FD18086003B5813 /* Models */, + 495D02E023F628AD0006ABA5 /* Views */, + ); + path = DozeHistory; + sourceTree = ""; + }; + CFA3E8FA1FC58E4F0092199D /* Controllers */ = { isa = PBXGroup; children = ( - CFC4A84C1F975A3900B5AC75 /* MenuViewController.swift */, + CF4902961FD917D4000EAB68 /* ControlPanel.swift */, + CFA3E8FB1FC58E6B0092199D /* DozeHistoryViewController.swift */, ); path = Controllers; sourceTree = ""; }; + CFAAB3751F9F3FCD00B24748 /* Extensions */ = { + isa = PBXGroup; + children = ( + 4944B99123AD7A1A008DFA30 /* ChartDataEntryExtension.swift */, + CFECDFF7201EF7C1003E8572 /* Colors.swift */, + CF2680011FB477AD004A6200 /* Date.swift */, + CF9EC1BF202196B100C7B3CA /* Fonts.swift */, + CFAAB3761F9F3FDF00B24748 /* URL.swift */, + 49D9B07A240DCA1F000E9822 /* UIViewExtension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + CFAAB37B1F9F4B0D00B24748 /* RealmLegacy */ = { + isa = PBXGroup; + children = ( + CFAAB3711F9F3BC600B24748 /* Doze.swift */, + CF987C271F9E157300D4893E /* Item.swift */, + 496917DC238716BD00A9A743 /* RealmManagerLegacy.swift */, + CFE793561FA756E40084F075 /* RealmProviderLegacy.swift */, + ); + path = RealmLegacy; + sourceTree = ""; + }; + CFB5568A1FD18086003B5813 /* Models */ = { + isa = PBXGroup; + children = ( + CFB5568B1FD180C3003B5813 /* Report.swift */, + CF49029A1FD91F01000EAB68 /* DozeHistoryViewModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + CFBEE22A1FDE8CBA005C0F45 /* InfoMenuAbout */ = { + isa = PBXGroup; + children = ( + 490A012A2410812D00C3EEEA /* Controllers */, + 490A01292410812300C3EEEA /* Views */, + ); + path = InfoMenuAbout; + sourceTree = ""; + }; CFC4A8501F97620400B5AC75 /* Controllers */ = { isa = PBXGroup; children = ( - CFC4A84E1F9761FF00B5AC75 /* PagerViewController.swift */, - CFC4A8531F9776A500B5AC75 /* ServingsViewController.swift */, - CF4DECF81F9DF82900DA094B /* ServingsDataProvider.swift */, + CFC4A84E1F9761FF00B5AC75 /* DozeEntryPagerViewController.swift */, + CF5B28B01FA0A2C500D57A48 /* DozeEntryStateCell.swift */, + CF4DECF61F9DF2A500DA094B /* DozeEntryTableViewCell.swift */, + CFC4A8531F9776A500B5AC75 /* DozeEntryViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -457,8 +913,12 @@ CFD1BA311FCD24220057F549 /* Views */ = { isa = PBXGroup; children = ( + 498F7249239E013E004E501B /* FirstLaunch.storyboard */, + 495020D82399DC780092D6F5 /* LaunchScreen.storyboard */, + 497390AA23A5956900492D92 /* Main.storyboard */, CFBEE2331FDE9A57005C0F45 /* RoundedView.swift */, CFD1BA321FCD24920057F549 /* RoundedButton.swift */, + 49F0377A233AD9B600FFD2EE /* UIButtonCheckbox.swift */, ); path = Views; sourceTree = ""; @@ -467,6 +927,8 @@ isa = PBXGroup; children = ( CFE6983A1F974B9700CD7905 /* DailyDozen */, + 498A2277237CD81F007AF5FB /* DailyDozenTests */, + 498A2285237CD88A007AF5FB /* DailyDozenUITests */, CFE698391F974B9700CD7905 /* Products */, D0E09D1F35AF7D3FF1EA14DC /* Pods */, 68646D18FC1A63146B00CB7C /* Frameworks */, @@ -477,6 +939,8 @@ isa = PBXGroup; children = ( CFE698381F974B9700CD7905 /* DailyDozen.app */, + 498A2276237CD81F007AF5FB /* DailyDozenTests.xctest */, + 498A2284237CD88A007AF5FB /* DailyDozenUITests.xctest */, ); name = Products; sourceTree = ""; @@ -484,14 +948,16 @@ CFE6983A1F974B9700CD7905 /* DailyDozen */ = { isa = PBXGroup; children = ( + 49BB8C6A237373840047AFA4 /* DailyDozen.entitlements */, CFE6984F1F974EFF00CD7905 /* App */, - CFF117711F9752CA00BFE73A /* Menu */, - CFF117751F9753B700BFE73A /* Servings */, - CF2FA9911FA87AB5003508F8 /* Details */, + 4941D724237E121E00ED84B5 /* Database */, + 495D02E823F632350006ABA5 /* DozeSection */, CF9E031D1FBD9F540084BC88 /* ItemHistory */, - CFA3E8F61FC58CD90092199D /* ServingsHistory */, - CFBEE22A1FDE8CBA005C0F45 /* About */, - CF13930F1FE0042800DD8679 /* Reminder */, + CFF117711F9752CA00BFE73A /* InfoMenu */, + CF13930F1FE0042800DD8679 /* Settings */, + 495D02E723F6321E0006ABA5 /* TweakSection */, + 49C646C9236F4997000DA8DC /* Utility */, + 495D02E923F632720006ABA5 /* WeightSection */, ); path = DailyDozen; sourceTree = ""; @@ -499,15 +965,13 @@ CFE6984F1F974EFF00CD7905 /* App */ = { isa = PBXGroup; children = ( - CFD1BA311FCD24220057F549 /* Views */, + CFE6983B1F974B9700CD7905 /* AppDelegate.swift */, CFE698501F974F0F00CD7905 /* Controllers */, - CFAAB37B1F9F4B0D00B24748 /* Realm */, - CFF72AFC1FB9BA4F001CC2E9 /* Services */, - CF22F07D1FA8ACC300F25B70 /* Texts */, CFAAB3751F9F3FCD00B24748 /* Extensions */, - CFE698531F974F6300CD7905 /* Storyboards */, + 4955013623CD453200C72FB3 /* Resources */, CFE698511F974F1800CD7905 /* SupportingFiles */, - CFE698521F974F4E00CD7905 /* Resources */, + CF22F07D1FA8ACC300F25B70 /* Texts */, + CFD1BA311FCD24220057F549 /* Views */, ); path = App; sourceTree = ""; @@ -515,8 +979,9 @@ CFE698501F974F0F00CD7905 /* Controllers */ = { isa = PBXGroup; children = ( - CFE6983D1F974B9700CD7905 /* MainViewController.swift */, + 498F724C239E08C0004E501B /* FirstLaunchViewController.swift */, CF55BC4E2020450D00003D0D /* AlertBuilder.swift */, + 497390A823A5941900492D92 /* MainViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -524,78 +989,43 @@ CFE698511F974F1800CD7905 /* SupportingFiles */ = { isa = PBXGroup; children = ( - CF90514F2022F01A00F4C1F2 /* Attachments */, - CFE6983B1F974B9700CD7905 /* AppDelegate.swift */, CFE698471F974B9700CD7905 /* Info.plist */, + 492474A123DB677A00D7D918 /* InfoPlist.strings */, + CFF72AFD1FB9BAAA001CC2E9 /* LinkService.swift */, + CFF72AFF1FB9BB70001CC2E9 /* LinkSettings.plist */, + 495FA4202453EEF200DE3E58 /* LogService.swift */, + CFF72AFA1FB9A499001CC2E9 /* UnitsType.swift */, ); path = SupportingFiles; sourceTree = ""; }; - CFE698521F974F4E00CD7905 /* Resources */ = { + CFF117711F9752CA00BFE73A /* InfoMenu */ = { isa = PBXGroup; children = ( - CFE698421F974B9700CD7905 /* Assets.xcassets */, + 490A0126241080D300C3EEEA /* InfoMenuMain */, + CFBEE22A1FDE8CBA005C0F45 /* InfoMenuAbout */, ); - path = Resources; + path = InfoMenu; sourceTree = ""; }; - CFE698531F974F6300CD7905 /* Storyboards */ = { + CFF117751F9753B700BFE73A /* DozeEntry */ = { isa = PBXGroup; children = ( - CFE6983F1F974B9700CD7905 /* Main.storyboard */, - CFE698441F974B9700CD7905 /* LaunchScreen.storyboard */, - ); - path = Storyboards; - sourceTree = ""; - }; - CFF117711F9752CA00BFE73A /* Menu */ = { - isa = PBXGroup; - children = ( - CFC4A84B1F975A0C00B5AC75 /* Controllers */, - CFF117741F97533F00BFE73A /* Storyboards */, - CF9EC1C12021D30800C7B3CA /* SupportingFiles */, - ); - path = Menu; - sourceTree = ""; - }; - CFF117741F97533F00BFE73A /* Storyboards */ = { - isa = PBXGroup; - children = ( - CFF117721F97530300BFE73A /* Menu.storyboard */, - ); - path = Storyboards; - sourceTree = ""; - }; - CFF117751F9753B700BFE73A /* Servings */ = { - isa = PBXGroup; - children = ( - CF987C261F9E149000D4893E /* Models */, - CF4DECF51F9DF27800DA094B /* Views */, - CFAAB3781F9F4A6000B24748 /* ViewModel */, CFC4A8501F97620400B5AC75 /* Controllers */, - CFF117781F97543900BFE73A /* Storyboards */, - CFC060911FBC579700A901A5 /* SupportingFiles */, - ); - path = Servings; - sourceTree = ""; - }; - CFF117781F97543900BFE73A /* Storyboards */ = { - isa = PBXGroup; - children = ( - CFE667281F98CF860037D1A2 /* Pager.storyboard */, - CFCE5FCA1F978007005C672B /* Servings.storyboard */, - CFA9CDF11FBAF653000468CA /* VitaminsHeader.xib */, + CF987C261F9E149000D4893E /* Models */, + CFF117781F97543900BFE73A /* Views */, ); - path = Storyboards; + path = DozeEntry; sourceTree = ""; }; - CFF72AFC1FB9BA4F001CC2E9 /* Services */ = { + CFF117781F97543900BFE73A /* Views */ = { isa = PBXGroup; children = ( - CFF72AFD1FB9BAAA001CC2E9 /* LinkService.swift */, - CFF72AFF1FB9BB70001CC2E9 /* LinkSettings.plist */, + 493A9BF523CE61E700F679BE /* DozeEntryPagerLayout.storyboard */, + 493A9BFB23CE61FD00F679BE /* DozeEntryExtrasHeader.xib */, + 493A9BF823CE61F100F679BE /* DozeEntryLayout.storyboard */, ); - path = Services; + path = Views; sourceTree = ""; }; D0E09D1F35AF7D3FF1EA14DC /* Pods */ = { @@ -603,6 +1033,8 @@ children = ( DA1790B64ED37B6DB70223BE /* Pods-DailyDozen.debug.xcconfig */, 7D4AA01331AD98C99ADB2BC0 /* Pods-DailyDozen.release.xcconfig */, + 2CB41DDE201AD844603D7163 /* Pods-DailyDozenTests.debug.xcconfig */, + 6ED3A55B48F7069CAE75156C /* Pods-DailyDozenTests.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -610,6 +1042,44 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 498A2275237CD81F007AF5FB /* DailyDozenTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 498A227F237CD81F007AF5FB /* Build configuration list for PBXNativeTarget "DailyDozenTests" */; + buildPhases = ( + F9148B5B6D2D32DAD583C1A4 /* [CP] Check Pods Manifest.lock */, + 498A2272237CD81F007AF5FB /* Sources */, + 498A2273237CD81F007AF5FB /* Frameworks */, + 498A2274237CD81F007AF5FB /* Resources */, + F69A747D623F34AF81001F1E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 498A227C237CD81F007AF5FB /* PBXTargetDependency */, + ); + name = DailyDozenTests; + productName = DailyDozenTests; + productReference = 498A2276237CD81F007AF5FB /* DailyDozenTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 498A2283237CD88A007AF5FB /* DailyDozenUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 498A228B237CD88A007AF5FB /* Build configuration list for PBXNativeTarget "DailyDozenUITests" */; + buildPhases = ( + 498A2280237CD88A007AF5FB /* Sources */, + 498A2281237CD88A007AF5FB /* Frameworks */, + 498A2282237CD88A007AF5FB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 498A228A237CD88A007AF5FB /* PBXTargetDependency */, + ); + name = DailyDozenUITests; + productName = DailyDozenUITests; + productReference = 498A2284237CD88A007AF5FB /* DailyDozenUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; CFE698371F974B9700CD7905 /* DailyDozen */ = { isa = PBXNativeTarget; buildConfigurationList = CFE6984A1F974B9700CD7905 /* Build configuration list for PBXNativeTarget "DailyDozen" */; @@ -620,7 +1090,6 @@ CFE698361F974B9700CD7905 /* Resources */, CFE6984E1F974CE400CD7905 /* ShellScript */, B928040A54DABDDC50DB0D10 /* [CP] Embed Pods Frameworks */, - 70F25E4D8F3A74B6E49D3041 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -637,12 +1106,23 @@ CFE698301F974B9700CD7905 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0900; - LastUpgradeCheck = 0900; + LastSwiftUpdateCheck = 1120; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = Nutritionfacts.org; TargetAttributes = { + 498A2275237CD81F007AF5FB = { + CreatedOnToolsVersion = 11.2; + ProvisioningStyle = Automatic; + TestTargetID = CFE698371F974B9700CD7905; + }; + 498A2283237CD88A007AF5FB = { + CreatedOnToolsVersion = 11.2; + ProvisioningStyle = Automatic; + TestTargetID = CFE698371F974B9700CD7905; + }; CFE698371F974B9700CD7905 = { CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1100; ProvisioningStyle = Automatic; }; }; @@ -654,6 +1134,7 @@ knownRegions = ( en, Base, + es, ); mainGroup = CFE6982F1F974B9700CD7905; productRefGroup = CFE698391F974B9700CD7905 /* Products */; @@ -661,32 +1142,64 @@ projectRoot = ""; targets = ( CFE698371F974B9700CD7905 /* DailyDozen */, + 498A2275237CD81F007AF5FB /* DailyDozenTests */, + 498A2283237CD88A007AF5FB /* DailyDozenUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 498A2274237CD81F007AF5FB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4966D7FB237DE9D4004F3A20 /* Resources in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 498A2282237CD88A007AF5FB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; CFE698361F974B9700CD7905 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CFE667291F98CF860037D1A2 /* Pager.storyboard in Resources */, - CF2FA9941FA87B2E003508F8 /* Details.storyboard in Resources */, - CFBEE2301FDE8D1E005C0F45 /* About.storyboard in Resources */, - CF9E03201FBD9FA80084BC88 /* ItemHistory.storyboard in Resources */, - CFE698461F974B9700CD7905 /* LaunchScreen.storyboard in Resources */, - CFCE5FCB1F978007005C672B /* Servings.storyboard in Resources */, - CFE698431F974B9700CD7905 /* Assets.xcassets in Resources */, - CF22F07F1FA8ACE000F25B70 /* Details.plist in Resources */, - CF64DF2C1FB5CD5100B4D124 /* TypesHeader.xib in Resources */, - CF9051512022F71B00F4C1F2 /* dr_greger.png in Resources */, - CFA9CDF21FBAF653000468CA /* VitaminsHeader.xib in Resources */, - CFA3E8F91FC58D460092199D /* ServingsHistory.storyboard in Resources */, - CF64DF271FB5BAC400B4D124 /* SizesHeader.xib in Resources */, - CFE698411F974B9700CD7905 /* Main.storyboard in Resources */, + 493A9BF323CE61E700F679BE /* DozeEntryPagerLayout.storyboard in Resources */, + 493A9C0023CE627B00F679BE /* SettingsLayout.storyboard in Resources */, + 493A9BD723CE4EED00F679BE /* DozeDetailLayout.storyboard in Resources */, + 497B137223CD72E600BC2488 /* InfoMenuAboutLayout.storyboard in Resources */, + 493A9C0623CE629900F679BE /* TweakEntryLayout.storyboard in Resources */, + 49D9B07D240DD09F000E9822 /* TweakDetailActivityUnitHeader.xib in Resources */, + 49D9B07C240DD08F000E9822 /* DozeDetailSizeUnitHeader.xib in Resources */, + 493A9C0A23CE62BC00F679BE /* TweakHistoryLayout.storyboard in Resources */, + 493A9BE623CE4F3700F679BE /* TweakDetailDescriptionHeader.xib in Resources */, + 493A9C0323CE629100F679BE /* TweakEntryPagerLayout.storyboard in Resources */, + 493A9BEC23CE612A00F679BE /* ItemHistoryLayout.storyboard in Resources */, + 495020DA2399DC780092D6F5 /* LaunchScreen.storyboard in Resources */, + 493A9BDA23CE4F0200F679BE /* TweakDetailLayout.storyboard in Resources */, + 493A9BF623CE61F100F679BE /* DozeEntryLayout.storyboard in Resources */, + 492474A323DB677A00D7D918 /* InfoPlist.strings in Resources */, + 49C646CB236F4A46000DA8DC /* UtilityLayout.storyboard in Resources */, + 493A9BE323CE4F2D00F679BE /* DozeDetailTypeHeader.xib in Resources */, + 493A9C1523CE634400F679BE /* WeightHistoryLayout.storyboard in Resources */, + 493A9BF923CE61FD00F679BE /* DozeEntryExtrasHeader.xib in Resources */, + 4955013A23CD453200C72FB3 /* Assets.xcassets in Resources */, + 493A9BE023CE4F1C00F679BE /* TweakDetailActivityHeader.xib in Resources */, + 493A9BFC23CE623700F679BE /* DozeHistoryLayout.storyboard in Resources */, + 4916451D23EE53610077731D /* TweakDetailData.json in Resources */, + 493A9BDD23CE4F1400F679BE /* DozeDetailSizeHeader.xib in Resources */, + 493A9BD323CE47A000F679BE /* Localizable.strings in Resources */, + 497390AC23A5956900492D92 /* Main.storyboard in Resources */, + 493A9C0E23CE631600F679BE /* WeightEntryLayout.storyboard in Resources */, + 493A9C1123CE631F00F679BE /* WeightEntryPagerLayout.storyboard in Resources */, CFF72B001FB9BB70001CC2E9 /* LinkSettings.plist in Resources */, - CF1393111FE0046800DD8679 /* Reminder.storyboard in Resources */, - CFF117731F97530300BFE73A /* Menu.storyboard in Resources */, + 493A9BF023CE61BE00F679BE /* InfoMenuMainLayout.storyboard in Resources */, + 4933A75023EE2BC800CE4631 /* DozeDetailData.json in Resources */, + 498F724B239E013E004E501B /* FirstLaunch.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -711,35 +1224,60 @@ 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; }; - 70F25E4D8F3A74B6E49D3041 /* [CP] Copy Pods Resources */ = { + B928040A54DABDDC50DB0D10 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DailyDozen/Pods-DailyDozen-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/ActiveLabel/ActiveLabel.framework", + "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/FSCalendar/FSCalendar.framework", + "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", + "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", + "${BUILT_PRODUCTS_DIR}/SimpleAnimation/SimpleAnimation.framework", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActiveLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FSCalendar.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SimpleAnimation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DailyDozen/Pods-DailyDozen-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DailyDozen/Pods-DailyDozen-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - B928040A54DABDDC50DB0D10 /* [CP] Embed Pods Frameworks */ = { + CFE6984E1F974CE400CD7905 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + F69A747D623F34AF81001F1E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-DailyDozen/Pods-DailyDozen-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-DailyDozenTests/Pods-DailyDozenTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/ActiveLabel/ActiveLabel.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/FSCalendar/FSCalendar.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/SimpleAnimation/SimpleAnimation.framework", - "${BUILT_PRODUCTS_DIR}/UICheckbox.Swift/UICheckbox_Swift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -749,103 +1287,508 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SimpleAnimation.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UICheckbox_Swift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DailyDozen/Pods-DailyDozen-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DailyDozenTests/Pods-DailyDozenTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - CFE6984E1F974CE400CD7905 /* ShellScript */ = { + F9148B5B6D2D32DAD583C1A4 /* [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-DailyDozenTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + 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; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 498A2272237CD81F007AF5FB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 498A2279237CD81F007AF5FB /* DailyDozenTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 498A2280237CD88A007AF5FB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 498A2287237CD88A007AF5FB /* DailyDozenUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; CFE698341F974B9700CD7905 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CFA3E8FC1FC58E6B0092199D /* ServingsHistoryViewController.swift in Sources */, + CFA3E8FC1FC58E6B0092199D /* DozeHistoryViewController.swift in Sources */, + 4933A75723EE49BB00CE4631 /* TweakDetailInfo.swift in Sources */, + 49D6448C245CEC3300814AD5 /* HealthWeightRecord.swift in Sources */, + 4933A75523EE39CC00CE4631 /* DozeDetailViewModel.swift in Sources */, CFF72AFE1FB9BAAA001CC2E9 /* LinkService.swift in Sources */, - CFC4A8541F9776A500B5AC75 /* ServingsViewController.swift in Sources */, + 49DB104D238E255D00E77E52 /* DozeEntryViewModel.swift in Sources */, + 492A1E722409B41000F6C37C /* TweakDetailDescriptionCell.swift in Sources */, + 496917DD238716BD00A9A743 /* RealmManagerLegacy.swift in Sources */, + CFC4A8541F9776A500B5AC75 /* DozeEntryViewController.swift in Sources */, + 4981709D2391B89E0033882C /* TweakHistoryViewModel.swift in Sources */, + 4916451823EE4E440077731D /* TweakDetailViewController.swift in Sources */, CF4902951FD91412000EAB68 /* ChartView.swift in Sources */, + 498F724D239E08C0004E501B /* FirstLaunchViewController.swift in Sources */, + 4941D733237E13DC00ED84B5 /* RealmProvider.swift in Sources */, + 4944B99223AD7A1A008DFA30 /* ChartDataEntryExtension.swift in Sources */, + 4941D72E237E13DC00ED84B5 /* RealmManager.swift in Sources */, CFAAB3721F9F3BC600B24748 /* Doze.swift in Sources */, - CF22F07A1FA88D2200F25B70 /* DetailsDataProvider.swift in Sources */, - CFE6983E1F974B9700CD7905 /* MainViewController.swift in Sources */, - CF64DF331FB5E03C00B4D124 /* TypesCell.swift in Sources */, + 4933A74D23EE28C500CE4631 /* DozeDetailInfo.swift in Sources */, + CF22F07A1FA88D2200F25B70 /* DozeDetailDataProvider.swift in Sources */, + 497390A923A5941900492D92 /* MainViewController.swift in Sources */, + 49F0377B233AD9B600FFD2EE /* UIButtonCheckbox.swift in Sources */, + CF64DF331FB5E03C00B4D124 /* DozeDetailTypeCell.swift in Sources */, CFAAB3771F9F3FDF00B24748 /* URL.swift in Sources */, - CF6E6D351FB1B4FE00B177E7 /* Detail.swift in Sources */, - CF1393151FE0080900DD8679 /* ReminderViewController.swift in Sources */, - CF6E6D391FB1B8E300B177E7 /* TextsProvider.swift in Sources */, - CFC4A84D1F975A3900B5AC75 /* MenuViewController.swift in Sources */, - CF6E6D3C1FB1BEC400B177E7 /* DetailViewModel.swift in Sources */, + 495FA4212453EEF200DE3E58 /* LogService.swift in Sources */, + 4916451A23EE51310077731D /* TweakDetailDataProvider.swift in Sources */, + 4981709B2391B85B0033882C /* TweakHistoryViewController.swift in Sources */, + 497A8F0A24748E88001BEDCA /* SettingsManager.swift in Sources */, + 4941D731237E13DC00ED84B5 /* DataWeightRecord.swift in Sources */, + 4916451423EE4C410077731D /* TweakDetailViewModel.swift in Sources */, + 499B82FB239CD4D300E4AD19 /* WeightHistoryViewController.swift in Sources */, + 498170A123920DD60033882C /* TweakEntryPagerViewController.swift in Sources */, + 4981708F2391B1790033882C /* TweakEntryStateCell.swift in Sources */, + 49821BAA2383727D006F166E /* DailyTracker.swift in Sources */, + 498F7251239E3007004E501B /* HealthManager.swift in Sources */, + 4904AF84239CB99600E271EC /* WeightEntryPagerViewController.swift in Sources */, + 49F8D1DC23905B70003E139F /* TweakEntryViewController.swift in Sources */, + 498F7246239D90E2004E501B /* WeightEntryViewController.swift in Sources */, + 4930360724760CBD00746D49 /* DatabaseMaintainer.swift in Sources */, + CFC4A84D1F975A3900B5AC75 /* InfoMenuMainTableVC.swift in Sources */, CFE6983C1F974B9700CD7905 /* AppDelegate.swift in Sources */, - CF64DF311FB5DFCF00B4D124 /* SizesCell.swift in Sources */, + CF64DF311FB5DFCF00B4D124 /* DozeDetailSizeCell.swift in Sources */, + 4941D730237E13DC00ED84B5 /* DataCountType.swift in Sources */, + 4941D732237E13DC00ED84B5 /* DataWeightType.swift in Sources */, + 4941D72F237E13DC00ED84B5 /* DataCountRecord.swift in Sources */, CFB5568C1FD180C3003B5813 /* Report.swift in Sources */, + 498170952391B6AB0033882C /* TweakEntrySections.swift in Sources */, CF987C281F9E157300D4893E /* Item.swift in Sources */, + 49B93E502477361100144C80 /* DatabaseBuiltInTest.swift in Sources */, + 4964604623AC524700FBD976 /* PopupPickerView.swift in Sources */, CF9EC1C0202196B100C7B3CA /* Fonts.swift in Sources */, CFBEE2341FDE9A57005C0F45 /* RoundedView.swift in Sources */, + 498F7244239D8B5C004E501B /* WeightHistoryViewModel.swift in Sources */, + 497390B023A5D4F900492D92 /* SettingsViewController.swift in Sources */, CF9EC1C32021D37000C7B3CA /* MenuItem.swift in Sources */, - CF49029B1FD91F01000EAB68 /* ServingsHistoryViewModel.swift in Sources */, - CF2FA9961FA87F0C003508F8 /* DetailsViewController.swift in Sources */, + CF49029B1FD91F01000EAB68 /* DozeHistoryViewModel.swift in Sources */, + 4933A74B23EE267600CE4631 /* DozeTextsProvider.swift in Sources */, + CF2FA9961FA87F0C003508F8 /* DozeDetailViewController.swift in Sources */, CFECDFF8201EF7C1003E8572 /* Colors.swift in Sources */, - CF4DECF71F9DF2A500DA094B /* ServingsCell.swift in Sources */, - CFE793571FA756E40084F075 /* RealmProvider.swift in Sources */, - CFAAB37A1F9F4A8000B24748 /* DozeViewModel.swift in Sources */, + 492A1E702409B3FA00F6C37C /* TweakDetailActivityCell.swift in Sources */, + 497390B123A5D4F900492D92 /* SettingsReminderViewController.swift in Sources */, + CF4DECF71F9DF2A500DA094B /* DozeEntryTableViewCell.swift in Sources */, + 498D76A423709F9D00B5DF06 /* UtilityTableViewController.swift in Sources */, + CFE793571FA756E40084F075 /* RealmProviderLegacy.swift in Sources */, CF4902971FD917D4000EAB68 /* ControlPanel.swift in Sources */, - CFC060931FBC57C800A901A5 /* ServingsSection.swift in Sources */, - CF5B28B11FA0A2C500D57A48 /* StateCell.swift in Sources */, - CFBEE2321FDE963D005C0F45 /* AboutViewController.swift in Sources */, + 497A8F0C2474AFC8001BEDCA /* DataWeightValues.swift in Sources */, + CFC060931FBC57C800A901A5 /* DozeEntrySections.swift in Sources */, + CF5B28B11FA0A2C500D57A48 /* DozeEntryStateCell.swift in Sources */, + CFBEE2321FDE963D005C0F45 /* InfoMenuAboutTableVC.swift in Sources */, CF12918F1FC42DD300D1C17E /* DateCell.swift in Sources */, CF2680021FB477AD004A6200 /* Date.swift in Sources */, CFF72AFB1FB9A499001CC2E9 /* UnitsType.swift in Sources */, - CFC4A84F1F9761FF00B5AC75 /* PagerViewController.swift in Sources */, + 493A8B9A235E93DA00A5009A /* SettingsKeys.swift in Sources */, + CFC4A84F1F9761FF00B5AC75 /* DozeEntryPagerViewController.swift in Sources */, + 4943720D2487702300632053 /* HealthSynchronizer.swift in Sources */, + 495DAAEB23A06C330036077C /* WeightReport.swift in Sources */, CFD1BA331FCD24920057F549 /* RoundedButton.swift in Sources */, + 49D9B07B240DCA1F000E9822 /* UIViewExtension.swift in Sources */, + 49F8D1DE23906B29003E139F /* TweakEntryDataProvider.swift in Sources */, + 498170912391B19E0033882C /* TweakEntryTableViewCell.swift in Sources */, + 4941D735237E266300ED84B5 /* DataCountAttributes.swift in Sources */, CF9E03231FBD9FDE0084BC88 /* ItemHistoryViewController.swift in Sources */, - CF4DECF91F9DF82900DA094B /* ServingsDataProvider.swift in Sources */, - CF17C8A71FAA019600367BF8 /* DetailsSection.swift in Sources */, + 497F91B2240990B600464E4E /* TweakDetailSections.swift in Sources */, + 49DB104F238E257D00E77E52 /* TweakEntryViewModel.swift in Sources */, + CF4DECF91F9DF82900DA094B /* DozeEntryDataProvider.swift in Sources */, + CF17C8A71FAA019600367BF8 /* DozeDetailSections.swift in Sources */, CF55BC4F2020450D00003D0D /* AlertBuilder.swift in Sources */, - CFAAB3741F9F3E1200B24748 /* RealmConfig.swift in Sources */, + 4916451623EE4D360077731D /* TweakTextsProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 498A227C237CD81F007AF5FB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CFE698371F974B9700CD7905 /* DailyDozen */; + targetProxy = 498A227B237CD81F007AF5FB /* PBXContainerItemProxy */; + }; + 498A228A237CD88A007AF5FB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CFE698371F974B9700CD7905 /* DailyDozen */; + targetProxy = 498A2289237CD88A007AF5FB /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ - CFE6983F1F974B9700CD7905 /* Main.storyboard */ = { + 4916451F23EE53610077731D /* TweakDetailData.json */ = { isa = PBXVariantGroup; children = ( - CFE698401F974B9700CD7905 /* Base */, + 4916451E23EE53610077731D /* Base */, + 4916452023EE53640077731D /* es */, ); - name = Main.storyboard; + name = TweakDetailData.json; + sourceTree = ""; + }; + 492474A123DB677A00D7D918 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 492474A223DB677A00D7D918 /* es */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 492A1E75240A054C00F6C37C /* DozeDetailSizeUnitHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 492A1E74240A054C00F6C37C /* Base */, + 492A1E77240A055700F6C37C /* es */, + ); + name = DozeDetailSizeUnitHeader.xib; + sourceTree = ""; + }; + 492A1E7A240A05D600F6C37C /* TweakDetailActivityUnitHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 492A1E79240A05D600F6C37C /* Base */, + 492A1E7C240A05FA00F6C37C /* es */, + ); + name = TweakDetailActivityUnitHeader.xib; + sourceTree = ""; + }; + 4933A75223EE2BC800CE4631 /* DozeDetailData.json */ = { + isa = PBXVariantGroup; + children = ( + 4933A75123EE2BC800CE4631 /* Base */, + 4933A75323EE2BCB00CE4631 /* es */, + ); + name = DozeDetailData.json; + sourceTree = ""; + }; + 493A9BD523CE47A000F679BE /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 493A9BD423CE47A000F679BE /* en */, + 493A9C3223CE6B7500F679BE /* es */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 493A9BD923CE4EED00F679BE /* DozeDetailLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BD823CE4EED00F679BE /* Base */, + 493A9C1F23CE6B7300F679BE /* es */, + ); + name = DozeDetailLayout.storyboard; + sourceTree = ""; + }; + 493A9BDC23CE4F0200F679BE /* TweakDetailLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BDB23CE4F0200F679BE /* Base */, + 493A9C2023CE6B7300F679BE /* es */, + ); + name = TweakDetailLayout.storyboard; + sourceTree = ""; + }; + 493A9BDF23CE4F1400F679BE /* DozeDetailSizeHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 493A9BDE23CE4F1400F679BE /* Base */, + 493A9C2123CE6B7300F679BE /* es */, + ); + name = DozeDetailSizeHeader.xib; + sourceTree = ""; + }; + 493A9BE223CE4F1C00F679BE /* TweakDetailActivityHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 493A9BE123CE4F1C00F679BE /* Base */, + 493A9C2223CE6B7400F679BE /* es */, + ); + name = TweakDetailActivityHeader.xib; sourceTree = ""; }; - CFE698441F974B9700CD7905 /* LaunchScreen.storyboard */ = { + 493A9BE523CE4F2D00F679BE /* DozeDetailTypeHeader.xib */ = { isa = PBXVariantGroup; children = ( - CFE698451F974B9700CD7905 /* Base */, + 493A9BE423CE4F2D00F679BE /* Base */, + 493A9C2323CE6B7400F679BE /* es */, + ); + name = DozeDetailTypeHeader.xib; + sourceTree = ""; + }; + 493A9BE823CE4F3700F679BE /* TweakDetailDescriptionHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 493A9BE723CE4F3700F679BE /* Base */, + 493A9C2423CE6B7400F679BE /* es */, + ); + name = TweakDetailDescriptionHeader.xib; + sourceTree = ""; + }; + 493A9BEE23CE612A00F679BE /* ItemHistoryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BED23CE612A00F679BE /* Base */, + 493A9C2523CE6B7400F679BE /* es */, + ); + name = ItemHistoryLayout.storyboard; + sourceTree = ""; + }; + 493A9BF223CE61BE00F679BE /* InfoMenuMainLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BF123CE61BE00F679BE /* Base */, + 493A9C2623CE6B7400F679BE /* es */, + ); + name = InfoMenuMainLayout.storyboard; + sourceTree = ""; + }; + 493A9BF523CE61E700F679BE /* DozeEntryPagerLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BF423CE61E700F679BE /* Base */, + 493A9C2723CE6B7400F679BE /* es */, + ); + name = DozeEntryPagerLayout.storyboard; + sourceTree = ""; + }; + 493A9BF823CE61F100F679BE /* DozeEntryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BF723CE61F100F679BE /* Base */, + 493A9C2823CE6B7400F679BE /* es */, + ); + name = DozeEntryLayout.storyboard; + sourceTree = ""; + }; + 493A9BFB23CE61FD00F679BE /* DozeEntryExtrasHeader.xib */ = { + isa = PBXVariantGroup; + children = ( + 493A9BFA23CE61FD00F679BE /* Base */, + 493A9C2923CE6B7400F679BE /* es */, + ); + name = DozeEntryExtrasHeader.xib; + sourceTree = ""; + }; + 493A9BFE23CE623700F679BE /* DozeHistoryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9BFD23CE623700F679BE /* Base */, + 493A9C2A23CE6B7500F679BE /* es */, + ); + name = DozeHistoryLayout.storyboard; + sourceTree = ""; + }; + 493A9C0223CE627B00F679BE /* SettingsLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C0123CE627B00F679BE /* Base */, + 493A9C2B23CE6B7500F679BE /* es */, + ); + name = SettingsLayout.storyboard; + sourceTree = ""; + }; + 493A9C0523CE629100F679BE /* TweakEntryPagerLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C0423CE629100F679BE /* Base */, + 493A9C2C23CE6B7500F679BE /* es */, + ); + name = TweakEntryPagerLayout.storyboard; + sourceTree = ""; + }; + 493A9C0823CE629900F679BE /* TweakEntryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C0723CE629900F679BE /* Base */, + 493A9C2D23CE6B7500F679BE /* es */, + ); + name = TweakEntryLayout.storyboard; + sourceTree = ""; + }; + 493A9C0C23CE62BC00F679BE /* TweakHistoryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C0B23CE62BC00F679BE /* Base */, + 493A9C2E23CE6B7500F679BE /* es */, + ); + name = TweakHistoryLayout.storyboard; + sourceTree = ""; + }; + 493A9C1023CE631600F679BE /* WeightEntryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C0F23CE631600F679BE /* Base */, + 493A9C2F23CE6B7500F679BE /* es */, + ); + name = WeightEntryLayout.storyboard; + sourceTree = ""; + }; + 493A9C1323CE631F00F679BE /* WeightEntryPagerLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C1223CE631F00F679BE /* Base */, + 493A9C3023CE6B7500F679BE /* es */, + ); + name = WeightEntryPagerLayout.storyboard; + sourceTree = ""; + }; + 493A9C1723CE634400F679BE /* WeightHistoryLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 493A9C1623CE634400F679BE /* Base */, + 493A9C3123CE6B7500F679BE /* es */, + ); + name = WeightHistoryLayout.storyboard; + sourceTree = ""; + }; + 495020D82399DC780092D6F5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 495020D92399DC780092D6F5 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; + 497390AA23A5956900492D92 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 497390AB23A5956900492D92 /* Base */, + 493A9C1E23CE6B7300F679BE /* es */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 497B137423CD72E600BC2488 /* InfoMenuAboutLayout.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 497B137323CD72E600BC2488 /* Base */, + 493A9C1C23CE6B7300F679BE /* es */, + ); + name = InfoMenuAboutLayout.storyboard; + sourceTree = ""; + }; + 498F7249239E013E004E501B /* FirstLaunch.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 498F724A239E013E004E501B /* Base */, + 493A9C1D23CE6B7300F679BE /* es */, + ); + name = FirstLaunch.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 498A227D237CD81F007AF5FB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2CB41DDE201AD844603D7163 /* Pods-DailyDozenTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2MG57YUZL5; + INFOPLIST_FILE = DailyDozenTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.nutritionfacts.apple.DailyDozenTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DailyDozen.app/DailyDozen"; + }; + name = Debug; + }; + 498A227E237CD81F007AF5FB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6ED3A55B48F7069CAE75156C /* Pods-DailyDozenTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2MG57YUZL5; + INFOPLIST_FILE = DailyDozenTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.nutritionfacts.apple.DailyDozenTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DailyDozen.app/DailyDozen"; + }; + name = Release; + }; + 498A228C237CD88A007AF5FB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2MG57YUZL5; + INFOPLIST_FILE = DailyDozenUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.nutritionfacts.apple.DailyDozenUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DailyDozen; + }; + name = Debug; + }; + 498A228D237CD88A007AF5FB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2MG57YUZL5; + INFOPLIST_FILE = DailyDozenUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.nutritionfacts.apple.DailyDozenUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = DailyDozen; + }; + name = Release; + }; CFE698481F974B9700CD7905 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -856,6 +1799,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -863,6 +1807,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -890,7 +1835,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -903,6 +1848,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -913,6 +1859,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -920,6 +1867,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -941,7 +1889,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -954,14 +1902,17 @@ baseConfigurationReference = DA1790B64ED37B6DB70223BE /* Pods-DailyDozen.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = DailyDozen/DailyDozen.entitlements; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = 2MG57YUZL5; INFOPLIST_FILE = "$(SRCROOT)/DailyDozen/App/SupportingFiles/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 3.2.1; PRODUCT_BUNDLE_IDENTIFIER = com.nutritionfacts.dailydozen; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -971,14 +1922,17 @@ baseConfigurationReference = 7D4AA01331AD98C99ADB2BC0 /* Pods-DailyDozen.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = DailyDozen/DailyDozen.entitlements; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = 2MG57YUZL5; INFOPLIST_FILE = "$(SRCROOT)/DailyDozen/App/SupportingFiles/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 3.2.1; PRODUCT_BUNDLE_IDENTIFIER = com.nutritionfacts.dailydozen; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; @@ -986,6 +1940,24 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 498A227F237CD81F007AF5FB /* Build configuration list for PBXNativeTarget "DailyDozenTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 498A227D237CD81F007AF5FB /* Debug */, + 498A227E237CD81F007AF5FB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 498A228B237CD88A007AF5FB /* Build configuration list for PBXNativeTarget "DailyDozenUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 498A228C237CD88A007AF5FB /* Debug */, + 498A228D237CD88A007AF5FB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; CFE698331F974B9700CD7905 /* Build configuration list for PBXProject "DailyDozen" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DailyDozen/DailyDozen.xcodeproj/xcshareddata/xcschemes/DailyDozen.xcscheme b/DailyDozen/DailyDozen.xcodeproj/xcshareddata/xcschemes/DailyDozen.xcscheme new file mode 100644 index 00000000..c158193f --- /dev/null +++ b/DailyDozen/DailyDozen.xcodeproj/xcshareddata/xcschemes/DailyDozen.xcscheme @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DailyDozen/DailyDozen.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/DailyDozen/DailyDozen.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/DailyDozen/DailyDozen/.DS_Store b/DailyDozen/DailyDozen/.DS_Store deleted file mode 100644 index f5bfb49b..00000000 Binary files a/DailyDozen/DailyDozen/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/About/.DS_Store b/DailyDozen/DailyDozen/About/.DS_Store deleted file mode 100644 index ff948ecf..00000000 Binary files a/DailyDozen/DailyDozen/About/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/.DS_Store b/DailyDozen/DailyDozen/App/.DS_Store deleted file mode 100644 index 1f013747..00000000 Binary files a/DailyDozen/DailyDozen/App/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/AppDelegate.swift b/DailyDozen/DailyDozen/App/AppDelegate.swift new file mode 100644 index 00000000..0b7d104c --- /dev/null +++ b/DailyDozen/DailyDozen/App/AppDelegate.swift @@ -0,0 +1,79 @@ +// +// AppDelegate.swift +// DailyDozen +// +// Created by Konstantin Khokhlov on 18.10.17. +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import UserNotifications + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + weak var realmDelegate: RealmDelegate? + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let logger = LogService.shared + logger.logLevel = LogServiceLevel.off + + #if DEBUG + print(":DEBUG:WAYPOINT: AppDelegate didFinishLaunchingWithOptions\n\((URL.inDocuments().path))") + logger.logLevel = LogServiceLevel.verbose + logger.useLogFileDefault() + logger.debug("::::: DEBUG :::::") + logger.debug("AppDelegate didFinishLaunchingWithOptions DEBUG enabled") + + DatabaseBuiltInTest.shared.runSuite() + + logger.debug(":::::::::::::::::\n") + #endif + + #if targetEnvironment(simulator) + logger.debug("::::: SIMULATOR ENVIRONMENT :::::") + let bundle = Bundle(for: type(of: self)) + logger.debug("Bundle & Resources Path:\n\(bundle.bundlePath)\n") + logger.debug("App Documents (RealmDB) Directory:\n\(URL.inDocuments().path)\n") + + /* + // Preliminary integrity checks. + let realmMngrOldCheck = RealmManagerLegacy(workingDirUrl: URL.inDocuments()) + let realmDbOldCheck = realmMngrOldCheck.realmDb + // World Pasta Day: Oct 25, 1995 + let date1995Pasta = Date(datestampKey: "19951025")! + // Add known content to legacy + let dozeCheck = realmDbOldCheck.getDozeLegacy(for: date1995Pasta) + realmDbOldCheck.saveStatesLegacy([true, false, true], id: dozeCheck.items[0].id) // Beans + realmDbOldCheck.saveStatesLegacy([false, true, false], id: dozeCheck.items[2].id) // Other Fruits + */ + + logger.debug(":::::::::::::::::::::::::::::::::\n") + #endif + + // ===== Global Setup ===== + + // ----- Database Setup ----- + DatabaseMaintainer.shared.doMigration() + + // ----- User Interface Setup ----- + // Note: User Interface particulars would be in SceneDelegate for newer impementations. + UILabel.appearance(whenContainedInInstancesOf: [UISegmentedControl.self]).numberOfLines = 0 // :!!!:NOPE: needed for variable number of lines ??? + + // ----- Notification Setup ----- + + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (_, error) in + if let error = error { + logger.debug("AppDelegate didFinishLaunchingWithOptions \(error.localizedDescription)") + } + } + return true + } + + func applicationDidBecomeActive(_ application: UIApplication) { + application.applicationIconBadgeNumber = 0 + } + +} diff --git a/DailyDozen/DailyDozen/App/Controllers/AlertBuilder.swift b/DailyDozen/DailyDozen/App/Controllers/AlertBuilder.swift index 12b0c501..e686c8bb 100644 --- a/DailyDozen/DailyDozen/App/Controllers/AlertBuilder.swift +++ b/DailyDozen/DailyDozen/App/Controllers/AlertBuilder.swift @@ -12,13 +12,9 @@ class AlertBuilder { // MARK: - Nested private struct Strings { - static let vitamins = "VITAMINS" - static let message = """ - Vitamin B12 and Vitamin D are essential for your health but do not count towards your daily servings. - - They are included in this app to provide you with an easy way to track your intake. - """ - static let confirm = "OK" + static let dozeOtherInfoTitle = NSLocalizedString("dozeOtherInfo.title", comment: "Daily Dozen other info title") + static let dozeOtherInfoMessage = NSLocalizedString("dozeOtherInfo.message", comment: "Daily Dozen other info message") + static let dozeOtherInfoConfirm = NSLocalizedString("dozeOtherInfo.confirm", comment: "Daily Dozen other info confirm") } private struct Keys { @@ -29,43 +25,42 @@ class AlertBuilder { enum AlertContent { - case vitamin + case dietarySupplement var title: String { switch self { - case .vitamin: - return Strings.vitamins + case .dietarySupplement: + return Strings.dozeOtherInfoTitle } } var message: String { switch self { - case .vitamin: - return Strings.message + case .dietarySupplement: + return Strings.dozeOtherInfoMessage } } } static func instantiateController(for content: AlertContent) -> UIAlertController { - let alert = UIAlertController(title: content.title, message: content.title, preferredStyle: .actionSheet) + let alert = UIAlertController(title: content.title, message: content.message, preferredStyle: .actionSheet) alert.setValue( NSAttributedString( string: content.title, attributes: [ - NSAttributedStringKey.font: UIFont.helveticaBold, - NSAttributedStringKey.foregroundColor: UIColor.greenColor]), + NSAttributedString.Key.font: UIFont.helveticaBold, + NSAttributedString.Key.foregroundColor: UIColor.greenColor]), forKey: Keys.title) - alert.setValue( - NSAttributedString( - string: content.message, - attributes: [ - NSAttributedStringKey.font: UIFont.helevetica, - NSAttributedStringKey.foregroundColor: UIColor.lightGray]), - forKey: Keys.message) + let message = NSAttributedString( + string: content.message, + attributes: [ + NSAttributedString.Key.font: UIFont.helevetica, + NSAttributedString.Key.foregroundColor: UIColor.lightGray]) + alert.setValue(message, forKey: Keys.message) - let action = UIAlertAction(title: Strings.confirm, style: .cancel, handler: nil) + let action = UIAlertAction(title: Strings.dozeOtherInfoConfirm, style: .cancel, handler: nil) action.setValue(UIColor.greenColor, forKey: Keys.textColor) alert.addAction(action) diff --git a/DailyDozen/DailyDozen/App/Controllers/FirstLaunchViewController.swift b/DailyDozen/DailyDozen/App/Controllers/FirstLaunchViewController.swift new file mode 100644 index 00000000..057d8be6 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Controllers/FirstLaunchViewController.swift @@ -0,0 +1,64 @@ +// +// FirstLaunchViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class FirstLaunchBuilder { + + // MARK: Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Returns: The initial view controller in the storyboard. + static func instantiateController() -> FirstLaunchViewController { + let storyboard = UIStoryboard(name: "FirstLaunch", bundle: nil) + guard + let viewController = storyboard + .instantiateInitialViewController() as? FirstLaunchViewController + else { fatalError("Did not instantiate `FirstLaunchViewController`") } + viewController.title = "" + + return viewController + } +} + +class FirstLaunchViewController: UIViewController { + + @IBAction func dailyDozenOnly(_ sender: Any) { + UserDefaults.standard.set(true, forKey: SettingsKeys.hasSeenFirstLaunch) + UserDefaults.standard.set(false, forKey: SettingsKeys.show21TweaksPref) + NotificationCenter.default.post( + name: Notification.Name(rawValue: "NoticeUpdatedShowTweaksTab"), + object: 0, + userInfo: nil) + } + + @IBAction func dozenPlusTweaks(_ sender: Any) { + //NYI: Insert Weight permisisons here + UserDefaults.standard.set(true, forKey: SettingsKeys.hasSeenFirstLaunch) + UserDefaults.standard.set(true, forKey: SettingsKeys.show21TweaksPref) + + NotificationCenter.default.post( + name: Notification.Name(rawValue: "NoticeUpdatedShowTweaksTab"), + object: 1, + userInfo: nil) + } + + func prepareNextViewController() { + UserDefaults.standard.set(true, forKey: "didSee") + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let mainVC = storyboard.instantiateViewController(withIdentifier: "mainVC") + self.navigationController?.setViewControllers([mainVC], animated: true) + mainVC.modalPresentationStyle = .fullScreen + self.present(mainVC, animated: true, completion: nil) + } + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + +} diff --git a/DailyDozen/DailyDozen/App/Controllers/MainViewController.swift b/DailyDozen/DailyDozen/App/Controllers/MainViewController.swift index ea7d8d6c..c39d4573 100644 --- a/DailyDozen/DailyDozen/App/Controllers/MainViewController.swift +++ b/DailyDozen/DailyDozen/App/Controllers/MainViewController.swift @@ -2,56 +2,82 @@ // MainViewController.swift // DailyDozen // -// Created by Konstantin Khokhlov on 18.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// Copyright © 2019 Nutritionfacts.org. All rights reserved. // +// swiftlint:disable function_body_length import UIKit import UserNotifications -class MainViewController: UISplitViewController { +class MainViewController: UIViewController { + + let mainTabBarController = UITabBarController() override func viewDidLoad() { super.viewDidLoad() - + navigationController?.navigationBar.tintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.isTranslucent = false + + // Global App Settings + setupUnitsType() + setupReminders() + setupTabNaviation() + } + + private func setupUnitsType() { + // ----- Settings: Units Type ----- + if UserDefaults.standard.bool(forKey: SettingsKeys.hasSeenFirstLaunch) == false { + // Set true to Show UnitsType Toggle to be similar to legacy user's experience + UserDefaults.standard.set(true, forKey: SettingsKeys.unitsTypeToggleShowPref) + } + + if UserDefaults.standard.object(forKey: SettingsKeys.unitsTypePref) == nil { + // Skip if user had already set a preferred imperial or metric choice + // :TBD:ToBeLocalized: set initial default based on device language + UserDefaults.standard.set(UnitsType.imperial.rawValue, forKey: SettingsKeys.unitsTypePref) + } + } + + private func setupReminders() { + // ----- Settings: Reminders ----- UNUserNotificationCenter.current().delegate = self - UNUserNotificationCenter.current().removeAllPendingNotificationRequests() - if UserDefaults.standard.object(forKey: "canNotificate") == nil { - + if UserDefaults.standard.object(forKey: SettingsKeys.reminderCanNotify) == nil { UNUserNotificationCenter.current().getNotificationSettings { settings in - UserDefaults.standard.set(settings.authorizationStatus == .authorized, forKey: "canNotificate") + UserDefaults.standard.set(settings.authorizationStatus == .authorized, forKey: SettingsKeys.reminderCanNotify) } } - if UserDefaults.standard.object(forKey: "hour") == nil { - UserDefaults.standard.set(8, forKey: "hour") + if UserDefaults.standard.object(forKey: SettingsKeys.reminderHourPref) == nil { + UserDefaults.standard.set(8, forKey: SettingsKeys.reminderHourPref) } - if UserDefaults.standard.object(forKey: "minute") == nil { - UserDefaults.standard.set(0, forKey: "minute") + if UserDefaults.standard.object(forKey: SettingsKeys.reminderMinutePref) == nil { + UserDefaults.standard.set(0, forKey: SettingsKeys.reminderMinutePref) } - if UserDefaults.standard.object(forKey: "sound") == nil { - UserDefaults.standard.set(true, forKey: "sound") + if UserDefaults.standard.object(forKey: SettingsKeys.reminderSoundPref) == nil { + UserDefaults.standard.set(true, forKey: SettingsKeys.reminderSoundPref) } - guard UserDefaults.standard.bool(forKey: "canNotificate") else { return } + guard UserDefaults.standard.bool(forKey: SettingsKeys.reminderCanNotify) else { return } let content = UNMutableNotificationContent() - content.title = "DailyDozen app." - content.subtitle = "Do you remember about the app?" - content.body = "Use this app on a daily basis!" + content.title = "DailyDozen app." // :NYI:ToBeLocalized: + content.subtitle = "Do you remember about the app?" // :NYI:ToBeLocalized: + content.body = "Use this app on a daily basis!" // :NYI:ToBeLocalized: content.badge = 1 - if UserDefaults.standard.bool(forKey: "sound") { - content.sound = UNNotificationSound.default() + if UserDefaults.standard.bool(forKey: SettingsKeys.reminderSoundPref) { + content.sound = UNNotificationSound.default } var dateComponents = DateComponents() - dateComponents.hour = UserDefaults.standard.integer(forKey: "hour") - dateComponents.minute = UserDefaults.standard.integer(forKey: "minute") + dateComponents.hour = UserDefaults.standard.integer(forKey: SettingsKeys.reminderHourPref) + dateComponents.minute = UserDefaults.standard.integer(forKey: SettingsKeys.reminderMinutePref) let dateTrigget = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) @@ -59,14 +85,136 @@ class MainViewController: UISplitViewController { UNUserNotificationCenter.current().add(request) { (error) in if let error = error { - print(error.localizedDescription) + let logger = LogService.shared + logger.warning("MainViewController setupReminders() \(error.localizedDescription)") } } + + } + + private func setupTabNaviation() { + // ----- Tab Navigation Setup ----- + if UserDefaults.standard.bool(forKey: SettingsKeys.hasSeenFirstLaunch) == false { + UserDefaults.standard.set(true, forKey: SettingsKeys.show21TweaksPref) + } + + mainTabBarController.tabBar.barTintColor = UIColor.white + mainTabBarController.tabBar.isTranslucent = false + mainTabBarController.tabBar.tintColor = UIColor.black + updateTabBarController() + + NotificationCenter.default.addObserver( + self, + selector: #selector(updateTabBarController(notification:)), + name: Notification.Name(rawValue: "NoticeUpdatedShowTweaksTab"), + object: nil) } override var preferredStatusBarStyle: UIStatusBarStyle { return UIStatusBarStyle.lightContent } + + // MARK: - Navigation + + @objc func updateTabBarController(notification: Notification) { + if let index = notification.object as? Int { + updateTabBarController(selectedIndex: index) + } else { + updateTabBarController() + } + } + + func updateTabBarController(selectedIndex: Int? = nil) { + var controllerArray = [UIViewController]() + + // Daily Dozen Tab + let tabDailyDozenStoryboard = UIStoryboard(name: "DozeEntryPagerLayout", bundle: nil) + guard + let tabDailyDozenViewController = tabDailyDozenStoryboard + .instantiateInitialViewController() as? DozeEntryPagerViewController + else { fatalError("Did not instantiate `DozeEntryPagerViewController`") } + + let titleDoze = NSLocalizedString("navtab.doze", comment: "Daily Dozen (proper noun) navigation tab") + tabDailyDozenViewController.title = titleDoze + tabDailyDozenViewController.tabBarItem = UITabBarItem( + title: titleDoze, // shows below tab bar item icon + image: UIImage(named: "ic_tabapp_dailydozen"), + tag: 0 + ) + controllerArray.append(tabDailyDozenViewController) + + // Tweaks Tab + if UserDefaults.standard.bool(forKey: SettingsKeys.show21TweaksPref) { + let tab2ndStoryboard = UIStoryboard(name: "TweakEntryPagerLayout", bundle: nil) + guard + let tabTweaksViewController = tab2ndStoryboard + .instantiateInitialViewController() as? TweakEntryPagerViewController + else { fatalError("Did not instantiate `TweakEntryPagerViewController`") } + + let titleTweak = NSLocalizedString("navtab.tweaks", comment: "21 Tweaks (proper noun) navigation tab") + tabTweaksViewController.title = titleTweak + tabTweaksViewController.tabBarItem = UITabBarItem( + title: titleTweak, + image: UIImage(named: "ic_tabapp_21tweaks"), + tag: 1 + ) + controllerArray.append(tabTweaksViewController) + } + + // More Tab + let tabInfoStoryboard = UIStoryboard(name: "InfoMenuMainLayout", bundle: nil) + guard + let tabInfoViewController = tabInfoStoryboard + .instantiateInitialViewController() as? InfoMenuMainTableVC + else { fatalError("Did not instantiate More `InfoMenuMainTableVC`") } + + let titleInfo = NSLocalizedString("navtab.info", comment: "More Information navigation tab") + tabInfoViewController.title = titleInfo + tabInfoViewController.tabBarItem = UITabBarItem( + title: titleInfo, + image: UIImage(named: "ic_tabapp_more"), + tag: 0 + ) + controllerArray.append(tabInfoViewController) + + // Settings Tab + let tabSettingsStoryboard = UIStoryboard(name: "SettingsLayout", bundle: nil) + guard + let tabSettingsViewController = tabSettingsStoryboard + .instantiateInitialViewController() as? SettingsViewController + else { fatalError("Did not instantiate `SettingsViewController`") } + + let titleSettings = NSLocalizedString("navtab.preferences", comment: "Preferences (aka Settings, Configuration) navigation tab. Choose word different from 'Tweaks' translation") + tabSettingsViewController.title = titleSettings + tabSettingsViewController.tabBarItem = UITabBarItem( + title: titleSettings, + image: UIImage(named: "ic_tabapp_settings"), + tag: 0 + ) + controllerArray.append(tabSettingsViewController) + + // Main Nav Bar Controller + var navControllerArray = [UINavigationController]() + for vc in controllerArray { + let navController = UINavigationController(rootViewController: vc) + navControllerArray.append(navController) + } + + mainTabBarController.viewControllers = navControllerArray + if let selectedIndex = selectedIndex { + mainTabBarController.selectedIndex = selectedIndex + } + self.view.addSubview(mainTabBarController.view) + } + + /* + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + } extension MainViewController: UNUserNotificationCenterDelegate { diff --git a/DailyDozen/DailyDozen/App/Extensions/ChartDataEntryExtension.swift b/DailyDozen/DailyDozen/App/Extensions/ChartDataEntryExtension.swift new file mode 100644 index 00000000..568ab9fb --- /dev/null +++ b/DailyDozen/DailyDozen/App/Extensions/ChartDataEntryExtension.swift @@ -0,0 +1,15 @@ +// +// ChartDataEntryExtension.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import Charts + +extension ChartDataEntry { + func toStringXY() -> String { + return String(format: "%.2f\t%.2f", self.x, self.y) + } +} diff --git a/DailyDozen/DailyDozen/App/Extensions/Colors.swift b/DailyDozen/DailyDozen/App/Extensions/Colors.swift index f685d67f..80c318c8 100644 --- a/DailyDozen/DailyDozen/App/Extensions/Colors.swift +++ b/DailyDozen/DailyDozen/App/Extensions/Colors.swift @@ -8,29 +8,91 @@ import UIKit +/// `*Color` suffix indicates NutritionFacts app specific colors. +/// `*ColorNamed` suffix indicates colors named in NutritionFacts Press Media Kit +/// https://brandfolder.com/nutritionfacts/media-kit extension UIColor { + + /// rgb(69,66,82) `#3C4252` + static var blueDarkColor: UIColor { + return UIColor(red: 60/255, green: 66/255, blue: 82/255, alpha: 1) + } + + /// rgb(61,90,108) `#3D5A6C` + static var blueFiordColor: UIColor { + return UIColor(red: 61/255, green: 90/255, blue: 08/255, alpha: 1) + } + /// rgb(239,239,239) `#EFEFEF` Grayscale 93.7% Light + static var grayGalleryColor: UIColor { + return UIColor(red: 239/255, green: 239/255, blue: 239/255, alpha: 1) + } + + /// rgb(41,43,44) `#292B2C` + static var grayJaguarColor: UIColor { + return UIColor(red: 41/255, green: 43/255, blue: 44/255, alpha: 1) + } + + /// rgb(213,213,213) `#D5D5D5` Grayscale 84% (83.52%) + static var grayLightColor: UIColor { + return UIColor(red: 213/255, green: 213/255, blue: 213/255, alpha: 1) + } + + /// rgb(127,192,76) `#7FC04C` "BrandGreen" static var greenColor: UIColor { return UIColor(red: 127/255, green: 192/255, blue: 76/255, alpha: 1) } - - static var lightGreenColor: UIColor { + + /// rgb(174,215,142) `#AED78E` + static var greenLightColor: UIColor { return UIColor(red: 174/255, green: 215/255, blue: 142/255, alpha: 1) } + + /// rgb(108,174,117) `#6CAE75` + static var greenIguanaColor: UIColor { + return UIColor(red: 108/255, green: 174/255, blue: 117/255, alpha: 1) + } + + /// rgb(255, 82, 82) `#FF5252` + static var redCheckmarkColor: UIColor { + return UIColor(red: 255/255, green: 82/255, blue: 82/255, alpha: 1) + } + + /// rgb(198,108,108) `#C66C6C` + static var redDarkColor: UIColor { + return UIColor(red: 198/255, green: 108/255, blue: 108/255, alpha: 1) + } + + /// rgb(228,87,46) `#E4572E` + static var redFlamePeaColor: UIColor { + return UIColor(red: 228/255, green: 87/255, blue: 46/255, alpha: 1) + } + /// rgb(235,193,64) `#EBC140` static var yellowColor: UIColor { return UIColor(red: 235/255, green: 193/255, blue: 64/255, alpha: 1) } - - static var darkRedColor: UIColor { - return UIColor(red: 198/255, green: 108/255, blue: 108/255, alpha: 1) + + /// rgb(253,212,69) `#FDD445` + static var yellowSunglowColor: UIColor { + return UIColor(red: 253/255, green: 212/255, blue: 69/255, alpha: 1) } + + // ** see also: Android …/src/main/res/values/colors.xml ** - static var lightGrayColor: UIColor { - return UIColor(red: 213/255, green: 213/255, blue: 213/255, alpha: 1) + /// use black text. rgb(255, 215, 0) `#FFD700` + static var streakGoldColor: UIColor { + return UIColor(red: 255/255, green: 215/255, blue: 0/255, alpha: 1) } - - static var darkBlueColor: UIColor { - return UIColor(red: 60/255, green: 66/255, blue: 82/255, alpha: 1) + /// use black text. rgb(192, 192, 192) `#C0C0C0` + static var streakSilverColor: UIColor { + return UIColor(red: 192/255, green: 192/255, blue: 192/255, alpha: 1) + } + /// use white text. rgb(140, 120, 83) `#8C7853` + static var streakBronzeColor: UIColor { + return UIColor(red: 140/255, green: 120/255, blue: 83/255, alpha: 1) } + + // #A52A2A + } diff --git a/DailyDozen/DailyDozen/App/Extensions/Date.swift b/DailyDozen/DailyDozen/App/Extensions/Date.swift index 1cc2b69a..b0251a34 100644 --- a/DailyDozen/DailyDozen/App/Extensions/Date.swift +++ b/DailyDozen/DailyDozen/App/Extensions/Date.swift @@ -9,33 +9,137 @@ import Foundation extension Date { - + + /// Return DataWeightType `.am` or `.pm` + var ampm: DataWeightType { + if self.hour < 12 { + return .am + } + return .pm + } + + /// Return yyyyMMdd based on the current locale. + var datestampKey: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd" + return dateFormatter.string(from: self) + } + + var datestampHHmm: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm" + return dateFormatter.string(from: self) + } + + var datestampyyyyMMddHHmmss: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd_HHmmss" + return dateFormatter.string(from: self) + } + + var datestampyyyyMMddHHmmssSSS: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd_HHmmss.SSS" + return dateFormatter.string(from: self) + } + + static func datestampNow() -> String { + let currentTime = Date() + let dateFormatter = DateFormatter() + // filename compatible format + dateFormatter.dateFormat = "yyyyMMdd_HHmmss" + return dateFormatter.string(from: currentTime) + } + + init?(healthkit: String) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd hh:mm a" + if let date = dateFormatter.date(from: healthkit) { + self = date + return + } else { + return nil + } + } + + init?(datestampKey: String) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd" + if let date = dateFormatter.date(from: datestampKey) { + self = date + return + } else { + return nil + } + } + + /// - parameter datastampLong: "yyyyMMdd_HHmmss.SSS" 24-hour millisecond format + init?(datastampLong: String) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd_HHmmss.SSS" + if let date = dateFormatter.date(from: datastampLong) { + self = date + return + } else { + return nil + } + } + + init?(y: Int, m: Int, d: Int) { + let calendar = Calendar.current + var dateComponents: DateComponents = DateComponents() + dateComponents.year = y + dateComponents.month = m + dateComponents.day = d + if let date = calendar.date(from: dateComponents) { + self = date + return + } else { + return nil + } + } + /// Returns a day name from the date. var dayName: String { let dateFormatter = DateFormatter() dateFormatter.setLocalizedDateFormatFromTemplate("EEE") return dateFormatter.string(from: self) } - + /// Returns a day int for the date. var day: Int { return Calendar.current.component(.day, from: self) } - + + var lastDayInMonth: Int { + let month = self.month + let year = self.year + return Date.lastDayInMonth(month: month, year: year) + } + + static func lastDayInMonth(month: Int, year: Int) -> Int { + let cal = Calendar.current + var comps = DateComponents(calendar: cal, year: year, month: month) + comps.setValue(month + 1, for: .month) + comps.setValue(0, for: .day) + let date = cal.date(from: comps)! + return cal.component(.day, from: date) + } + var monthName: String { let dateFormatter = DateFormatter() dateFormatter.setLocalizedDateFormatFromTemplate("MMM") return dateFormatter.string(from: self) } - + var month: Int { return Calendar.current.component(.month, from: self) } - + var year: Int { return Calendar.current.component(.year, from: self) } - + var hour: Int { get { return Calendar.current.component(.hour, from: self) @@ -43,7 +147,7 @@ extension Date { set { let allowedRange = Calendar.current.range(of: .hour, in: .day, for: self)! guard allowedRange.contains(newValue) else { return } - + let currentHour = Calendar.current.component(.hour, from: self) let hoursToAdd = newValue - currentHour if let date = Calendar.current.date(byAdding: .hour, value: hoursToAdd, to: self) { @@ -51,7 +155,7 @@ extension Date { } } } - + public var minute: Int { get { return Calendar.current.component(.minute, from: self) @@ -59,7 +163,7 @@ extension Date { set { let allowedRange = Calendar.current.range(of: .minute, in: .hour, for: self)! guard allowedRange.contains(newValue) else { return } - + let currentMinutes = Calendar.current.component(.minute, from: self) let minutesToAdd = newValue - currentMinutes if let date = Calendar.current.date(byAdding: .minute, value: minutesToAdd, to: self) { @@ -67,7 +171,7 @@ extension Date { } } } - + /// Returns a date string from the date. /// /// - Parameter style: A dateFormatter style (default is .medium). @@ -79,7 +183,7 @@ extension Date { dateFormatter.locale = Locale.current return dateFormatter.string(from: self) } - + /// Checks if a date is within the current date day. /// /// - Parameter date: The current date. @@ -87,7 +191,7 @@ extension Date { func isInCurrentDayWith(_ date: Date) -> Bool { return Calendar.current.isDate(self, equalTo: date, toGranularity: .day) } - + /// Checks if a date is within the current date month. /// /// - Parameter date: The current date. @@ -95,7 +199,7 @@ extension Date { func isInCurrentMonthWith(_ date: Date) -> Bool { return Calendar.current.isDate(self, equalTo: date, toGranularity: .month) } - + /// Returns a new date by adding the calendar component value. /// /// - Parameters: @@ -105,4 +209,31 @@ extension Date { func adding(_ component: Calendar.Component, value: Int) -> Date? { return Calendar.current.date(byAdding: component, value: value, to: self) } + + // :???: candidate code to be either improved or deleted + // var startOfDay: Date { + // return Calendar.current.startOfDay(for: self) + // } + // + // var endOfDay: Date { + // var components = DateComponents() + // components.day = 1 + // components.second = -1 + // return Calendar.current.date(byAdding: components, to: startOfDay)! + // } + + // var startOfMonth: Date { + // let components = Calendar.current.dateComponents([.year, .month], from: startOfDay) + // return Calendar.current.date(from: components)! + // } + // + // var endOfMonth: Date { + // var components = DateComponents() + // components.month = 1 + // components.second = -1 + // return Calendar.current.date(byAdding: components, to: startOfMonth)! + // } + // End of day = Start of tomorrow minus 1 second + // End of month = Start of next month minus 1 second + } diff --git a/DailyDozen/DailyDozen/App/Extensions/Fonts.swift b/DailyDozen/DailyDozen/App/Extensions/Fonts.swift index 80262d5e..ba02bd80 100644 --- a/DailyDozen/DailyDozen/App/Extensions/Fonts.swift +++ b/DailyDozen/DailyDozen/App/Extensions/Fonts.swift @@ -11,16 +11,16 @@ import UIKit extension UIFont { // MARK: - Nested - private struct Keys { + private struct Strings { static let helveticaBold = "Helvetica-Bold" static let helvetica = "Helvetica" } static var helevetica: UIFont { - return UIFont(name: Keys.helvetica, size: 17) ?? UIFont.systemFont(ofSize: 17) + return UIFont(name: Strings.helvetica, size: 17) ?? UIFont.systemFont(ofSize: 17) } static var helveticaBold: UIFont { - return UIFont(name: Keys.helveticaBold, size: 22) ?? UIFont.boldSystemFont(ofSize: 22) + return UIFont(name: Strings.helveticaBold, size: 22) ?? UIFont.boldSystemFont(ofSize: 22) } } diff --git a/DailyDozen/DailyDozen/App/Extensions/UIViewExtension.swift b/DailyDozen/DailyDozen/App/Extensions/UIViewExtension.swift new file mode 100644 index 00000000..d7151f8e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Extensions/UIViewExtension.swift @@ -0,0 +1,18 @@ +// +// UIViewExtension.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +extension UIView { + func subviews(ofType whatType: T.Type) -> [T] { + var result = self.subviews.compactMap {$0 as? T} + for sub in self.subviews { + result.append(contentsOf: sub.subviews(ofType: whatType)) + } + return result + } +} diff --git a/DailyDozen/DailyDozen/App/Extensions/URL.swift b/DailyDozen/DailyDozen/App/Extensions/URL.swift index e26a9700..f39e4f5f 100644 --- a/DailyDozen/DailyDozen/App/Extensions/URL.swift +++ b/DailyDozen/DailyDozen/App/Extensions/URL.swift @@ -2,7 +2,6 @@ // URL.swift // DailyDozen // -// Created by Konstantin Khokhlov on 24.10.17. // Copyright © 2017 Nutritionfacts.org. All rights reserved. // @@ -10,12 +9,58 @@ import Foundation extension URL { - /// Returns a file URL in the documents directory. - /// - /// - Parameter file: A file name. - /// - Returns: A file URL in the documents directory. - static func inDocuments(for file: String) -> URL { - let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - return path[0].appendingPathComponent(file) + /// - Returns: `Documents/` directory URL + static func inDocuments() -> URL { + let fm = FileManager.default + let urlList = fm.urls(for: .documentDirectory, in: .userDomainMask) + return urlList[0] } + + /// - Parameter filename: A file name. + /// - Returns: `Documents/filename` URL + static func inDocuments(filename: String) -> URL { + return URL.inDocuments().appendingPathComponent(filename, isDirectory: false) + } + + /// - Returns: `Library/` directory URL + static func inLibrary() -> URL { + let fm = FileManager.default + let urlList = fm.urls(for: .libraryDirectory, in: .userDomainMask) + return urlList[0] + } + + /// - Parameter filename: A file name. + /// - Returns: `Library/filename` URL + static func inLibrary(filename: String) -> URL { + return URL.inLibrary().appendingPathComponent(filename, isDirectory: false) + } + + /// - Returns: `Library/Database/` directory URL + static func inDatabase() -> URL { + let fm = FileManager.default + let urlList = fm.urls(for: .libraryDirectory, in: .userDomainMask) + let url = urlList[0].appendingPathComponent("Database", isDirectory: true) + return url + } + + /// - Parameter filename: A file name. + /// - Returns: `Library/Database/filename` URL + static func inDatabase(filename: String) -> URL { + return URL.inDatabase().appendingPathComponent(filename, isDirectory: false) + } + + /// - Returns: `Library/Backup/` directory URL + static func inBackup() -> URL { + let fm = FileManager.default + let urlList = fm.urls(for: .libraryDirectory, in: .userDomainMask) + let url = urlList[0].appendingPathComponent("Backup", isDirectory: true) + return url + } + + /// - Parameter filename: A file name. + /// - Returns: `Library/Backup/filename` URL + static func inBackup(filename: String) -> URL { + return URL.inBackup().appendingPathComponent(filename, isDirectory: false) + } + } diff --git a/DailyDozen/DailyDozen/App/Realm/RealmConfig.swift b/DailyDozen/DailyDozen/App/Realm/RealmConfig.swift deleted file mode 100644 index 40fed3b4..00000000 --- a/DailyDozen/DailyDozen/App/Realm/RealmConfig.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// RealmConfig.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 24.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import Foundation -import RealmSwift - -enum RealmConfig { - - private struct Keys { - static let realm = "main.realm" - } - - case servings - - /// A private instance of a Realm for the Servings. - private static let servingsConfig = Realm.Configuration( - fileURL: URL.inDocuments(for: Keys.realm), - objectTypes: [Doze.self, Item.self]) - - /// A public configuration instance of a Realm. - var configuration: Realm.Configuration { - switch self { - case .servings: - return RealmConfig.servingsConfig - } - } - - /// Returns an initial doze for the current date. - /// - /// - Parameter date: The current date. - static func initialDoze(for date: Date) -> Doze { - let items = [ - Item(name: "Beans", states: [false, false, false]), - Item(name: "Berries", states: [false]), - Item(name: "Other Fruits", states: [false, false, false]), - Item(name: "Cruciferous Vegetables", states: [false]), - Item(name: "Greens", states: [false, false]), - Item(name: "Other Vegetables", states: [false, false]), - Item(name: "Flaxseeds", states: [false]), - Item(name: "Nuts", states: [false]), - Item(name: "Spices", states: [false]), - Item(name: "Whole Grains", states: [false, false, false]), - Item(name: "Beverages", states: [false, false, false, false, false]), - Item(name: "Exercise", states: [false]), - Item(name: "Vitamin B12", states: [false]), - Item(name: "Vitamin D", states: [false]) - ] - return Doze(date: date, items: items) - } -} diff --git a/DailyDozen/DailyDozen/App/Realm/RealmProvider.swift b/DailyDozen/DailyDozen/App/Realm/RealmProvider.swift deleted file mode 100644 index 1bd21962..00000000 --- a/DailyDozen/DailyDozen/App/Realm/RealmProvider.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// RealmProvider.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 30.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit -import RealmSwift - -protocol RealmDelegate: AnyObject { - func didUpdateFile() -} - -class RealmProvider { - - private let realm: Realm - private var unsavedDoze: Doze? - - init() { - guard let realm = try? Realm(configuration: RealmConfig.servings.configuration) else { - fatalError("There should be a realm") - } - self.realm = realm - } - - /// Returns a Doze object for the current date. - /// - /// - Returns: The doze. - func getDoze(for date: Date) -> Doze { - let doze = realm - .objects(Doze.self) - .filter { $0.date.isInCurrentDayWith(date) } - .first ?? RealmConfig.initialDoze(for: date) - unsavedDoze = doze - return doze - } - - func getDozes() -> Results { - return realm.objects(Doze.self) - } - - /// Updates an Item object with an ID for new states. - /// - /// - Parameters: - /// - states: The new state. - /// - id: The ID. - func saveStates(_ states: [Bool], with id: String) { - saveDoze() - do { - try realm.write { - realm.create(Item.self, value: ["id": id, "states": states], update: true) - } - } catch { - print(error.localizedDescription) - } - } - - func updateStreak(_ streak: Int, with id: String) { - saveDoze() - do { - try realm.write { - realm.create(Item.self, value: ["id": id, "streak": streak], update: true) - } - } catch { - print(error.localizedDescription) - } - } - - /// Saves the unsaved doze. - private func saveDoze() { - if let doze = unsavedDoze { - do { - try realm.write { - realm.add(doze, update: true) - unsavedDoze = nil - } - } catch { - print(error.localizedDescription) - } - } - } -} diff --git a/DailyDozen/DailyDozen/App/Resources/.DS_Store b/DailyDozen/DailyDozen/App/Resources/.DS_Store deleted file mode 100644 index 7351136e..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/.DS_Store b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/.DS_Store deleted file mode 100644 index 2285a546..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-100x100.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-100x100.png new file mode 100644 index 00000000..a3957866 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-100x100.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x-1.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x-1.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-216x216.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-216x216.png new file mode 100644 index 00000000..2aa138bc Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-216x216.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-24@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-24@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-24@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-24@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-27.5@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-27.5@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-27.5@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-27.5@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x-1.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x-1.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x-1.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x-1.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-42@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-42@2x.png new file mode 100644 index 00000000..30b8897d Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-42@2x.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-86@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-86@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-86@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-86@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-98@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-98@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-98@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/App-Icon-98@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index dc56ea3c..58dc1eab 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,103 +3,103 @@ { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", + "filename" : "App-Icon-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", + "filename" : "App-Icon-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", + "filename" : "App-Icon-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", + "filename" : "App-Icon-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", + "filename" : "App-Icon-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", + "filename" : "App-Icon-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", + "filename" : "App-Icon-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", + "filename" : "App-Icon-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", + "filename" : "App-Icon-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x-1.png", + "filename" : "App-Icon-20x20@2x-1.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", + "filename" : "App-Icon-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x-1.png", + "filename" : "App-Icon-29x29@2x-1.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", + "filename" : "App-Icon-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x-1.png", + "filename" : "App-Icon-40x40@2x-1.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", + "filename" : "App-Icon-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", + "filename" : "App-Icon-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", + "filename" : "App-Icon-83.5x83.5@2x.png", "scale" : "2x" }, { @@ -111,7 +111,7 @@ { "size" : "24x24", "idiom" : "watch", - "filename" : "Icon-24@2x.png", + "filename" : "App-Icon-24@2x.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "38mm" @@ -119,7 +119,7 @@ { "size" : "27.5x27.5", "idiom" : "watch", - "filename" : "Icon-27.5@2x.png", + "filename" : "App-Icon-27.5@2x.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "42mm" @@ -127,21 +127,21 @@ { "size" : "29x29", "idiom" : "watch", - "filename" : "Icon-29@2x.png", + "filename" : "App-Icon-29@2x.png", "role" : "companionSettings", "scale" : "2x" }, { "size" : "29x29", "idiom" : "watch", - "filename" : "Icon-29@3x.png", + "filename" : "App-Icon-29@3x.png", "role" : "companionSettings", "scale" : "3x" }, { "size" : "40x40", "idiom" : "watch", - "filename" : "Icon-40@2x.png", + "filename" : "App-Icon-40@2x.png", "scale" : "2x", "role" : "appLauncher", "subtype" : "38mm" @@ -149,15 +149,23 @@ { "size" : "44x44", "idiom" : "watch", - "filename" : "Icon-44@2x.png", + "filename" : "App-Icon-42@2x.png", "scale" : "2x", - "role" : "longLook", - "subtype" : "42mm" + "role" : "appLauncher", + "subtype" : "40mm" + }, + { + "size" : "50x50", + "idiom" : "watch", + "filename" : "App-Icon-100x100.png", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "44mm" }, { "size" : "86x86", "idiom" : "watch", - "filename" : "Icon-86@2x.png", + "filename" : "App-Icon-86@2x.png", "scale" : "2x", "role" : "quickLook", "subtype" : "38mm" @@ -165,14 +173,23 @@ { "size" : "98x98", "idiom" : "watch", - "filename" : "Icon-98@2x.png", + "filename" : "App-Icon-98@2x.png", "scale" : "2x", "role" : "quickLook", "subtype" : "42mm" }, { - "idiom" : "watch-marketing", + "size" : "108x108", + "idiom" : "watch", + "filename" : "App-Icon-216x216.png", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "44mm" + }, + { "size" : "1024x1024", + "idiom" : "watch-marketing", + "filename" : "ItunesArtwork@2x-1.png", "scale" : "1x" } ], diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-44@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-44@2x.png deleted file mode 100644 index f7bcc51f..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/Icon-44@2x.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x-1.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x-1.png new file mode 100644 index 00000000..3ed426b8 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x-1.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/BrandGreen.colorset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/BrandGreen.colorset/Contents.json new file mode 100644 index 00000000..f8c2c384 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/BrandGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.498", + "alpha" : "1.000", + "blue" : "0.298", + "green" : "0.753" + } + } + } + ] +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/IguanaGreen.colorset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/IguanaGreen.colorset/Contents.json new file mode 100644 index 00000000..14b505a2 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Colors/IguanaGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.424", + "alpha" : "1.000", + "blue" : "0.459", + "green" : "0.682" + } + } + } + ] +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/.DS_Store b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/.DS_Store deleted file mode 100644 index 53d68796..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/Contents.json similarity index 85% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/Contents.json index 74266589..1b39b589 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_exercise.png", + "filename" : "detail_bannerGray.png", "scale" : "1x" }, { diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/detail_bannerGray.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/detail_bannerGray.png new file mode 100644 index 00000000..09d3e956 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_bannerGray.imageset/detail_bannerGray.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beans.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeans.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beans.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeans.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beans.imageset/beans.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeans.imageset/beans.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beans.imageset/beans.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeans.imageset/beans.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/berries.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBerries.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/berries.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBerries.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/berries.imageset/berries.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBerries.imageset/berries.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/berries.imageset/berries.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBerries.imageset/berries.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beverages.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeverages.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beverages.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeverages.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beverages.imageset/beverages.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeverages.imageset/beverages.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/beverages.imageset/beverages.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeBeverages.imageset/beverages.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/exercise.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeExercise.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/exercise.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeExercise.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/exercise.imageset/exercise.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeExercise.imageset/exercise.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/exercise.imageset/exercise.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeExercise.imageset/exercise.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/flaxseeds.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFlaxseeds.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/flaxseeds.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFlaxseeds.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/flaxseeds.imageset/flaxseeds.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFlaxseeds.imageset/flaxseeds.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/flaxseeds.imageset/flaxseeds.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFlaxseeds.imageset/flaxseeds.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_fruits.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFruitsOther.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_fruits.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFruitsOther.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_fruits.imageset/other_fruits.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFruitsOther.imageset/other_fruits.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_fruits.imageset/other_fruits.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeFruitsOther.imageset/other_fruits.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/greens.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeGreens.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/greens.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeGreens.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/greens.imageset/greens.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeGreens.imageset/greens.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/greens.imageset/greens.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeGreens.imageset/greens.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/nuts.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeNuts.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/nuts.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeNuts.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/nuts.imageset/nuts.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeNuts.imageset/nuts.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/nuts.imageset/nuts.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeNuts.imageset/nuts.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/spices.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeSpices.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/spices.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeSpices.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/spices.imageset/spices.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeSpices.imageset/spices.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/spices.imageset/spices.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeSpices.imageset/spices.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/cruciferous_vegetables.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesCruciferous.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/cruciferous_vegetables.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesCruciferous.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/cruciferous_vegetables.imageset/cruciferous_vegetables.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesCruciferous.imageset/cruciferous_vegetables.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/cruciferous_vegetables.imageset/cruciferous_vegetables.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesCruciferous.imageset/cruciferous_vegetables.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_vegetables.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesOther.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_vegetables.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesOther.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_vegetables.imageset/other_vegetables.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesOther.imageset/other_vegetables.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/other_vegetables.imageset/other_vegetables.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeVegetablesOther.imageset/other_vegetables.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/whole_grains.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeWholeGrains.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/whole_grains.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeWholeGrains.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/whole_grains.imageset/whole_grains.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeWholeGrains.imageset/whole_grains.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/whole_grains.imageset/whole_grains.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_dozeWholeGrains.imageset/whole_grains.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/Contents.json new file mode 100644 index 00000000..fac78c29 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakCompleteIntentions.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/detail_tweakCompleteIntentions.jpg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/detail_tweakCompleteIntentions.jpg new file mode 100644 index 00000000..c7022dd0 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakCompleteIntentions.imageset/detail_tweakCompleteIntentions.jpg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/Contents.json new file mode 100644 index 00000000..e25ac9ae --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyBlackCumin.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/detail_tweakDailyBlackCumin.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/detail_tweakDailyBlackCumin.jpeg new file mode 100644 index 00000000..049f4935 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyBlackCumin.imageset/detail_tweakDailyBlackCumin.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/Contents.json similarity index 84% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/Contents.json index d2b64a67..8211da84 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_beverages.png", + "filename" : "detail_tweakDailyCumin.jpeg", "scale" : "1x" }, { diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/detail_tweakDailyCumin.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/detail_tweakDailyCumin.jpeg new file mode 100644 index 00000000..55d5f19e Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyCumin.imageset/detail_tweakDailyCumin.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/Contents.json new file mode 100644 index 00000000..b4eebd4e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyDeflourDiet.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/detail_tweakDailyDeflourDiet.jpg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/detail_tweakDailyDeflourDiet.jpg new file mode 100644 index 00000000..50f601ef Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyDeflourDiet.imageset/detail_tweakDailyDeflourDiet.jpg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/Contents.json new file mode 100644 index 00000000..e17bb923 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyFrontLoad.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/detail_tweakDailyFrontLoad.jpg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/detail_tweakDailyFrontLoad.jpg new file mode 100644 index 00000000..30caec7b Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyFrontLoad.imageset/detail_tweakDailyFrontLoad.jpg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/Contents.json new file mode 100644 index 00000000..fbad38aa --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyGarlic.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/detail_tweakDailyGarlic.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/detail_tweakDailyGarlic.jpeg new file mode 100644 index 00000000..71a89664 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGarlic.imageset/detail_tweakDailyGarlic.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/Contents.json new file mode 100644 index 00000000..cb89f151 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyGinger.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/detail_tweakDailyGinger.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/detail_tweakDailyGinger.jpeg new file mode 100644 index 00000000..669e3a55 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGinger.imageset/detail_tweakDailyGinger.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/Contents.json similarity index 83% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/Contents.json index 68624a89..7e265c88 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_cruciferous_vegetables.png", + "filename" : "detail_tweakDailyGreenTe.jpeg", "scale" : "1x" }, { diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/detail_tweakDailyGreenTe.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/detail_tweakDailyGreenTe.jpeg new file mode 100644 index 00000000..40da50b3 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyGreenTea.imageset/detail_tweakDailyGreenTe.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/Contents.json new file mode 100644 index 00000000..b2590363 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyHydrate.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/detail_tweakDailyHydrate.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/detail_tweakDailyHydrate.jpeg new file mode 100644 index 00000000..cd656b91 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyHydrate.imageset/detail_tweakDailyHydrate.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/Contents.json new file mode 100644 index 00000000..3b2716b2 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyNutriYeast.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/detail_tweakDailyNutriYeast.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/detail_tweakDailyNutriYeast.jpeg new file mode 100644 index 00000000..a7771455 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyNutriYeast.imageset/detail_tweakDailyNutriYeast.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/Contents.json new file mode 100644 index 00000000..ad2270ef --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakDailyTimeRestrict.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/detail_tweakDailyTimeRestrict.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/detail_tweakDailyTimeRestrict.jpeg new file mode 100644 index 00000000..26136985 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakDailyTimeRestrict.imageset/detail_tweakDailyTimeRestrict.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/Contents.json new file mode 100644 index 00000000..e5dd6b13 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakExerciseTiming.heading.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/detail_tweakExerciseTiming.heading.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/detail_tweakExerciseTiming.heading.jpeg new file mode 100644 index 00000000..f8880e5a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakExerciseTiming.imageset/detail_tweakExerciseTiming.heading.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/Contents.json new file mode 100644 index 00000000..5f9a804c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakMeal20Minutes.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/detail_tweakMeal20Minutes.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/detail_tweakMeal20Minutes.jpeg new file mode 100644 index 00000000..803fe57b Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMeal20Minutes.imageset/detail_tweakMeal20Minutes.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/Contents.json new file mode 100644 index 00000000..4ae08437 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakMealNegCal.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/detail_tweakMealNegCal.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/detail_tweakMealNegCal.jpeg new file mode 100644 index 00000000..29da7284 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealNegCal.imageset/detail_tweakMealNegCal.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/Contents.json new file mode 100644 index 00000000..95278b59 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakMealUndistracted.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/detail_tweakMealUndistracted.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/detail_tweakMealUndistracted.jpeg new file mode 100644 index 00000000..9430ae4c Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealUndistracted.imageset/detail_tweakMealUndistracted.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/Contents.json new file mode 100644 index 00000000..b8c2dd4b --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakMealVinegar.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/detail_tweakMealVinegar.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/detail_tweakMealVinegar.jpeg new file mode 100644 index 00000000..6111ec23 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealVinegar.imageset/detail_tweakMealVinegar.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/Contents.json similarity index 84% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/Contents.json index 202d64da..a4b39b85 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_berries.png", + "filename" : "detail_tweakMealWater.jpeg", "scale" : "1x" }, { diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/detail_tweakMealWater.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/detail_tweakMealWater.jpeg new file mode 100644 index 00000000..42be623a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakMealWater.imageset/detail_tweakMealWater.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/Contents.json new file mode 100644 index 00000000..c5fb8f0f --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakNightlyFast.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/detail_tweakNightlyFast.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/detail_tweakNightlyFast.jpeg new file mode 100644 index 00000000..14e64e2a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyFast.imageset/detail_tweakNightlyFast.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/Contents.json new file mode 100644 index 00000000..d8bd65bf --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakNightlySleep.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/detail_tweakNightlySleep.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/detail_tweakNightlySleep.jpeg new file mode 100644 index 00000000..e8ff4507 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlySleep.imageset/detail_tweakNightlySleep.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/Contents.json new file mode 100644 index 00000000..42a1dc50 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakNightlyTrendelenbrug.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/detail_tweakNightlyTrendelenbrug.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/detail_tweakNightlyTrendelenbrug.png new file mode 100644 index 00000000..80112df5 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakNightlyTrendelenbrug.imageset/detail_tweakNightlyTrendelenbrug.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/Contents.json new file mode 100644 index 00000000..610829a4 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "detail_tweakWeightTwice.heading.jpeg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/detail_tweakWeightTwice.heading.jpeg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/detail_tweakWeightTwice.heading.jpeg new file mode 100644 index 00000000..ec270b38 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Details/detail_tweakWeightTwice.imageset/detail_tweakWeightTwice.heading.jpeg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/.DS_Store b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/.DS_Store deleted file mode 100644 index ea97f4fb..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/Contents.json similarity index 84% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/Contents.json index 06a5b274..71b61cd6 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_beans.png", + "filename" : "how-not-to-diet_title.jpg", "scale" : "1x" }, { diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/how-not-to-diet_title.jpg b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/how-not-to-diet_title.jpg new file mode 100644 index 00000000..d224f767 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Images/how-not-to-diet_title.imageset/how-not-to-diet_title.jpg differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/ic_beans.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/ic_beans.png deleted file mode 100644 index 3a74be28..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beans.imageset/ic_beans.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/ic_berries.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/ic_berries.png deleted file mode 100644 index aa361b0f..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_berries.imageset/ic_berries.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/ic_beverages.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/ic_beverages.png deleted file mode 100644 index a9718ad5..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_beverages.imageset/ic_beverages.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/ic_cruciferous_vegetables.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/ic_cruciferous_vegetables.png deleted file mode 100644 index 693c46a6..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_cruciferous_vegetables.imageset/ic_cruciferous_vegetables.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/Contents.json new file mode 100644 index 00000000..4b45fbd3 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_beans.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/ic_beans.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/ic_beans.pdf new file mode 100644 index 00000000..bafcf8b0 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeans.imageset/ic_beans.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/Contents.json new file mode 100644 index 00000000..ad796846 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_berries.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/ic_berries.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/ic_berries.pdf new file mode 100644 index 00000000..65ceb444 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBerries.imageset/ic_berries.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/Contents.json new file mode 100644 index 00000000..72773080 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_beverages.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/ic_beverages.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/ic_beverages.pdf new file mode 100644 index 00000000..faf3d1a1 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeBeverages.imageset/ic_beverages.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/Contents.json new file mode 100644 index 00000000..32a5dced --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_exercise.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/ic_exercise.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/ic_exercise.pdf new file mode 100644 index 00000000..b6773e92 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeExercise.imageset/ic_exercise.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/Contents.json new file mode 100644 index 00000000..11848dc8 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_flaxseeds.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/ic_flaxseeds.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/ic_flaxseeds.pdf new file mode 100644 index 00000000..2a6d94a7 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFlaxseeds.imageset/ic_flaxseeds.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/Contents.json new file mode 100644 index 00000000..28bee2fd --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_other_fruits.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/ic_other_fruits.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/ic_other_fruits.pdf new file mode 100644 index 00000000..b76f44cd Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeFruitsOther.imageset/ic_other_fruits.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/Contents.json new file mode 100644 index 00000000..d86b822e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_greens.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/ic_greens.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/ic_greens.pdf new file mode 100644 index 00000000..9f1a4a96 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeGreens.imageset/ic_greens.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/Contents.json new file mode 100644 index 00000000..37bbcae3 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_nuts.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/ic_nuts.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/ic_nuts.pdf new file mode 100644 index 00000000..65386178 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeNuts.imageset/ic_nuts.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/Contents.json new file mode 100644 index 00000000..3109a2c3 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_spices.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/ic_spices.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/ic_spices.pdf new file mode 100644 index 00000000..e57c6999 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeSpices.imageset/ic_spices.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/Contents.json new file mode 100644 index 00000000..30bfc302 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_cruciferous_vegetables.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/ic_cruciferous_vegetables.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/ic_cruciferous_vegetables.pdf new file mode 100644 index 00000000..d30e728c Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesCruciferous.imageset/ic_cruciferous_vegetables.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/Contents.json new file mode 100644 index 00000000..d7d9970b --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_other_vegetables.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/ic_other_vegetables.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/ic_other_vegetables.pdf new file mode 100644 index 00000000..5d9a9636 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeVegetablesOther.imageset/ic_other_vegetables.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/Contents.json new file mode 100644 index 00000000..9923b460 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_whole_grains.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/ic_whole_grains.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/ic_whole_grains.pdf new file mode 100644 index 00000000..ebcfa2b2 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_dozeWholeGrains.imageset/ic_whole_grains.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/ic_exercise.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/ic_exercise.png deleted file mode 100644 index 0e3218be..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_exercise.imageset/ic_exercise.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/Contents.json deleted file mode 100644 index 4e521e1c..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_flaxseeds.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/ic_flaxseeds.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/ic_flaxseeds.png deleted file mode 100644 index 9baca10a..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_flaxseeds.imageset/ic_flaxseeds.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/Contents.json deleted file mode 100644 index be17ff25..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_greens.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/ic_greens.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/ic_greens.png deleted file mode 100644 index 35a1d684..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_greens.imageset/ic_greens.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/Contents.json deleted file mode 100644 index 06f632c6..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_nuts.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/ic_nuts.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/ic_nuts.png deleted file mode 100644 index fb9b9b4f..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_nuts.imageset/ic_nuts.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/Contents.json new file mode 100644 index 00000000..bf9d0e89 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_omega.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/ic_omega.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/ic_omega.pdf new file mode 100644 index 00000000..0e6a8e89 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherOmega3.imageset/ic_omega.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/Contents.json new file mode 100644 index 00000000..3ce6f98c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_vitamin_b12.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/ic_vitamin_b12.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/ic_vitamin_b12.pdf new file mode 100644 index 00000000..612e3e08 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminB12.imageset/ic_vitamin_b12.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/Contents.json new file mode 100644 index 00000000..0a453f03 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_vitamin_d.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/ic_vitamin_d.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/ic_vitamin_d.pdf new file mode 100644 index 00000000..fd290826 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_otherVitaminD.imageset/ic_vitamin_d.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/Contents.json deleted file mode 100644 index 1f5ff39b..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_other_fruits.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/ic_other_fruits.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/ic_other_fruits.png deleted file mode 100644 index 57fd30ad..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_fruits.imageset/ic_other_fruits.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/Contents.json deleted file mode 100644 index a0fc163b..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_other_vegetables.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/ic_other_vegetables.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/ic_other_vegetables.png deleted file mode 100644 index 1c59c58e..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_other_vegetables.imageset/ic_other_vegetables.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_settings_black_24dp.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_settings_black_24dp.imageset/Contents.json index 14648ddb..69c88fd7 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_settings_black_24dp.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_settings_black_24dp.imageset/Contents.json @@ -17,5 +17,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true } } \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/Contents.json deleted file mode 100644 index d5014c26..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_spices.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/ic_spices.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/ic_spices.png deleted file mode 100644 index 1497963a..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_spices.imageset/ic_spices.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/Contents.json new file mode 100644 index 00000000..1e96dfb4 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakCompleteIntentions.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/ic_tweakCompleteIntentions.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/ic_tweakCompleteIntentions.pdf new file mode 100644 index 00000000..c3ca6faf Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakCompleteIntentions.imageset/ic_tweakCompleteIntentions.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/Contents.json new file mode 100644 index 00000000..7f012916 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyBlackCumin.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/ic_tweakDailyBlackCumin.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/ic_tweakDailyBlackCumin.pdf new file mode 100644 index 00000000..25a7e718 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyBlackCumin.imageset/ic_tweakDailyBlackCumin.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/Contents.json new file mode 100644 index 00000000..2f3d350e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyCumin.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/ic_tweakDailyCumin.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/ic_tweakDailyCumin.pdf new file mode 100644 index 00000000..64e7e71c Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyCumin.imageset/ic_tweakDailyCumin.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/Contents.json new file mode 100644 index 00000000..9a8e133b --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyDeflourDiet.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/ic_tweakDailyDeflourDiet.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/ic_tweakDailyDeflourDiet.pdf new file mode 100644 index 00000000..231c57e4 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyDeflourDiet.imageset/ic_tweakDailyDeflourDiet.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/Contents.json new file mode 100644 index 00000000..596ef948 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyFrontLoad.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/ic_tweakDailyFrontLoad.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/ic_tweakDailyFrontLoad.pdf new file mode 100644 index 00000000..5226fce6 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyFrontLoad.imageset/ic_tweakDailyFrontLoad.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/Contents.json new file mode 100644 index 00000000..c91e5925 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyGarlic.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/ic_tweakDailyGarlic.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/ic_tweakDailyGarlic.pdf new file mode 100644 index 00000000..0d963f88 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGarlic.imageset/ic_tweakDailyGarlic.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/Contents.json new file mode 100644 index 00000000..b69a23b5 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyGinger.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/ic_tweakDailyGinger.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/ic_tweakDailyGinger.pdf new file mode 100644 index 00000000..34f0c825 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGinger.imageset/ic_tweakDailyGinger.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/Contents.json new file mode 100644 index 00000000..13828ab1 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyGreenTea.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/ic_tweakDailyGreenTea.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/ic_tweakDailyGreenTea.pdf new file mode 100644 index 00000000..107f305e Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyGreenTea.imageset/ic_tweakDailyGreenTea.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/Contents.json new file mode 100644 index 00000000..be5969c4 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyHydrate.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/ic_tweakDailyHydrate.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/ic_tweakDailyHydrate.pdf new file mode 100644 index 00000000..41fdff68 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyHydrate.imageset/ic_tweakDailyHydrate.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/Contents.json new file mode 100644 index 00000000..056516be --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyNutriYeast.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/ic_tweakDailyNutriYeast.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/ic_tweakDailyNutriYeast.pdf new file mode 100644 index 00000000..7637ee02 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyNutriYeast.imageset/ic_tweakDailyNutriYeast.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/Contents.json new file mode 100644 index 00000000..91a3016c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakDailyTimeRestrict.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/ic_tweakDailyTimeRestrict.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/ic_tweakDailyTimeRestrict.pdf new file mode 100644 index 00000000..b901d6a6 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakDailyTimeRestrict.imageset/ic_tweakDailyTimeRestrict.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/Contents.json new file mode 100644 index 00000000..1d6eb610 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakExerciseTiming.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/ic_tweakExerciseTiming.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/ic_tweakExerciseTiming.pdf new file mode 100644 index 00000000..a2036e36 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakExerciseTiming.imageset/ic_tweakExerciseTiming.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/Contents.json new file mode 100644 index 00000000..ad3303f4 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakMeal20Minutes.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/ic_tweakMeal20Minutes.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/ic_tweakMeal20Minutes.pdf new file mode 100644 index 00000000..585ac61a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMeal20Minutes.imageset/ic_tweakMeal20Minutes.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/Contents.json new file mode 100644 index 00000000..62da0bdb --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakMealNegCal.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/ic_tweakMealNegCal.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/ic_tweakMealNegCal.pdf new file mode 100644 index 00000000..260f66da Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealNegCal.imageset/ic_tweakMealNegCal.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/Contents.json new file mode 100644 index 00000000..d615aeda --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakMealUndistracted.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/ic_tweakMealUndistracted.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/ic_tweakMealUndistracted.pdf new file mode 100644 index 00000000..bbf85611 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealUndistracted.imageset/ic_tweakMealUndistracted.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/Contents.json new file mode 100644 index 00000000..c9cc5622 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakMealVinegar.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/ic_tweakMealVinegar.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/ic_tweakMealVinegar.pdf new file mode 100644 index 00000000..66c2124a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealVinegar.imageset/ic_tweakMealVinegar.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/Contents.json new file mode 100644 index 00000000..c5b6f611 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakMealWater.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/ic_tweakMealWater.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/ic_tweakMealWater.pdf new file mode 100644 index 00000000..ad78d4ef Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakMealWater.imageset/ic_tweakMealWater.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/Contents.json new file mode 100644 index 00000000..9c18951c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakNightlyFast.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/ic_tweakNightlyFast.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/ic_tweakNightlyFast.pdf new file mode 100644 index 00000000..c255697c Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyFast.imageset/ic_tweakNightlyFast.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/Contents.json new file mode 100644 index 00000000..be301442 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakNightlySleep.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/ic_tweakNightlySleep.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/ic_tweakNightlySleep.pdf new file mode 100644 index 00000000..8822e6ea Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlySleep.imageset/ic_tweakNightlySleep.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/Contents.json new file mode 100644 index 00000000..6b698c20 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakNightlyTrendelenbrug.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/ic_tweakNightlyTrendelenbrug.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/ic_tweakNightlyTrendelenbrug.pdf new file mode 100644 index 00000000..155e21fa Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakNightlyTrendelenbrug.imageset/ic_tweakNightlyTrendelenbrug.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/Contents.json new file mode 100644 index 00000000..5ee1fe87 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tweakWeightTwice.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/ic_tweakWeightTwice.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/ic_tweakWeightTwice.pdf new file mode 100644 index 00000000..141e6b92 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_tweakWeightTwice.imageset/ic_tweakWeightTwice.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/Contents.json deleted file mode 100644 index dae7a59e..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_vitamin_b12.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/ic_vitamin_b12.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/ic_vitamin_b12.png deleted file mode 100644 index 64c7ecea..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_b12.imageset/ic_vitamin_b12.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/Contents.json deleted file mode 100644 index 048ae13d..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_vitamin_d.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/ic_vitamin_d.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/ic_vitamin_d.png deleted file mode 100644 index b8f463da..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_vitamin_d.imageset/ic_vitamin_d.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/Contents.json deleted file mode 100644 index 416839fd..00000000 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_whole_grains.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/ic_whole_grains.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/ic_whole_grains.png deleted file mode 100644 index 68159e8f..00000000 Binary files a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Servings/ic_whole_grains.imageset/ic_whole_grains.png and /dev/null differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_calendar.imageset/ic_calendar@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_calendar.imageset/ic_calendar@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/Contents.json new file mode 100644 index 00000000..40100798 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_checkmark_white_green.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/ic_checkmark_white_green.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/ic_checkmark_white_green.pdf new file mode 100644 index 00000000..b8ebc438 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_green.imageset/ic_checkmark_white_green.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/Contents.json new file mode 100644 index 00000000..7ca4201e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_checkmark_white_red.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/ic_checkmark_white_red.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/ic_checkmark_white_red.pdf new file mode 100644 index 00000000..fcade26d Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_checkmark_white_red.imageset/ic_checkmark_white_red.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left.imageset/ic_left@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left.imageset/ic_left@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_left_double.imageset/ic_left_double@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_left_double.imageset/ic_left_double@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/Contents.json similarity index 89% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/Contents.json index d4915996..27c95f00 100644 --- a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/Contents.json +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/Contents.json @@ -12,6 +12,7 @@ }, { "idiom" : "universal", + "filename" : "ic_menu@3x.png", "scale" : "3x" } ], diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/ic_menu.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/ic_menu.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/ic_menu@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_menu.imageset/ic_menu@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu@3x.png new file mode 100644 index 00000000..7f484dda Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_menu.imageset/ic_menu@3x.png differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right.imageset/ic_right@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right.imageset/ic_right@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_right_double.imageset/ic_right_double@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_right_double.imageset/ic_right_double@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_star.imageset/ic_star@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_star.imageset/ic_star@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/ic_stat.imageset/ic_stat@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/ic_stat.imageset/ic_stat@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/Contents.json similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/Contents.json rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/Contents.json diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info@2x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info@2x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info@2x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info@2x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info@3x.png b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info@3x.png similarity index 100% rename from DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Icons/music_info.imageset/music_info@3x.png rename to DailyDozen/DailyDozen/App/Resources/Assets.xcassets/Small/music_info.imageset/music_info@3x.png diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/Contents.json new file mode 100644 index 00000000..f98d045e --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tabnav_21tweaks.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/ic_tabnav_21tweaks.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/ic_tabnav_21tweaks.pdf new file mode 100644 index 00000000..5607c84a Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_21tweaks.imageset/ic_tabnav_21tweaks.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/Contents.json new file mode 100644 index 00000000..5d239917 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tabnav_Daily12_square.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/ic_tabnav_Daily12_square.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/ic_tabnav_Daily12_square.pdf new file mode 100644 index 00000000..20f8c2f4 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_dailydozen.imageset/ic_tabnav_Daily12_square.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/Contents.json new file mode 100644 index 00000000..157a9de5 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_tabnav_more_30x30.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/ic_tabnav_more_30x30.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/ic_tabnav_more_30x30.pdf new file mode 100644 index 00000000..bc84b70e Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_more.imageset/ic_tabnav_more_30x30.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/1295308_30x30.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/1295308_30x30.pdf new file mode 100644 index 00000000..3cee3bb7 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/1295308_30x30.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/Contents.json new file mode 100644 index 00000000..03d9cb74 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_settings.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "1295308_30x30.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/27130_30x30.pdf b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/27130_30x30.pdf new file mode 100644 index 00000000..fc8e2255 Binary files /dev/null and b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/27130_30x30.pdf differ diff --git a/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/Contents.json b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/Contents.json new file mode 100644 index 00000000..b42060d5 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Resources/Assets.xcassets/TabApp/ic_tabapp_stats.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "27130_30x30.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/DailyDozen/DailyDozen/App/Storyboards/Base.lproj/Main.storyboard b/DailyDozen/DailyDozen/App/Storyboards/Base.lproj/Main.storyboard deleted file mode 100644 index e621a85a..00000000 --- a/DailyDozen/DailyDozen/App/Storyboards/Base.lproj/Main.storyboard +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DailyDozen/DailyDozen/App/SupportingFiles/AppDelegate.swift b/DailyDozen/DailyDozen/App/SupportingFiles/AppDelegate.swift deleted file mode 100644 index 22d29c74..00000000 --- a/DailyDozen/DailyDozen/App/SupportingFiles/AppDelegate.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// AppDelegate.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 18.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit -import UserNotifications - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - // MARK: - Nested - private struct Keys { - static let extens = "realm" - static let main = "main.realm" - static let title = "Restore Backup" - static let message = "Any existing data will be deleted before restoring from backup. Do you wish to continue?" - static let confirm = "OK" - static let decline = "NO" - } - - weak var realmDelegate: RealmDelegate? - - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (_, error) in - if let error = error { - print(error.localizedDescription) - } - } - return true - } - - func applicationDidBecomeActive(_ application: UIApplication) { - application.applicationIconBadgeNumber = 0 - } - - func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { - guard url.pathExtension == Keys.extens else { return false } - - let importAlert = UIAlertController(title: Keys.title, - message: Keys.message, - preferredStyle: .alert) - let confirm = UIAlertAction(title: Keys.confirm, style: .default) { [weak self] (_) in - try? FileManager.default.removeItem(at: URL.inDocuments(for: Keys.main)) - try? FileManager.default.copyItem(at: url, to: URL.inDocuments(for: Keys.main)) - self?.realmDelegate?.didUpdateFile() - } - importAlert.addAction(confirm) - - let decline = UIAlertAction(title: Keys.decline, style: .cancel, handler: nil) - importAlert.addAction(decline) - - window?.rootViewController?.show(importAlert, sender: nil) - return true - } -} diff --git a/DailyDozen/DailyDozen/App/SupportingFiles/Info.plist b/DailyDozen/DailyDozen/App/SupportingFiles/Info.plist index ff25061d..1b0b6c22 100644 --- a/DailyDozen/DailyDozen/App/SupportingFiles/Info.plist +++ b/DailyDozen/DailyDozen/App/SupportingFiles/Info.plist @@ -30,11 +30,19 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.6 + $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + + NSHealthShareUsageDescription + The 21 Tweaks feature accesses your HealthKit data to provide updates to your daily weight measurements. + NSHealthUpdateUsageDescription + The 21 Tweaks feature can write weight data to HealthKit to help you keep track of your progress. + UIFileSharingEnabled + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -60,6 +68,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIUserInterfaceStyle + Light UTExportedTypeDeclarations diff --git a/DailyDozen/DailyDozen/App/Services/LinkService.swift b/DailyDozen/DailyDozen/App/SupportingFiles/LinkService.swift similarity index 69% rename from DailyDozen/DailyDozen/App/Services/LinkService.swift rename to DailyDozen/DailyDozen/App/SupportingFiles/LinkService.swift index 2c303990..5506d332 100644 --- a/DailyDozen/DailyDozen/App/Services/LinkService.swift +++ b/DailyDozen/DailyDozen/App/SupportingFiles/LinkService.swift @@ -11,7 +11,7 @@ import Foundation class LinksService { // MARK: - Nested - private struct Keys { + private struct Strings { static let baseURL = "base_url" } @@ -22,8 +22,9 @@ class LinksService { } private struct About { - static let christi = "https://github.com/christirichards" - static let const = "https://github.com/justaninja" + static let githubChristi = "https://github.com/christirichards" + static let githubConst = "https://github.com/justaninja" + static let githubMarc = "https://github.com/marc-medley" static let elements = "https://sketchapp.com/elements" } @@ -40,16 +41,20 @@ class LinksService { } var team: URL { - return baseURL - .appendingPathComponent(URLKeys.team) + let urlSegment = NSLocalizedString("urlSegmentInfoMenu.team", comment: "team") + return baseURL.appendingPathComponent(urlSegment) } var aboutChristi: URL? { - return URL(string: About.christi) + return URL(string: About.githubChristi) } var aboutConst: URL? { - return URL(string: About.const) + return URL(string: About.githubConst) + } + + var aboutMarc: URL? { + return URL(string: About.githubMarc) } var aboutElements: URL? { @@ -68,7 +73,7 @@ class LinksService { fatalError("There should be a settings dictionary") } - guard let urlString = dictionary[Keys.baseURL] as? String, + guard let urlString = dictionary[Strings.baseURL] as? String, let url = URL(string: urlString) else { fatalError("There should be a base URL") } @@ -85,18 +90,15 @@ class LinksService { /// /// - Parameter topic: The current topic. /// - Returns: A url. - func link(forTopic topic: String) -> URL { - return baseURL - .appendingPathComponent(URLKeys.topics) - .appendingPathComponent(topic) + func link(topic: String) -> URL { + return baseURL.appendingPathComponent(topic) } /// Returns a url for the current menu item. /// /// - Parameter menu: The current menu item. /// - Returns: A url. - func link(forMenu menu: String) -> URL { - return baseURL - .appendingPathComponent(menu) + func link(menu: String) -> URL { + return baseURL.appendingPathComponent(menu) } } diff --git a/DailyDozen/DailyDozen/App/Services/LinkSettings.plist b/DailyDozen/DailyDozen/App/SupportingFiles/LinkSettings.plist similarity index 100% rename from DailyDozen/DailyDozen/App/Services/LinkSettings.plist rename to DailyDozen/DailyDozen/App/SupportingFiles/LinkSettings.plist diff --git a/DailyDozen/DailyDozen/App/SupportingFiles/LogService.swift b/DailyDozen/DailyDozen/App/SupportingFiles/LogService.swift new file mode 100644 index 00000000..4ad738ca --- /dev/null +++ b/DailyDozen/DailyDozen/App/SupportingFiles/LogService.swift @@ -0,0 +1,176 @@ +// +// LogService.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +/// +/// - Note: Be sure to set the "DEBUG" symbol in the compiler flags for the development build. +/// +/// `Build Settings` > `All, Levels` > `Swift Compiler` - `Custom Flags/Other Swift Flags` > +/// `(+) -D DEBUG` +public enum LogServiceLevel: Int, Comparable { + case all = 6 // highest verbosity + case verbose = 5 + case debug = 4 + case info = 3 + case warning = 2 + case error = 1 + case off = 0 + + /// Get string description for log level. + /// + /// - parameter logLevel: A LogLevel + /// + /// - returns: A string. + public static func description(logLevel: LogServiceLevel) -> String { + switch logLevel { + case .all: return "all" + case .verbose: return "verbose" + case .debug: return "debug" + case .info: return "info" + case .warning: return "warning" + case .error: return "error" + case .off: return "off" + //default: assertionFailure("Invalid level") + //return "Null" + } + } + + // Set the "DEBUG" symbol in the compiler flags + #if DEBUG + static public let defaultLevel = LogServiceLevel.all + #else + static public let defaultLevel = LogServiceLevel.warning + #endif +} + +public func < (lhs: LogServiceLevel, rhs: LogServiceLevel) -> Bool { + return lhs.rawValue < rhs.rawValue +} + +public func == (lhs: LogServiceLevel, rhs: LogServiceLevel) -> Bool { + return lhs.rawValue == rhs.rawValue +} + +public class LogService { + + static var shared = LogService() + + /// Current log level. + public var logLevel = LogServiceLevel.defaultLevel + + /// Log line counter + private var lineCount = 0 + /// Log line numbers to watch: [Int] + public var watchpointList: [Int] = [] + /// + private var logfileUrl: URL? + + /// DateFromatter used internally. + private let dateFormatter = DateFormatter() + + public init() { + dateFormatter.locale = Locale(identifier: "en_US_POSIX") //24H + dateFormatter.dateFormat = "yyyyMMdd_HHmmss.SSS" + + /// LogFunction used, `print` for DEBUG, file for Production. + #if DEBUG + + #else + // :NYI: production would use the default file + #endif + } + + // public func message + public func verbose(_ s: String) { + if .verbose <= logLevel { + log(s) + } + } + + public func debug(_ s: String) { + if .debug <= logLevel { + log(s) + } + } + + public func info(_ s: String) { + if .info <= logLevel { + log(s) + } + } + + public func warning(_ s: String) { + if .warning <= logLevel { + log(s) + } + } + + public func error(_ s: String) { + if .error <= logLevel { + log(s) + } + } + + private func log(_ string: String) { + lineCount += 1 + var logString = "[[\(lineCount)]] \(string)" + #if DEBUG + if watchpointList.contains(lineCount) { + logString = ":::WATCHPOINT::: [[\(lineCount)]]\n" + logString + } + #endif + + if let url = logfileUrl { + do { + let fileHandle = try FileHandle(forWritingTo: url) + fileHandle.seekToEndOfFile() + if let data = (logString + "\n").data(using: .utf8) { + fileHandle.write( data ) + } + fileHandle.closeFile() + #if DEBUG + print(logString) + #endif + } catch { + #if DEBUG + print("FAIL: could not append to \(url.absoluteString)") + print(logString) + #endif + } + } else { + print(logString) + } + } + + public func useLogFileDefault() { + useLogFile(nameToken: "shared") + } + + /// - parameter nameToken: string included in file name + public func useLogFile(nameToken: String) { + let currentTime = Date() + let formatter = DateFormatter() + formatter.dateFormat = "yyyyMMdd_HHmmss" + //formatter.timeZone = NSTimeZone(abbreviation: "UTC") + let dateTimestamp = formatter.string(from: currentTime) + + let logfileName = "log-\(nameToken)-\(dateTimestamp).txt" + logfileUrl = URL.inDocuments(filename: logfileName) + + do { + if let url = logfileUrl { + try "FILE: \(logfileName)\n".write(to: url, atomically: true, encoding: String.Encoding.utf8) + } else { + print(":FAIL: LogService useLogFile() logfileUrl is nil") + } + } catch { + print(":FAIL: LogService useLogFile() could not write initial line to \(logfileName)") + } + } + +} diff --git a/DailyDozen/DailyDozen/App/SupportingFiles/UnitsType.swift b/DailyDozen/DailyDozen/App/SupportingFiles/UnitsType.swift new file mode 100644 index 00000000..1d8af802 --- /dev/null +++ b/DailyDozen/DailyDozen/App/SupportingFiles/UnitsType.swift @@ -0,0 +1,36 @@ +// +// UnitsType.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +/// Units System Type: imperial or metric +/// +/// Related Localization Files: +/// * DozeDetailSizeUnitHeader +/// * Localizable.strings +/// * SettingsLayout +/// * TweakDetailActivityUnitHeader +enum UnitsType: String { + + case imperial + case metric + + /// Returns localized name for the unit system currently in use. + var title: String { + switch self { + case .imperial: + return NSLocalizedString("unitToggle.imperial", comment: "Units toggle button text: imperial measurement system") + case .metric: + return NSLocalizedString("unitToggle.metric", comment: "Units toggle button text: metric measurement system") + } + } + + /// Returns toggled type for the current type. + var toggledType: UnitsType { + return self == .imperial ? UnitsType.metric : UnitsType.imperial + } +} diff --git a/DailyDozen/DailyDozen/App/SupportingFiles/es.lproj/InfoPlist.strings b/DailyDozen/DailyDozen/App/SupportingFiles/es.lproj/InfoPlist.strings new file mode 100644 index 00000000..f487b192 --- /dev/null +++ b/DailyDozen/DailyDozen/App/SupportingFiles/es.lproj/InfoPlist.strings @@ -0,0 +1,12 @@ +/* Bundle name */ +"CFBundleName" = "DailyDozen"; + +/* (No Comment) */ +"DailyDozen" = "DailyDozen"; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "La función 21 Ajustes accede a sus datos de HealthKit para proporcionar actualizaciones a sus mediciones diarias de peso."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "La función 21 Ajustes puede escribir datos de peso en HealthKit para ayudarlo a realizar un seguimiento de su progreso."; + diff --git a/DailyDozen/DailyDozen/App/Texts/Details.plist b/DailyDozen/DailyDozen/App/Texts/Details.plist deleted file mode 100644 index 3a122835..00000000 --- a/DailyDozen/DailyDozen/App/Texts/Details.plist +++ /dev/null @@ -1,1206 +0,0 @@ - - - - - Beans - - Sizes - - Metric - - 60 g of hummus or bean dip - 130 g cooked beans, split peas, lentils, tofu, or tempeh - 150 g of fresh peas or sprouted lentils - - Imperial - - ¼ cup of hummus or bean dip - ½ cup cooked beans, split peas, lentils, tofu, or tempeh - 1 cup of fresh peas or sprouted lentils - - - Types - - - Black beans - black-beans - - - Black-eyed peas - - - - Butter beans - - - - Cannellini beans - - - - Chickpeas - chickpeas - - - Edamame - edamame - - - English peas - - - - Garbanzo beans - garbanzo-beans - - - Great northern beans - - - - Kidney beans - kidney-beans - - - Lentils (beluga, french, and red varieties) - lentils - - - Miso - miso - - - Navy beans - navy-beans - - - Pinto beans - pinto-beans - - - Small red beans - - - - Split peas (yellow or green) - split-peas - - - Tempeh - tempeh - - - Topic - beans - - Berries - - Sizes - - Metric - - 60 g fresh or frozen - 40 g dried - - Imperial - - ½ cup fresh or frozen - ¼ cup dried - - - Types - - - Acai berries - acai-berries - - - Barberries - barberries - - - Blackberries - blackberries - - - Blueberries - blueberries - - - Cherries (sweet or tart) - cherries - - - Concord grapes - concord-grapes - - - Cranberries - cranberries - - - Goji berries - goji-berries - - - Kumquats - - - - Mulberries - - - - Raspberries (black or red) - raspberries - - - Strawberries - strawberries - - - Topic - berries - - Other Fruits - - Sizes - - Metric - - 1 medium-sized fruit - 120 g cut-up fruit - 40 g dried fruit - - Imperial - - 1 medium-sized fruit - 1 cup cut-up fruit - ¼ cup dried fruit - - - Types - - - Apples - apples - - - Dried apricots - apricots - - - Avocados - avocados - - - Bananas - bananas - - - Cantaloupe - cantaloupe - - - Clementines - - - - Dates - dates - - - Dried figs - figs - - - Grapefruit - grapefruit - - - Honeydew - - - - Kiwifruit - kiwifruit - - - Lemons - lemons - - - Limes - limes - - - Lychees - - - - Mangos - mango - - - Nectarines - - - - Oranges - oranges - - - Papaya - papaya - - - Passion fruit - - - - Peaches - peaches - - - Pears - pears - - - Pineapple - pineapples - - - Plums (especially black plums) - plums - - - Pluots - - - - Pomegranates - pomegranates - - - Prunes - prunes - - - Tangerines - - - - Watermelon - watermelon - - - Topic - fruit - - Cruciferous Vegetables - - Sizes - - Metric - - 30–80 g chopped - 12 g Brussels or broccoli sprouts - 1 tablespoon horseradish - - Imperial - - ½ cup chopped - ¼ cup Brussels or broccoli sprouts - 1 tablespoon horseradish - - - Types - - - Arugula - - - - Bok choy - bok-choy - - - Broccoli - broccoli - - - Brussels sprouts - brussels-sprouts - - - Cabbage - cabbage - - - Cauliflower - cauliflower - - - Collard greens - collard-greens - - - Horseradish - - - - Kale (black, green, and red) - kale - - - Mustard greens - mustard-greens - - - Radishes - radishes - - - Turnip greens - turnips - - - Watercress - watercress - - - Topic - cruciferous-vegetables - - Greens - - Sizes - - Metric - - 60 g raw - 90 g cooked - - Imperial - - 1 cup raw - ½ cup cooked - - - Types - - - Arugula - - - - Beet greens - beet-greens - - - Collard greens - collard-greens - - - Kale (black, green, and red) - kale - - - Mesclun mix (assorted young salad greens) - mesclun-mix - - - Mustard greens - mustard-greens - - - Sorrel - - - - Spinach - spinach - - - Swiss chard - swiss-chard - - - Turnip greens - turnips - - - Topic - greens - - Other Vegetables - - Sizes - - Metric - - 60 g raw leafy vegetables - 50 g raw or cooked nonleafy vegetables - 125 ml vegetable juice - 7 g - - Imperial - - 1 cup raw leafy vegetables - ½ cup raw or cooked nonleafy vegetables - ½ cup vegetable juice - ¼ cup dried mushrooms - - - Types - - - Artichokes - artichokes - - - Asparagus - asparagus - - - Beets - beets - - - Bell peppers - bell-peppers - - - Carrots - carrots - - - Corn - corn - - - Garlic - garlic - - - Mushrooms (button, oyster, portobello, and shiitake) - mushrooms - - - Okra - okra - - - Onions - onions - - - Pumpkin - pumpkin - - - Purple potatoes - purple-potatoes - - - Sea vegetables (arame, dulse, and nori) - sea-vegetables - - - Snap peas - - - - Squash (delicata, summer, and spaghetti squash varieties) - squash - - - Sweet potatoes/yams - sweet-potatoes - - - Tomatoes - tomatoes - - - Zucchini - zucchini - - - Topic - vegetables - - Flaxseeds - - Sizes - - Metric - - 1 tablespoon ground - - Imperial - - 1 tablespoon ground - - - Types - - - Brown flaxseeds - - - - Golden flaxseeds - - - - Topic - flax-seeds - - Nuts - - Sizes - - Metric - - 30 g nuts or seeds - 2 tablespoons nut or seed butter - - Imperial - - ¼ cup nuts or seeds - 2 tablespoons nut or seed butter - - - Types - - - Almonds - almonds - - - Brazil nuts - brazil-nuts - - - Cashews - cashews - - - Chia seeds - chia-seeds - - - Hazelnuts - hazelnuts - - - Hemp seeds - - - - Macadamia nuts - macadamia-nuts - - - Pecans - pecans - - - Pistachios - pistachios - - - Pumpkin seeds - pumpkin-seeds - - - Sesame seeds - sesame-seeds - - - Sunflower seeds - sunflower-seeds - - - Walnuts - walnuts - - - Topic - nuts - - Spices - - Sizes - - Metric - - ¼ teaspoon of turmeric - Any other (salt-free) herbs and spices you enjoy - - Imperial - - ¼ teaspoon of turmeric - Any other (salt-free) herbs and spices you enjoy - - - Types - - - Allspice - allspice - - - Barberries - barberries - - - Basil - basil - - - Bay leaves - - - - Cardamom - cardamom - - - Chili powder - - - - Cilantro - cilantro - - - Cinnamon - cinnamon - - - Cloves - cloves - - - Coriander - coriander - - - Cumin - cumin - - - Curry powder - curry-powder - - - Dill - - - - Fenugreek - fenugreek - - - Garlic - garlic - - - Ginger - ginger - - - Horseradish - - - - Lemongrass - lemongrass - - - Marjoram - marjoram - - - Mustard powder - mustard-powder - - - Nutmeg - nutmeg - - - Oregano - oregano - - - Parsley - parsley - - - Pepper - pepper - - - Peppermint - peppermint - - - Rosemary - rosemary - - - Saffron - saffron - - - Sage - - - - Smoked paprika - - - - Thyme - thyme - - - Turmeric - turmeric - - - Vanilla - - - - Topic - spices - - Whole Grains - - Sizes - - Metric - - 100 g hot cereal or cooked grains, pasta, or corn kernels - 50 g cold cereal - 1 tortilla or slice of bread - ½ a bagel or English muffin - 30 g popped popcorn - - Imperial - - ½ cup hot cereal or cooked grains, pasta, or corn kernels - 1 cup cold cereal - 1 tortilla or slice of bread - ½ a bagel or English muffin - 3 cups popped popcorn - - - Types - - - Barley - barley - - - Brown rice - rice - - - Buckwheat - buckwheat - - - Millet - millet - - - Oats - oats - - - Whole-wheat pasta - pasta - - - Popcorn - popcorn - - - Quinoa - quinoa - - - Rye - rye - - - Teff - teff - - - Wild rice - rice - - - Topic - grains - - Beverages - - Sizes - - Metric - - One glass (350 ml) - - Imperial - - One glass (12 ounces) - - - Types - - - Black tea - black-tea - - - Chai tea - chai-tea - - - Vanilla chamomile tea - chamomile-tea - - - Coffee - coffee - - - Earl grey tea - earl-grey-tea - - - Green tea - green-tea - - - Hibiscus tea - hibiscus-tea - - - Hot chocolate - - - - Jasmine tea - jasmine-tea - - - Lemon balm tea - - - - Matcha tea - matcha - - - Almond blossom oolong tea - oolong-tea - - - Peppermint tea - - - - Rooibos tea - rooibos-tea - - - Water - water - - - White tea - white-tea - - - Topic - beverages - - Exercise - - Sizes - - Metric - - 90 minutes of moderate-intensity activity - 40 minutes of vigorous-intensity activity - - Imperial - - 90 minutes of moderate-intensity activity - 40 minutes of vigorous-intensity activity - - - Types - - - Bicycling - - - - Canoeing - - - - Dancing - - - - Dodgeball - - - - Downhill skiing - - - - Fencing - - - - Hiking - - - - Housework - - - - Ice-skating - - - - Inline skating - - - - Juggling - - - - Jumping on a trampoline - - - - Paddle boating - - - - Playing frisbee - - - - Roller-skating - - - - Shooting baskets - - - - Shoveling light snow - - - - Skateboarding - - - - Snorkeling - - - - Surfing - - - - Swimming recreationally - - - - Tennis (doubles) - - - - Treading water - - - - Walking briskly (4 mph) - - - - Water aerobics - - - - Waterskiing - - - - Yard work - - - - Yoga - - - - Backpacking - - - - Basketball - - - - Bicycling uphill - - - - Circuit weight training - - - - Cross-country skiing - - - - Football - - - - Hockey - - - - Jogging - - - - Jumping jacks - - - - Jumping rope - - - - Lacrosse - - - - Push-ups - - - - Pull-ups - - - - Racquetball - - - - Rock climbing - - - - Rugby - - - - Running - - - - Scuba diving - - - - Tennis (singles) - - - - Soccer - - - - Speed skating - - - - Squash - - - - Step aerobics - - - - Swimming laps - - - - Walking briskly uphill - - - - Water jogging - - - - Topic - exercise - - Vitamin B12 - - Sizes - - Metric - - Imperial - - - Types - - - - Topic - vitamin-b12 - - Vitamin D - - Sizes - - Metric - - Imperial - - - Types - - - - Topic - vitamin-d-supplements - - - diff --git a/DailyDozen/DailyDozen/App/Texts/DozeTextsProvider.swift b/DailyDozen/DailyDozen/App/Texts/DozeTextsProvider.swift new file mode 100644 index 00000000..6ed0aac7 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/DozeTextsProvider.swift @@ -0,0 +1,59 @@ +// +// DozeTextsProvider.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +class DozeTextsProvider { + + static let shared: DozeTextsProvider = { + let decoder = JSONDecoder() + guard + let path = Bundle.main.path(forResource: "DozeDetailData", ofType: "json"), + let jsonString = try? String(contentsOfFile: path), + let jsonData = jsonString.data(using: .utf8), + let info = try? decoder.decode(DozeDetailInfo.self, from: jsonData) + else { + fatalError("FAIL DozeTextsProvider did not load 'DozeDetailData.json'") + } + return DozeTextsProvider(info: info) + }() + + private let info: DozeDetailInfo + + init(info: DozeDetailInfo) { + self.info = info + } + + /// Loads static texts for the current item. + /// + /// - Parameter itemName: The current item name. + /// - Returns: A detail view model for static texts. + func getDetails(itemTypeKey: String) -> DozeDetailViewModel { + guard + let itemInfo = info.itemsDict[itemTypeKey] + else { fatalError("DozeTextsProvider getDetails(\(itemTypeKey)) Item not found.") } + return DozeDetailViewModel(itemTypeKey: itemTypeKey, info: itemInfo) + } + + /// Returns the URL topic for the current item name. + /// + /// Use: + /// + /// ``` + /// https://nutritionfacts.org/topics/TOPIC/ + /// ``` + /// + /// - Parameter itemName: The current item name. + /// - Returns: URL path TOPIC component. + func getTopic(itemTypeKey: String) -> String { + guard + let item = info.itemsDict[itemTypeKey] + else { fatalError("DozeTextsProvider getTopic(\(itemTypeKey)) Item not found.") } + return item.topic + } + +} diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/DozeDetailData.json b/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/DozeDetailData.json new file mode 100644 index 00000000..c6c470a0 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/DozeDetailData.json @@ -0,0 +1,1130 @@ +{ + "itemsDict" : { + "dozeBeans" : { + "heading" : "Beans", + "servings" : [ + { + "imperial" : "¼ cup of hummus or bean dip", + "metric" : "60 g of hummus or bean dip" + }, + { + "imperial" : "½ cup cooked beans, split peas, lentils, tofu, or tempeh", + "metric" : "130 g cooked beans, split peas, lentils, tofu, or tempeh" + }, + { + "imperial" : "1 cup of fresh peas or sprouted lentils", + "metric" : "150 g of fresh peas or sprouted lentils" + } + ], + "topic" : "topics/beans", + "varieties" : [ + { + "text" : "Black beans", + "topic" : "topics/black-beans" + }, + { + "text" : "Black-eyed peas", + "topic" : "" + }, + { + "text" : "Butter beans", + "topic" : "" + }, + { + "text" : "Cannellini beans", + "topic" : "" + }, + { + "text" : "Chickpeas", + "topic" : "topics/chickpeas" + }, + { + "text" : "Edamame", + "topic" : "topics/edamame" + }, + { + "text" : "English peas", + "topic" : "" + }, + { + "text" : "Garbanzo beans", + "topic" : "topics/garbanzo-beans" + }, + { + "text" : "Great northern beans", + "topic" : "" + }, + { + "text" : "Kidney beans", + "topic" : "topics/kidney-beans" + }, + { + "text" : "Lentils (beluga, french, and red varieties)", + "topic" : "topics/lentils" + }, + { + "text" : "Miso", + "topic" : "topics/miso" + }, + { + "text" : "Navy beans", + "topic" : "topics/navy-beans" + }, + { + "text" : "Pinto beans", + "topic" : "topics/pinto-beans" + }, + { + "text" : "Small red beans", + "topic" : "" + }, + { + "text" : "Split peas (yellow or green)", + "topic" : "topics/split-peas" + }, + { + "text" : "Tempeh", + "topic" : "topics/tempeh" + } + ] + }, + "dozeBerries" : { + "heading" : "Berries", + "servings" : [ + { + "imperial" : "½ cup fresh or frozen", + "metric" : "60 g fresh or frozen" + }, + { + "imperial" : "¼ cup dried", + "metric" : "40 g dried" + } + ], + "topic" : "topics/berries", + "varieties" : [ + { + "text" : "Acai berries", + "topic" : "topics/acai-berries" + }, + { + "text" : "Barberries", + "topic" : "topics/barberries" + }, + { + "text" : "Blackberries", + "topic" : "topics/blackberries" + }, + { + "text" : "Blueberries", + "topic" : "topics/blueberries" + }, + { + "text" : "Cherries (sweet or tart)", + "topic" : "topics/cherries" + }, + { + "text" : "Concord grapes", + "topic" : "topics/concord-grapes" + }, + { + "text" : "Cranberries", + "topic" : "topics/cranberries" + }, + { + "text" : "Goji berries", + "topic" : "topics/goji-berries" + }, + { + "text" : "Kumquats", + "topic" : "" + }, + { + "text" : "Mulberries", + "topic" : "" + }, + { + "text" : "Raspberries (black or red)", + "topic" : "topics/raspberries" + }, + { + "text" : "Strawberries", + "topic" : "topics/strawberries" + } + ] + }, + "dozeBeverages" : { + "heading" : "Beverages", + "servings" : [ + { + "imperial" : "One glass (12 ounces)", + "metric" : "One glass (350 ml)" + } + ], + "topic" : "topics/beverages", + "varieties" : [ + { + "text" : "Black tea", + "topic" : "topics/black-tea" + }, + { + "text" : "Chai tea", + "topic" : "topics/chai-tea" + }, + { + "text" : "Vanilla chamomile tea", + "topic" : "topics/chamomile-tea" + }, + { + "text" : "Coffee", + "topic" : "topics/coffee" + }, + { + "text" : "Earl grey tea", + "topic" : "topics/earl-grey-tea" + }, + { + "text" : "Green tea", + "topic" : "topics/green-tea" + }, + { + "text" : "Hibiscus tea", + "topic" : "topics/hibiscus-tea" + }, + { + "text" : "Hot chocolate", + "topic" : "" + }, + { + "text" : "Jasmine tea", + "topic" : "topics/jasmine-tea" + }, + { + "text" : "Lemon balm tea", + "topic" : "" + }, + { + "text" : "Matcha tea", + "topic" : "topics/matcha" + }, + { + "text" : "Almond blossom oolong tea", + "topic" : "topics/oolong-tea" + }, + { + "text" : "Peppermint tea", + "topic" : "" + }, + { + "text" : "Rooibos tea", + "topic" : "topics/rooibos-tea" + }, + { + "text" : "Water", + "topic" : "topics/water" + }, + { + "text" : "White tea", + "topic" : "topics/white-tea" + } + ] + }, + "dozeExercise" : { + "heading" : "Exercise", + "servings" : [ + { + "imperial" : "90 minutes of moderate-intensity activity", + "metric" : "90 minutes of moderate-intensity activity" + }, + { + "imperial" : "40 minutes of vigorous-intensity activity", + "metric" : "40 minutes of vigorous-intensity activity" + } + ], + "topic" : "topics/exercise", + "varieties" : [ + { + "text" : "Bicycling", + "topic" : "" + }, + { + "text" : "Canoeing", + "topic" : "" + }, + { + "text" : "Dancing", + "topic" : "" + }, + { + "text" : "Dodgeball", + "topic" : "" + }, + { + "text" : "Downhill skiing", + "topic" : "" + }, + { + "text" : "Fencing", + "topic" : "" + }, + { + "text" : "Hiking", + "topic" : "" + }, + { + "text" : "Housework", + "topic" : "" + }, + { + "text" : "Ice-skating", + "topic" : "" + }, + { + "text" : "Inline skating", + "topic" : "" + }, + { + "text" : "Juggling", + "topic" : "" + }, + { + "text" : "Jumping on a trampoline", + "topic" : "" + }, + { + "text" : "Paddle boating", + "topic" : "" + }, + { + "text" : "Playing frisbee", + "topic" : "" + }, + { + "text" : "Roller-skating", + "topic" : "" + }, + { + "text" : "Shooting baskets", + "topic" : "" + }, + { + "text" : "Shoveling light snow", + "topic" : "" + }, + { + "text" : "Skateboarding", + "topic" : "" + }, + { + "text" : "Snorkeling", + "topic" : "" + }, + { + "text" : "Surfing", + "topic" : "" + }, + { + "text" : "Swimming recreationally", + "topic" : "" + }, + { + "text" : "Tennis (doubles)", + "topic" : "" + }, + { + "text" : "Treading water", + "topic" : "" + }, + { + "text" : "Walking briskly (4 mph)", + "topic" : "" + }, + { + "text" : "Water aerobics", + "topic" : "" + }, + { + "text" : "Waterskiing", + "topic" : "" + }, + { + "text" : "Yard work", + "topic" : "" + }, + { + "text" : "Yoga", + "topic" : "" + }, + { + "text" : "Backpacking", + "topic" : "" + }, + { + "text" : "Basketball", + "topic" : "" + }, + { + "text" : "Bicycling uphill", + "topic" : "" + }, + { + "text" : "Circuit weight training", + "topic" : "" + }, + { + "text" : "Cross-country skiing", + "topic" : "" + }, + { + "text" : "Football", + "topic" : "" + }, + { + "text" : "Hockey", + "topic" : "" + }, + { + "text" : "Jogging", + "topic" : "" + }, + { + "text" : "Jumping jacks", + "topic" : "" + }, + { + "text" : "Jumping rope", + "topic" : "" + }, + { + "text" : "Lacrosse", + "topic" : "" + }, + { + "text" : "Push-ups", + "topic" : "" + }, + { + "text" : "Pull-ups", + "topic" : "" + }, + { + "text" : "Racquetball", + "topic" : "" + }, + { + "text" : "Rock climbing", + "topic" : "" + }, + { + "text" : "Rugby", + "topic" : "" + }, + { + "text" : "Running", + "topic" : "" + }, + { + "text" : "Scuba diving", + "topic" : "" + }, + { + "text" : "Tennis (singles)", + "topic" : "" + }, + { + "text" : "Soccer", + "topic" : "" + }, + { + "text" : "Speed skating", + "topic" : "" + }, + { + "text" : "Squash", + "topic" : "" + }, + { + "text" : "Step aerobics", + "topic" : "" + }, + { + "text" : "Swimming laps", + "topic" : "" + }, + { + "text" : "Walking briskly uphill", + "topic" : "" + }, + { + "text" : "Water jogging", + "topic" : "" + } + ] + }, + "dozeFlaxseeds" : { + "heading" : "Flaxseeds", + "servings" : [ + { + "imperial" : "1 tablespoon ground", + "metric" : "1 tablespoon ground" + } + ], + "topic" : "topics/flax-seeds", + "varieties" : [ + { + "text" : "Brown flaxseeds", + "topic" : "" + }, + { + "text" : "Golden flaxseeds", + "topic" : "" + } + ] + }, + "dozeFruitsOther" : { + "heading" : "Other Fruits", + "servings" : [ + { + "imperial" : "1 medium-sized fruit", + "metric" : "1 medium-sized fruit" + }, + { + "imperial" : "1 cup cut-up fruit", + "metric" : "120 g cut-up fruit" + }, + { + "imperial" : "¼ cup dried fruit", + "metric" : "40 g dried fruit" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Apples", + "topic" : "topics/apples" + }, + { + "text" : "Apricots", + "topic" : "topics/apricots" + }, + { + "text" : "Avocados", + "topic" : "topics/avocados" + }, + { + "text" : "Bananas", + "topic" : "topics/bananas" + }, + { + "text" : "Cantaloupe", + "topic" : "topics/cantaloupe" + }, + { + "text" : "Clementines", + "topic" : "" + }, + { + "text" : "Dates", + "topic" : "topics/dates" + }, + { + "text" : "Figs", + "topic" : "topics/figs" + }, + { + "text" : "Grapefruit", + "topic" : "topics/grapefruit" + }, + { + "text" : "Honeydew", + "topic" : "" + }, + { + "text" : "Kiwifruit", + "topic" : "topics/kiwifruit" + }, + { + "text" : "Lemons", + "topic" : "topics/lemons" + }, + { + "text" : "Limes", + "topic" : "topics/limes" + }, + { + "text" : "Lychees", + "topic" : "" + }, + { + "text" : "Mangos", + "topic" : "topics/mango" + }, + { + "text" : "Nectarines", + "topic" : "" + }, + { + "text" : "Oranges", + "topic" : "topics/oranges" + }, + { + "text" : "Papaya", + "topic" : "topics/papaya" + }, + { + "text" : "Passion fruit", + "topic" : "" + }, + { + "text" : "Peaches", + "topic" : "topics/peaches" + }, + { + "text" : "Pears", + "topic" : "topics/pears" + }, + { + "text" : "Pineapple", + "topic" : "topics/pineapples" + }, + { + "text" : "Plums (especially black plums)", + "topic" : "topics/plums" + }, + { + "text" : "Pluots", + "topic" : "" + }, + { + "text" : "Pomegranates", + "topic" : "topics/pomegranates" + }, + { + "text" : "Prunes", + "topic" : "topics/prunes" + }, + { + "text" : "Tangerines", + "topic" : "" + }, + { + "text" : "Watermelon", + "topic" : "topics/watermelon" + } + ] + }, + "dozeGreens" : { + "heading" : "Greens", + "servings" : [ + { + "imperial" : "1 cup raw", + "metric" : "60 g raw" + }, + { + "imperial" : "½ cup cooked", + "metric" : "90 g cooked" + } + ], + "topic" : "topics/greens", + "varieties" : [ + { + "text" : "Arugula", + "topic" : "" + }, + { + "text" : "Beet greens", + "topic" : "topics/beet-greens" + }, + { + "text" : "Collard greens", + "topic" : "topics/collard-greens" + }, + { + "text" : "Kale (black, green, and red)", + "topic" : "topics/kale" + }, + { + "text" : "Mesclun mix (assorted young salad greens)", + "topic" : "topics/mesclun-mix" + }, + { + "text" : "Mustard greens", + "topic" : "topics/mustard-greens" + }, + { + "text" : "Sorrel", + "topic" : "" + }, + { + "text" : "Spinach", + "topic" : "topics/spinach" + }, + { + "text" : "Swiss chard", + "topic" : "topics/swiss-chard" + }, + { + "text" : "Turnip greens", + "topic" : "topics/turnips" + } + ] + }, + "dozeNuts" : { + "heading" : "Nuts and Seeds", + "servings" : [ + { + "imperial" : "¼ cup nuts or seeds", + "metric" : "30 g nuts or seeds" + }, + { + "imperial" : "2 tablespoons nut or seed butter", + "metric" : "2 tablespoons nut or seed butter" + } + ], + "topic" : "topics/nuts", + "varieties" : [ + { + "text" : "Almonds", + "topic" : "topics/almonds" + }, + { + "text" : "Brazil nuts", + "topic" : "topics/brazil-nuts" + }, + { + "text" : "Cashews", + "topic" : "topics/cashews" + }, + { + "text" : "Chia seeds", + "topic" : "topics/chia-seeds" + }, + { + "text" : "Hazelnuts", + "topic" : "topics/hazelnuts" + }, + { + "text" : "Hemp seeds", + "topic" : "" + }, + { + "text" : "Macadamia nuts", + "topic" : "topics/macadamia-nuts" + }, + { + "text" : "Pecans", + "topic" : "topics/pecans" + }, + { + "text" : "Pistachios", + "topic" : "topics/pistachios" + }, + { + "text" : "Pumpkin seeds", + "topic" : "topics/pumpkin-seeds" + }, + { + "text" : "Sesame seeds", + "topic" : "topics/sesame-seeds" + }, + { + "text" : "Sunflower seeds", + "topic" : "topics/sunflower-seeds" + }, + { + "text" : "Walnuts", + "topic" : "topics/walnuts" + } + ] + }, + "dozeSpices" : { + "heading" : "Herbs and Spices", + "servings" : [ + { + "imperial" : "¼ teaspoon of turmeric", + "metric" : "¼ teaspoon of turmeric" + }, + { + "imperial" : "Any other (salt-free) herbs and spices you enjoy", + "metric" : "Any other (salt-free) herbs and spices you enjoy" + } + ], + "topic" : "topics/spices", + "varieties" : [ + { + "text" : "Allspice", + "topic" : "topics/allspice" + }, + { + "text" : "Barberries", + "topic" : "topics/barberries" + }, + { + "text" : "Basil", + "topic" : "topics/basil" + }, + { + "text" : "Bay leaves", + "topic" : "" + }, + { + "text" : "Cardamom", + "topic" : "topics/cardamom" + }, + { + "text" : "Chili powder", + "topic" : "" + }, + { + "text" : "Cilantro", + "topic" : "topics/cilantro" + }, + { + "text" : "Cinnamon", + "topic" : "topics/cinnamon" + }, + { + "text" : "Cloves", + "topic" : "topics/cloves" + }, + { + "text" : "Coriander", + "topic" : "topics/coriander" + }, + { + "text" : "Cumin", + "topic" : "topics/cumin" + }, + { + "text" : "Curry powder", + "topic" : "topics/curry-powder" + }, + { + "text" : "Dill", + "topic" : "" + }, + { + "text" : "Fenugreek", + "topic" : "topics/fenugreek" + }, + { + "text" : "Garlic", + "topic" : "topics/garlic" + }, + { + "text" : "Ginger", + "topic" : "topics/ginger" + }, + { + "text" : "Horseradish", + "topic" : "" + }, + { + "text" : "Lemongrass", + "topic" : "topics/lemongrass" + }, + { + "text" : "Marjoram", + "topic" : "topics/marjoram" + }, + { + "text" : "Mustard powder", + "topic" : "topics/mustard-powder" + }, + { + "text" : "Nutmeg", + "topic" : "topics/nutmeg" + }, + { + "text" : "Oregano", + "topic" : "topics/oregano" + }, + { + "text" : "Smoked paprika", + "topic" : "" + }, + { + "text" : "Parsley", + "topic" : "topics/parsley" + }, + { + "text" : "Pepper", + "topic" : "topics/pepper" + }, + { + "text" : "Peppermint", + "topic" : "topics/peppermint" + }, + { + "text" : "Rosemary", + "topic" : "topics/rosemary" + }, + { + "text" : "Saffron", + "topic" : "topics/saffron" + }, + { + "text" : "Sage", + "topic" : "" + }, + { + "text" : "Thyme", + "topic" : "topics/thyme" + }, + { + "text" : "Turmeric", + "topic" : "topics/turmeric" + }, + { + "text" : "Vanilla", + "topic" : "" + } + ] + }, + "dozeVegetablesCruciferous" : { + "heading" : "Cruciferous Vegetables", + "servings" : [ + { + "imperial" : "½ cup chopped", + "metric" : "30–80 g chopped" + }, + { + "imperial" : "¼ cup Brussels or broccoli sprouts", + "metric" : "12 g Brussels or broccoli sprouts" + }, + { + "imperial" : "1 tablespoon horseradish", + "metric" : "1 tablespoon horseradish" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Arugula", + "topic" : "" + }, + { + "text" : "Bok choy", + "topic" : "topics/bok-choy" + }, + { + "text" : "Broccoli (incl. Romanesco)", + "topic" : "topics/broccoli" + }, + { + "text" : "Brussels sprouts", + "topic" : "topics/brussels-sprouts" + }, + { + "text" : "Cabbage (green, red and savoy)", + "topic" : "topics/cabbage" + }, + { + "text" : "Cauliflower (white, green, orange and purple)", + "topic" : "topics/cauliflower" + }, + { + "text" : "Collard greens", + "topic" : "topics/collard-greens" + }, + { + "text" : "Horseradish", + "topic" : "" + }, + { + "text" : "Kale (black, green, and red)", + "topic" : "topics/kale" + }, + { + "text" : "Kohlrabi (green and purple)", + "topic" : "topics/kohlrabi" + }, + { + "text" : "Mustard greens", + "topic" : "topics/mustard-greens" + }, + { + "text" : "Radishes", + "topic" : "topics/radishes" + }, + { + "text" : "Turnip greens", + "topic" : "topics/turnips" + }, + { + "text" : "Watercress", + "topic" : "topics/watercress" + } + ] + }, + "dozeVegetablesOther" : { + "heading" : "Other Vegetables", + "servings" : [ + { + "imperial" : "1 cup raw leafy vegetables", + "metric" : "60 g raw leafy vegetables" + }, + { + "imperial" : "½ cup raw or cooked nonleafy vegetables", + "metric" : "50 g raw or cooked nonleafy vegetables" + }, + { + "imperial" : "½ cup vegetable juice", + "metric" : "125 ml vegetable juice" + }, + { + "imperial" : "¼ cup dried mushrooms", + "metric" : "7 g dried mushrooms" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Artichokes", + "topic" : "topics/artichokes" + }, + { + "text" : "Asparagus", + "topic" : "topics/asparagus" + }, + { + "text" : "Beets", + "topic" : "topics/beets" + }, + { + "text" : "Bell peppers", + "topic" : "topics/bell-peppers" + }, + { + "text" : "Carrots", + "topic" : "topics/carrots" + }, + { + "text" : "Corn", + "topic" : "topics/corn" + }, + { + "text" : "Garlic", + "topic" : "topics/garlic" + }, + { + "text" : "Mushrooms (button, oyster, portobello, and shiitake)", + "topic" : "topics/mushrooms" + }, + { + "text" : "Okra", + "topic" : "topics/okra" + }, + { + "text" : "Onions", + "topic" : "topics/onions" + }, + { + "text" : "Purple potatoes", + "topic" : "topics/purple-potatoes" + }, + { + "text" : "Pumpkin", + "topic" : "topics/pumpkin" + }, + { + "text" : "Sea vegetables (arame, dulse, and nori)", + "topic" : "topics/sea-vegetables" + }, + { + "text" : "Snap peas", + "topic" : "" + }, + { + "text" : "Squash (delicata, summer, and spaghetti squash varieties)", + "topic" : "topics/squash" + }, + { + "text" : "Sweet potatoes\/yams", + "topic" : "topics/sweet-potatoes" + }, + { + "text" : "Tomatoes", + "topic" : "topics/tomatoes" + }, + { + "text" : "Zucchini", + "topic" : "topics/zucchini" + } + ] + }, + "dozeWholeGrains" : { + "heading" : "Whole Grains", + "servings" : [ + { + "imperial" : "½ cup hot cereal or cooked grains, pasta, or corn kernels", + "metric" : "100 g hot cereal or cooked grains, pasta, or corn kernels" + }, + { + "imperial" : "1 cup cold cereal", + "metric" : "50 g cold cereal" + }, + { + "imperial" : "1 tortilla or slice of bread", + "metric" : "1 tortilla or slice of bread" + }, + { + "imperial" : "½ a bagel or English muffin", + "metric" : "½ a bagel or English muffin" + }, + { + "imperial" : "3 cups popped popcorn", + "metric" : "30 g popped popcorn" + } + ], + "topic" : "topics/grains", + "varieties" : [ + { + "text" : "Barley", + "topic" : "topics/barley" + }, + { + "text" : "Buckwheat", + "topic" : "topics/buckwheat" + }, + { + "text" : "Millet", + "topic" : "topics/millet" + }, + { + "text" : "Oats", + "topic" : "topics/oats" + }, + { + "text" : "Popcorn", + "topic" : "topics/popcorn" + }, + { + "text" : "Quinoa", + "topic" : "topics/quinoa" + }, + { + "text" : "Rye", + "topic" : "topics/rye" + }, + { + "text" : "Teff", + "topic" : "topics/teff" + }, + { + "text" : "Whole-wheat pasta", + "topic" : "topics/pasta" + } + ] + }, + "otherVitaminB12" : { + "heading" : "Vitamin B12", + "servings" : [ + + ], + "topic" : "topics/vitamin-b12", + "varieties" : [ + + ] + } + } +} diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/TweakDetailData.json b/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/TweakDetailData.json new file mode 100644 index 00000000..4f7d1198 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/Base.lproj/TweakDetailData.json @@ -0,0 +1,235 @@ +{ + "itemsDict" : { + "tweakCompleteIntentions" : { + "activity" : { + "imperial" : "Complete Your Implementation Intentions", + "metric" : "Complete Your Implementation Intentions" + }, + "description" : [ + "Every two months, create three new implementation intentions—\"if X, then Y\" plans to perform a particular behavior in a specific context—and check each one of them off as you complete them every day." + ], + "heading" : "Complete Your Implementation Intentions", + "topic" : "" + }, + "tweakDailyBlackCumin" : { + "activity" : { + "imperial" : "Black Cumin (Nigella sativa) (¼ tsp)", + "metric" : "Black Cumin (Nigella sativa) (¼ tsp)" + }, + "description" : [ + "As noted in the Appetite Suppression section, a systematic review and meta-analysis of randomized, controlled weight-loss trials found that about a quarter teaspoon of black cumin powder every day appears to reduce body mass index within a span of a couple of months. Note that black cumin is different from regular cumin, for which the dosing is different." + ], + "heading" : "Black Cumin (¼ tsp)", + "topic" : "" + }, + "tweakDailyCumin" : { + "activity" : { + "imperial" : "Cumin (Cuminum cyminum) (½ tsp with lunch and dinner)", + "metric" : "Cumin (Cuminum cyminum) (½ tsp with lunch and dinner)" + }, + "description" : [ + "Overweight women randomized to add a half teaspoon of cumin to their lunches and dinners beat out the control group by four more pounds and an extra inch off their waists. There is also evidence to support the use of the spice saffron, but a pinch a day would cost a dollar, whereas a teaspoon of cumin costs less than ten cents." + ], + "heading" : "Cumin (½ tsp with lunch and dinner)", + "topic" : "" + }, + "tweakDailyDeflourDiet" : { + "activity" : { + "imperial" : "Deflour Your Diet", + "metric" : "Deflour Your Diet" + }, + "description" : [ + "Check this box every day your whole grain servings are in the form of intact grains. The powdering of even 100 percent whole grains robs our microbiomes of the starch that would otherwise be ferried down to our colons enclosed in unbroken cell walls." + ], + "heading" : "Deflour Your Diet", + "topic" : "" + }, + "tweakDailyFrontLoad" : { + "activity" : { + "imperial" : "Front-Load Your Calories", + "metric" : "Front-Load Your Calories" + }, + "description" : [ + "There are metabolic benefits to distributing more calories to earlier in the day, so make breakfast (ideally) or lunch your largest meal of the day in true king\/prince\/pauper style." + ], + "heading" : "Front-Load Your Calories", + "topic" : "" + }, + "tweakDailyGarlic" : { + "activity" : { + "imperial" : "Garlic Powder (¼ tsp)", + "metric" : "Garlic Powder (¼ tsp)" + }, + "description" : [ + "Randomized, double-blind, placebo-controlled studies have found that as little as a daily quarter teaspoon of garlic powder can reduce body fat at a cost of perhaps two cents a day." + ], + "heading" : "Garlic Powder (¼ tsp)", + "topic" : "" + }, + "tweakDailyGinger" : { + "activity" : { + "imperial" : "Ground Ginger (1 tsp) or Cayenne Pepper (½ tsp)", + "metric" : "Ground Ginger (1 tsp) or Cayenne Pepper (½ tsp)" + }, + "description" : [ + "Randomized controlled trials have found that ¼ teaspoon to 1½ teaspoons a day of ground ginger significantly decreased body weight for just pennies a day. It can be as easy as stirring the ground spice into a cup of hot water. Note: Ginger may work better in the morning than evening. Chai tea is a tasty way to combine the green tea and ginger tricks into a single beverage. Alternately, for BAT activation, you can add one raw jalapeño pepper or a half teaspoon of red pepper powder (or, presumably, crushed red pepper flakes) into your daily diet. To help beat the heat, you can very thinly slice or finely chop the jalapeño to reduce its bite to little prickles, or mix the red pepper into soup or the whole-food vegetable smoothie I featured in one of my cooking videos on NutritionFacts.org." + ], + "heading" : "Ground Ginger (1 tsp) or Cayenne Pepper (½ tsp)", + "topic" : "" + }, + "tweakDailyGreenTea" : { + "activity" : { + "imperial" : "Green Tea (3 cups)", + "metric" : "Green Tea (3 cups)" + }, + "description" : [ + "Drink three cups a day between meals (waiting at least an hour after a meal so as to not interfere with iron absorption). During meals, drink water, black coffee, or hibiscus tea mixed 6:1 with lemon verbena, but never exceed three cups of fluid an hour (important given my water preloading advice). Take advantage of the reinforcing effect of caffeine by drinking your green tea along with something healthy you wish you liked more, but don't consume large amounts of caffeine within six hours of bedtime. Taking your tea without sweetener is best, but if you typically sweeten your tea with honey or sugar, try yacon syrup instead." + ], + "heading" : "Green Tea (3 cups)", + "topic" : "" + }, + "tweakDailyHydrate" : { + "activity" : { + "imperial" : "Stay Hydrated", + "metric" : "Stay Hydrated" + }, + "description" : [ + "Check this box if your urine never appears darker than a pale yellow.\n\nNote that if you're eating riboflavin-fortified foods (such as nutritional yeast), then base this instead on getting nine cups of unsweetened beverages a day for women (which would be taken care of by the green tea and water preloading recommendations) or thirteen cups a day for men.\n\nIf you have heart or kidney issues, don't increase fluid intake at all without first talking with your physician.\n\nRemember, diet soda may be calorie-free, but it's not consequence-free, as we learned in the Low in Added Sugar section." + ], + "heading" : "Stay Hydrated", + "topic" : "" + }, + "tweakDailyNutriYeast" : { + "activity" : { + "imperial" : "Nutritional Yeast (2 tsp)", + "metric" : "Nutritional Yeast (2 tsp)" + }, + "description" : [ + "Two teaspoons of baker's, brewer's, or nutritional yeast contains roughly the amount of beta 1,3\/1,6 glucans found in randomized, double-blind, placebo-controlled clinical trials to facilitate weight loss." + ], + "heading" : "Nutritional Yeast (2 tsp)", + "topic" : "" + }, + "tweakDailyTimeRestrict" : { + "activity" : { + "imperial" : "Time-Restrict Your Eating", + "metric" : "Time-Restrict Your Eating" + }, + "description" : [ + "Confine eating to a daily window of time of your choosing under twelve hours in length that you can stick to consistently, seven days a week. Given the circadian benefits of reducing evening food intake, the window should end before 7:00 p.m." + ], + "heading" : "Time-Restrict Your Eating", + "topic" : "" + }, + "tweakExerciseTiming" : { + "activity" : { + "imperial" : "Optimize Exercise Timing", + "metric" : "Optimize Exercise Timing" + }, + "description" : [ + "The Daily Dozen's recommendation for optimum exercise duration for longevity is ninety minutes of moderately intense activity a day, which is also the optimum exercise duration for weight loss. Anytime is good, and the more, the better, but there may be an advantage to exercising in a fasted state, at least six hours after your last meal. Typically, this would mean before breakfast, but if you timed it right, you could exercise midday before a late lunch or, if lunch is eaten early enough, before dinner. This is the timing for nondiabetics. Diabetics and prediabetics should instead start exercising thirty minutes after the start of a meal and ideally go for at least an hour to completely straddle the blood sugar peak. If you had to choose a single meal to exercise after, it would be dinner, due to the circadian rhythm of blood sugar control that wanes throughout the day. Ideally, though, breakfast would be the largest meal of the day, and you'd exercise after that—or, even better, after every meal." + ], + "heading" : "Optimize Exercise Timing", + "topic" : "" + }, + "tweakMeal20Minutes" : { + "activity" : { + "imperial" : "Follow the Twenty-Minute Rule", + "metric" : "Follow the Twenty-Minute Rule" + }, + "description" : [ + "Whether through increasing viscosity or the number of chews, or decreasing bite size and eating rate, dozens of studies have demonstrated that no matter how we boost the amount of time food is in our mouths, it can result in lower caloric intake. So extend meal duration to at least twenty minutes to allow your natural satiety signals to take full effect. How? By choosing foods that take longer to eat and eating them in a way that prolongs the time they stay in your mouth. Think bulkier, hardier, chewier foods in smaller, well-chewed bites." + ], + "heading" : "Follow the Twenty-Minute Rule", + "topic" : "" + }, + "tweakMealNegCal" : { + "activity" : { + "imperial" : "Preload with \"Negative Calorie\" Foods", + "metric" : "Preload with \"Negative Calorie\" Foods" + }, + "description" : [ + "As the first course, start each meal with an apple or a Green Light soup or salad containing fewer than one hundred calories per cup." + ], + "heading" : "Preload with \"Negative Calorie\" Foods", + "topic" : "" + }, + "tweakMealUndistracted" : { + "activity" : { + "imperial" : "Enjoy Undistracted Meals", + "metric" : "Enjoy Undistracted Meals" + }, + "description" : [ + "Don't eat while watching TV or playing on your phone. Give yourself a check for each meal you're able to eat without distraction." + ], + "heading" : "Enjoy Undistracted Meals", + "topic" : "" + }, + "tweakMealVinegar" : { + "activity" : { + "imperial" : "Incorporate Vinegar (2 tsp with each meal)", + "metric" : "Incorporate Vinegar (2 tsp with each meal)" + }, + "description" : [ + "Never drink vinegar straight. Instead, flavor meals or dress a side salad with any of the sweet and savory vinegars out there. If you want to drink it, make sure to mix it in a glass of water and, afterward, be sure to rinse your mouth out with water to protect your tooth enamel." + ], + "heading" : "Incorporate Vinegar (2 tsp with each meal)", + "topic" : "" + }, + "tweakMealWater" : { + "activity" : { + "imperial" : "Preload with Water", + "metric" : "Preload with Water" + }, + "description" : [ + "Time your metabolism-boosting two cups of cool or cold unflavored water before each meal to also take advantage of its preload benefits." + ], + "heading" : "Preload with Water", + "topic" : "" + }, + "tweakNightlyFast" : { + "activity" : { + "imperial" : "Fast After 7:00 p.m.", + "metric" : "Fast After 7:00 p.m." + }, + "description" : [ + "Due to our circadian rhythms, food eaten at night is more fattening than the exact same food eaten earlier in the day, so fast every night for at least twelve hours starting before 7:00 p.m. The fewer calories after sundown, the better." + ], + "heading" : "Fast After 7:00 pm", + "topic" : "" + }, + "tweakNightlySleep" : { + "activity" : { + "imperial" : "Get Sufficient Sleep", + "metric" : "Get Sufficient Sleep" + }, + "description" : [ + "Check this box if you get at least seven hours of sleep at your regular bedtime." + ], + "heading" : "Get Sufficient Sleep", + "topic" : "" + }, + "tweakNightlyTrendelenbrug" : { + "activity" : { + "imperial" : "Experiment with Mild Trendelenburg", + "metric" : "Experiment with Mild Trendelenburg" + }, + "description" : [ + "Try spending at least four hours a night lying with your body tilted head-down six degrees by elevating the posts at the foot of your bed by eight inches (or by nine inches if you have a California king). Be extremely careful when you get out of bed, as this causes orthostatic intolerance in most people, even if you're young and healthy—meaning if you get up too fast, you can feel dizzy, faint, or light-headed and could fall and hurt yourself. So get up slowly. Drinking two cups of cold water thirty minutes before rising may also help prevent this potentially hazardous side effect.\n\nIMPORTANT: Do not try this at home at all if you have any heart or lung issues, acid reflux, or problems with your brain (like head trauma) or eyes (even a family history of glaucoma disqualifies you). Also do not try this until you ask your physician if they think it's safe for you to sleep in mild Trendelenburg." + ], + "heading" : "Experiment with Mild Trendelenburg", + "topic" : "" + }, + "tweakWeightTwice" : { + "activity" : { + "imperial" : "Weigh Yourself Twice a Day", + "metric" : "Weigh Yourself Twice a Day" + }, + "description" : [ + "Regular self-weighing is considered crucial for long-term weight control, but there is insufficient evidence to support a specific frequency of weighing. My recommendation is based on the one study that found that twice daily—upon waking and right before bed—appeared superior to once a day (about six versus two pounds of weight loss over twelve weeks)." + ], + "heading" : "Weigh Yourself Twice a Day", + "topic" : "" + } + } +} diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/en.lproj/Localizable.strings b/DailyDozen/DailyDozen/App/Texts/LocalStrings/en.lproj/Localizable.strings new file mode 100644 index 00000000..90f582f1 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/en.lproj/Localizable.strings @@ -0,0 +1,94 @@ +/* + Localizable.strings + DailyDozen + + Copyright © 2019 Nutritionfacts.org. All rights reserved. +*/ + +/* Daily Dozen (proper noun) navigation tab */ +"navtab.doze"="Daily Dozen"; +/* 21 Tweaks (proper noun) navigation tab */ +"navtab.tweaks"="21 Tweaks"; +/* More Information navigation tab */ +"navtab.info"="Info"; +/* Preferences (aka Settings, Configuration) navigation tab. Choose word different from 'Tweaks' translation */ +"navtab.preferences"="Settings"; + +/* Records: Daily Tally */ +"historyRecord.movingMean"="Moving Average"; +"historyRecordDoze.heading"="Servings History"; +"historyRecordDoze.legend"="Servings"; +"historyRecordTweak.heading"="Tweaks History"; +"historyRecordTweak.legend"="Tweaks"; +"historyRecordWeight.heading"="Weight History"; +"historyRecordWeight.titleImperial"="Weight (lbs)"; +"historyRecordWeight.titleMetric"="Weight (kg)"; +"historyRecordWeight.legendMorning"="Morning"; +"historyRecordWeight.legendEvening"="Evening"; + +/* URL path */ +"urlSegment.base"="https://nutritionfacts.org/"; +"urlSegment.lang"=""; + +"urlSegmentInfoMenu.videos"="videos"; +"urlSegmentInfoMenu.book"="book"; +"urlSegmentInfoMenu.cookbook"="cookbook"; +"urlSegmentInfoMenu.diet"="how-not-to-diet"; +"urlSegmentInfoMenu.challenge"="daily-dozen-challenge"; +"urlSegmentInfoMenu.donate"="donate"; +"urlSegmentInfoMenu.subscribe"="subscribe"; +"urlSegmentInfoMenu.source"="open-source"; + +"urlSegmentInfoMenu.team"="team"; + +/* Units toggle button text: metric measurement system */ +"unitToggle.metric"="METRIC"; +/* Units toggle button text: imperial measurement system */ +"unitToggle.imperial"="IMPERIAL"; + +/* streak days format */ +"streakDaysFormat"="%d days"; + +/* ***** Daily Dozen ***** */ +"dozeBeans.heading"="Beans"; +"dozeBerries.heading"="Berries"; +"dozeFruitsOther.heading"="Other Fruits"; +"dozeVegetablesCruciferous.heading"="Cruciferous Vegetables"; +"dozeGreens.heading"="Greens"; +"dozeVegetablesOther.heading"="Other Vegetables"; +"dozeFlaxseeds.heading"="Flaxseeds"; +"dozeNuts.heading"="Nuts and Seeds"; +"dozeSpices.heading"="Herbs and Spices"; +"dozeWholeGrains.heading"="Whole Grains"; +"dozeBeverages.heading"="Beverages"; +"dozeExercise.heading"="Exercise"; +/* ***** Daily Dozen Other ***** */ +"otherVitaminB12.heading"="Vitamin B12"; +"otherVitaminD.heading"="Vitamin D"; +"otherOmega3.heading"="Plant-based Omega 3s"; +/* ***** Daily Dozen Supplements ***** */ +"dozeOtherInfo.title"="Supplements"; +"dozeOtherInfo.message"="Vitamin B12 is essential for your health but does not count towards your daily servings.\n\nVitamin B12 is included in this app to provide you with an easy way to track your intake."; +"dozeOtherInfo.confirm"="OK"; +/* ***** 21 Tweaks ***** */ +"tweakMealWater.heading"="Preload Water"; +"tweakMealNegCal.heading"="Negative Calorie Preload"; +"tweakMealVinegar.heading"="Incorporate Vinegar"; +"tweakMealUndistracted.heading"="Undistracted Meals"; +"tweakMeal20Minutes.heading"="Twenty-minute Rule"; +"tweakDailyBlackCumin.heading"="Black Cumin"; +"tweakDailyGarlic.heading"="Garlic Powder"; +"tweakDailyGinger.heading"="Ginger or Cayenne"; +"tweakDailyNutriYeast.heading"="Nutritional Yeast"; +"tweakDailyCumin.heading"="Cumin"; +"tweakDailyGreenTea.heading"="Green Tea"; +"tweakDailyHydrate.heading"="Stay Hydrated"; +"tweakDailyDeflourDiet.heading"="Deflour Diet"; +"tweakDailyFrontLoad.heading"="Front-load Calories"; +"tweakDailyTimeRestrict.heading"="Time-restrict Eating"; +"tweakExerciseTiming.heading"="Exercising Timing"; +"tweakWeightTwice.heading"="Weigh Twice Daily"; +"tweakCompleteIntentions.heading"="Complete Intentions"; +"tweakNightlyFast.heading"="Fast After 7:00 p.m."; +"tweakNightlySleep.heading"="Sufficient Sleep"; +"tweakNightlyTrendelenbrug.heading"="Trendelenburg"; diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/DozeDetailData.json b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/DozeDetailData.json new file mode 100644 index 00000000..9cd316d5 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/DozeDetailData.json @@ -0,0 +1,1130 @@ +{ + "itemsDict" : { + "dozeBeans" : { + "heading" : "Frijoles", + "servings" : [ + { + "imperial" : "¼ taza de hummus o dip de frijol", + "metric" : "60 g de hummus o dip de frijol" + }, + { + "imperial" : "½ taza de frijoles cocidos, arvejas partidas, lentejas, tofu o tempeh", + "metric" : "130 g de frijoles cocidos, arvejas partidas, lentejas, tofu o tempeh" + }, + { + "imperial" : "1 taza de guisantes frescos o retoños de lentejas", + "metric" : "150 g de guisantes frescos o retoños de lentejas" + } + ], + "topic" : "es/topics/legumbres-es", + "varieties" : [ + { + "text" : "Frijoles negros", + "topic" : "es/topics/frijoles-negros" + }, + { + "text" : "Frijoles de ojo negro", + "topic" : "" + }, + { + "text" : "Frijoles de mantequilla", + "topic" : "" + }, + { + "text" : "Frijoles cannellini", + "topic" : "" + }, + { + "text" : "Garbanzos", + "topic" : "es/topics/garbanzos" + }, + { + "text" : "Edamame", + "topic" : "es/topics/edamame" + }, + { + "text" : "Guisante Ingles", + "topic" : "" + }, + { + "text" : "Frijoles garbanzos", + "topic" : "es/topics/garbanzo" + }, + { + "text" : "Frijoles grandes del norte", + "topic" : "" + }, + { + "text" : "Frijoles", + "topic" : "es/topics/frijoles-rojos" + }, + { + "text" : "Lentejas (beluga, Francesa, y de variedades rojas)", + "topic" : "/es/topics/lentejas" + }, + { + "text" : "Miso", + "topic" : "es/topics/miso-es" + }, + { + "text" : "Frijoles blancos", + "topic" : "es/topics/frijoles-blancos" + }, + { + "text" : "Frijoles pintos", + "topic" : "es/topics/frijoles-pintos" + }, + { + "text" : "Frijoles rojos pequeños", + "topic" : "" + }, + { + "text" : "Guisantes partidos (amarillos o verdes)", + "topic" : "es/topics/guisantes-partidos" + }, + { + "text" : "Tempeh", + "topic" : "es/topics/tempeh-es" + } + ] + }, + "dozeBerries" : { + "heading" : "Bayas", + "servings" : [ + { + "imperial" : "½ taza frescos o congelados", + "metric" : "60 g frescos o congelados" + }, + { + "imperial" : "¼ taza secos", + "metric" : "40 g secos" + } + ], + "topic" : "es/topics/bayas-es", + "varieties" : [ + { + "text" : "Bayas de Acai", + "topic" : "/es/topics/bayas-de-acai-es" + }, + { + "text" : "Agracejos", + "topic" : "es/topics/berberos-es" + }, + { + "text" : "Moras", + "topic" : "es/topics/moras" + }, + { + "text" : "Arándanos azules", + "topic" : "es/topics/arandanos-azules" + }, + { + "text" : "Cerezas (dulces o acidas)", + "topic" : "es/topics/cerezas" + }, + { + "text" : "Uvas concord", + "topic" : "es/topics/uvas-de-concordia-es" + }, + { + "text" : "Arándanos agrios", + "topic" : "es/topics/arandanos-es" + }, + { + "text" : "Bayas Goji", + "topic" : "es/topics/goji" + }, + { + "text" : "Kumquats", + "topic" : "" + }, + { + "text" : "Moras mulberry", + "topic" : "" + }, + { + "text" : "Frambuesas (negras o rojas)", + "topic" : "es/topics/frambuesas" + }, + { + "text" : "Fresas", + "topic" : "es/topics/fresas" + } + ] + }, + "dozeBeverages" : { + "heading" : "Bebidas", + "servings" : [ + { + "imperial" : "Un vaso (12 onzas)", + "metric" : "Un vaso (350 ml)" + } + ], + "topic" : "es/topics/bebidas", + "varieties" : [ + { + "text" : "Té negro", + "topic" : "es/topics/te-negro" + }, + { + "text" : "Té Chai", + "topic" : "es/topics/te-chai" + }, + { + "text" : "Té de vainilla y manzanilla", + "topic" : "es/topics/te-de-manzanilla" + }, + { + "text" : "Café", + "topic" : "es/topics/cafe" + }, + { + "text" : "Té Earl Grey", + "topic" : "es/topics/te-earl-grey-es" + }, + { + "text" : "Té verde", + "topic" : "es/topics/te-verde" + }, + { + "text" : "Té de hibisco", + "topic" : "es/topics/hibiscus-tea-es" + }, + { + "text" : "Chocolate caliente", + "topic" : "" + }, + { + "text" : "Té de jasmín", + "topic" : "es/topics/te-de-jasmin" + }, + { + "text" : "Té de limón", + "topic" : "" + }, + { + "text" : "Té de matcha", + "topic" : "es/topics/matcha-es" + }, + { + "text" : "Té de retoño de almendra oolong", + "topic" : "es/topics/te-oolong-es" + }, + { + "text" : "Té de menta", + "topic" : "" + }, + { + "text" : "Té de rooibos", + "topic" : "es/topics/te-rooibos-es" + }, + { + "text" : "Agua", + "topic" : "es/topics/agua" + }, + { + "text" : "Té blanco", + "topic" : "es/topics/te-blanco" + } + ] + }, + "dozeExercise" : { + "heading" : "Ejercicio", + "servings" : [ + { + "imperial" : "90 minutos de actividad de moderada intensidad", + "metric" : "90 minutos de actividad de moderada intensidad" + }, + { + "imperial" : "40 minutos de actividad de vigorosa intensidad", + "metric" : "40 minutos de actividad de vigorosa intensidad" + } + ], + "topic" : "es/topics/ejercicio", + "varieties" : [ + { + "text" : "Bicicleta", + "topic" : "" + }, + { + "text" : "Canoa", + "topic" : "" + }, + { + "text" : "Baile", + "topic" : "" + }, + { + "text" : "Quemado\/Dodgeball", + "topic" : "" + }, + { + "text" : "Esquí en descenso", + "topic" : "" + }, + { + "text" : "Esgrima", + "topic" : "" + }, + { + "text" : "Senderismo", + "topic" : "" + }, + { + "text" : "Quehaceres domésticos", + "topic" : "" + }, + { + "text" : "Patinaje en hielo", + "topic" : "" + }, + { + "text" : "Patinaje en línea", + "topic" : "" + }, + { + "text" : "Malabarismos", + "topic" : "" + }, + { + "text" : "Saltar en trampolín", + "topic" : "" + }, + { + "text" : "Remos", + "topic" : "" + }, + { + "text" : "Jugar frisbee", + "topic" : "" + }, + { + "text" : "Patinaje", + "topic" : "" + }, + { + "text" : "Tirar canastas", + "topic" : "" + }, + { + "text" : "Palear nieve ligera", + "topic" : "" + }, + { + "text" : "Patineta", + "topic" : "" + }, + { + "text" : "Snorkeling", + "topic" : "" + }, + { + "text" : "Surfear", + "topic" : "" + }, + { + "text" : "Nadar recreacionalmente", + "topic" : "" + }, + { + "text" : "Tennis (dobles)", + "topic" : "" + }, + { + "text" : "Mantenerse a flote", + "topic" : "" + }, + { + "text" : "Caminata rápida (4 mph)", + "topic" : "" + }, + { + "text" : "Aerobicos acuáticos", + "topic" : "" + }, + { + "text" : "Esquí en agua", + "topic" : "" + }, + { + "text" : "Jardinear", + "topic" : "" + }, + { + "text" : "Yoga", + "topic" : "" + }, + { + "text" : "Mochilear", + "topic" : "" + }, + { + "text" : "Baloncesto", + "topic" : "" + }, + { + "text" : "Bicicleta cuesta arriba", + "topic" : "" + }, + { + "text" : "Circuito de entrenamiento con pesas", + "topic" : "" + }, + { + "text" : "Esquí a campo traviesa", + "topic" : "" + }, + { + "text" : "Football americano", + "topic" : "" + }, + { + "text" : "Hockey", + "topic" : "" + }, + { + "text" : "Trotar", + "topic" : "" + }, + { + "text" : "Saltos de tijera", + "topic" : "" + }, + { + "text" : "Salta cuerda", + "topic" : "" + }, + { + "text" : "Lacrosse", + "topic" : "" + }, + { + "text" : "Lagartijas", + "topic" : "" + }, + { + "text" : "Dominadas", + "topic" : "" + }, + { + "text" : "Racquetball", + "topic" : "" + }, + { + "text" : "Escalar", + "topic" : "" + }, + { + "text" : "Rugby", + "topic" : "" + }, + { + "text" : "Correr", + "topic" : "" + }, + { + "text" : "Buceo", + "topic" : "" + }, + { + "text" : "Tennis (sencillos)", + "topic" : "" + }, + { + "text" : "Fútbol", + "topic" : "" + }, + { + "text" : "Patinaje de velocidad", + "topic" : "" + }, + { + "text" : "Squash", + "topic" : "" + }, + { + "text" : "Aerobicos step", + "topic" : "" + }, + { + "text" : "Natación", + "topic" : "" + }, + { + "text" : "Caminata rápida cuesta arriba", + "topic" : "" + }, + { + "text" : "Trotar en agua", + "topic" : "" + } + ] + }, + "dozeFlaxseeds" : { + "heading" : "Linaza", + "servings" : [ + { + "imperial" : "1 cucharada molida", + "metric" : "1 cucharada molida" + } + ], + "topic" : "es/topics/semillas-de-linaza", + "varieties" : [ + { + "text" : "Linaza café", + "topic" : "" + }, + { + "text" : "Linaza dorada", + "topic" : "" + } + ] + }, + "dozeFruitsOther" : { + "heading" : "Otras frutas", + "servings" : [ + { + "imperial" : "1 fruta de tamaño medio", + "metric" : "1 fruta de tamaño medio" + }, + { + "imperial" : "1 taza de fruta partida", + "metric" : "120 g de fruta partida" + }, + { + "imperial" : "¼ taza de fruta seca", + "metric" : "40 g de fruta seca" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Manzanas", + "topic" : "es/topics/manzanas" + }, + { + "text" : "Albaricoques", + "topic" : "es/topics/albaricoques" + }, + { + "text" : "Aguacates", + "topic" : "es/topics/aguacates" + }, + { + "text" : "Bananos", + "topic" : "es/topics/bananos" + }, + { + "text" : "Melones naranja", + "topic" : "es/topics/melon" + }, + { + "text" : "Clementinas", + "topic" : "" + }, + { + "text" : "Dátiles", + "topic" : "es/topics/datiles" + }, + { + "text" : "Higos", + "topic" : "es/topics/higos" + }, + { + "text" : "Toronja", + "topic" : "es/topics/toronja" + }, + { + "text" : "Melones verdes", + "topic" : "" + }, + { + "text" : "Kiwis", + "topic" : "es/topics/kiwi" + }, + { + "text" : "Limones amarillos", + "topic" : "topics/lemons" + }, + { + "text" : "Limones verdes", + "topic" : "es/topics/limas" + }, + { + "text" : "Lychees", + "topic" : "" + }, + { + "text" : "Mangos", + "topic" : "es/topics/mango-es" + }, + { + "text" : "Nectarinas", + "topic" : "" + }, + { + "text" : "Naranjas", + "topic" : "es/topics/naranjas" + }, + { + "text" : "Papaya", + "topic" : "es/topics/papaya-es" + }, + { + "text" : "Maracuyá", + "topic" : "" + }, + { + "text" : "Duraznos", + "topic" : "es/topics/duraznos-es" + }, + { + "text" : "Peras", + "topic" : "es/topics/peras-es" + }, + { + "text" : "Piña", + "topic" : "/es/topics/pinas" + }, + { + "text" : "Ciruelas (en especial ciruelas negras)", + "topic" : "es/topics/ciruelas" + }, + { + "text" : "Pluots", + "topic" : "" + }, + { + "text" : "Granadas", + "topic" : "es/topics/granadas" + }, + { + "text" : "Ciruelas pasas", + "topic" : "es/topics/ciruelas-pasas" + }, + { + "text" : "Mandarinas", + "topic" : "" + }, + { + "text" : "Sandía", + "topic" : "es/topics/sandia" + } + ] + }, + "dozeGreens" : { + "heading" : "Vegetales de hoja verde", + "servings" : [ + { + "imperial" : "1 taza cruda", + "metric" : "60 g cruda" + }, + { + "imperial" : "½ taza cocida", + "metric" : "90 g cocida" + } + ], + "topic" : "es/topics/verduras", + "varieties" : [ + { + "text" : "Arugula", + "topic" : "" + }, + { + "text" : "Hojas de remolacha", + "topic" : "es/topics/hojas-de-remolacha-es" + }, + { + "text" : "Berza", + "topic" : "" + }, + { + "text" : "Col rizada (negra, verde, y roja)", + "topic" : "es/topics/col-rizada-es" + }, + { + "text" : "Mezcla Mesclun (hojas tiernas verdes variadas)", + "topic" : "es/topics/ensalada-mesclun-es" + }, + { + "text" : "Hojas de mostaza", + "topic" : "es/topics/hojas-de-mostaza" + }, + { + "text" : "Alazán", + "topic" : "" + }, + { + "text" : "Espinaca", + "topic" : "es/topics/espinaca" + }, + { + "text" : "Acelga", + "topic" : "es/topics/acelga-es" + }, + { + "text" : "Nabos verdes", + "topic" : "es/topics/nabos" + } + ] + }, + "dozeNuts" : { + "heading" : "Nueces", + "servings" : [ + { + "imperial" : "¼ taza de nueces o semillas", + "metric" : "30 g de nueces o semillas" + }, + { + "imperial" : "2 cucharadas de mantequilla de nueces o semillas", + "metric" : "2 cucharadas de mantequilla de nueces o semillas" + } + ], + "topic" : "es/topics/nueces", + "varieties" : [ + { + "text" : "Almendras", + "topic" : "es/topics/almendras" + }, + { + "text" : "Nueces de Brasil", + "topic" : "es/topics/nueces-de-brasil-es" + }, + { + "text" : "Marañones", + "topic" : "es/topics/anacardos" + }, + { + "text" : "Semillas de chia", + "topic" : "es/topics/semillas-de-chia" + }, + { + "text" : "Avellanas", + "topic" : "es/topics/avellanas-es" + }, + { + "text" : "Semillas de Cáñamo", + "topic" : "" + }, + { + "text" : "Nueces de macadamia", + "topic" : "es/topics/nueces-de-macadamia" + }, + { + "text" : "Nueces pecanas", + "topic" : "es/topics/nuez-pecana" + }, + { + "text" : "Pistachos", + "topic" : "es/topics/pistachos" + }, + { + "text" : "Semillas de calabaza", + "topic" : "es/topics/semillas-de-calabaza" + }, + { + "text" : "Semillas de ajonjolí", + "topic" : "es/topics/ajonjoli" + }, + { + "text" : "Semillas de girasol", + "topic" : "es/topics/semillas-de-girasol" + }, + { + "text" : "Nueces", + "topic" : "es/topics/nueces-es" + } + ] + }, + "dozeSpices" : { + "heading" : "Especias", + "servings" : [ + { + "imperial" : "¼ cucharadita de cúrcuma", + "metric" : "¼ cucharadita de cúrcuma" + }, + { + "imperial" : "Cualquier otra especie (sin sal) que gustes", + "metric" : "Cualquier otra especie (sin sal) que gustes" + } + ], + "topic" : "es/topics/especias", + "varieties" : [ + { + "text" : "Pimienta de Jamaica\/Inglesa", + "topic" : "" + }, + { + "text" : "Bérberos", + "topic" : "es/topics/berberos-es" + }, + { + "text" : "Albahaca", + "topic" : "es/topics/albahaca" + }, + { + "text" : "Hojas de laurel", + "topic" : "" + }, + { + "text" : "Cardamomo", + "topic" : "es/topics/cardamomo" + }, + { + "text" : "Chile en polvo", + "topic" : "" + }, + { + "text" : "Cilantro", + "topic" : "es/topics/cilantro-es" + }, + { + "text" : "Canela", + "topic" : "es/topics/canela" + }, + { + "text" : "Clavos", + "topic" : "es/topics/clavos-de-olor" + }, + { + "text" : "Cilantro", + "topic" : "es/topics/cilantro-es-2" + }, + { + "text" : "Comino", + "topic" : "es/topics/comino" + }, + { + "text" : "Polvo de curry", + "topic" : "es/topics/curry-en-polvo" + }, + { + "text" : "Eneldo", + "topic" : "" + }, + { + "text" : "Fenogreco", + "topic" : "es/topics/fenogreco" + }, + { + "text" : "Ajo", + "topic" : "es/topics/ajo" + }, + { + "text" : "Jengibre", + "topic" : "es/topics/jengibre" + }, + { + "text" : "Rábano picante", + "topic" : "" + }, + { + "text" : "Limoncillo", + "topic" : "es/topics/limoncillo-es" + }, + { + "text" : "Mejorana", + "topic" : "es/topics/mejorana" + }, + { + "text" : "Polvo de mostaza", + "topic" : "es/topics/mostaza-en-polvo" + }, + { + "text" : "Nuez moscada", + "topic" : "es/topics/nuez-moscada" + }, + { + "text" : "Oregano", + "topic" : "es/topics/oregano-es" + }, + { + "text" : "Papríka ahumada", + "topic" : "" + }, + { + "text" : "Perejil", + "topic" : "es/topics/perejil" + }, + { + "text" : "Pimienta", + "topic" : "es/topics/pimienta" + }, + { + "text" : "Menta", + "topic" : "es/topics/menta-es" + }, + { + "text" : "Romero", + "topic" : "es/topics/romero" + }, + { + "text" : "Azafrán", + "topic" : "es/topics/azafran" + }, + { + "text" : "Salvia", + "topic" : "" + }, + { + "text" : "Tomillo", + "topic" : "es/topics/tomillo" + }, + { + "text" : "Cúrcuma", + "topic" : "es/topics/curcuma" + }, + { + "text" : "Vainilla", + "topic" : "" + } + ] + }, + "dozeVegetablesCruciferous" : { + "heading" : "Vegetales crucíferos", + "servings" : [ + { + "imperial" : "½ taza picado", + "metric" : "30–80 g picado" + }, + { + "imperial" : "¼ taza de coles de Bruselas o retoños de brócoli", + "metric" : "12 g de coles de Bruselas o retoños de brócoli" + }, + { + "imperial" : "1 cucharada de rábano picante", + "metric" : "1 cucharada de rábano picante" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Arugula", + "topic" : "" + }, + { + "text" : "Bok choy", + "topic" : "es/topics/bok-choy-es" + }, + { + "text" : "Brócoli (Romanesco)", + "topic" : "es/topics/brocoli" + }, + { + "text" : "Repollos de Bruselas", + "topic" : "es/topics/coles-bruselas" + }, + { + "text" : "Repollo (verde, rojo, de Saboya)", + "topic" : "es/topics/repollo" + }, + { + "text" : "Coliflor (blanco, verde, naranja, morado)", + "topic" : "es/topics/coliflor" + }, + { + "text" : "Berza", + "topic" : "es/topics/acelgas-es" + }, + { + "text" : "Rábano picante", + "topic" : "" + }, + { + "text" : "Col rizada (negra, verde, y roja)", + "topic" : "es/topics/col-rizada-es" + }, + { + "text" : "Colinabo (verde, morado)", + "topic" : "es/topics/colinabo" + }, + { + "text" : "Hojas de mostaza", + "topic" : "es/topics/hojas-de-mostaza" + }, + { + "text" : "Rábanos", + "topic" : "es/topics/rabanos" + }, + { + "text" : "Nabos verdes", + "topic" : "es/topics/nabos" + }, + { + "text" : "Berro", + "topic" : "es/topics/berros-es" + } + ] + }, + "dozeVegetablesOther" : { + "heading" : "Otros vegetales", + "servings" : [ + { + "imperial" : "1 taza de vegetales de hoja cruda", + "metric" : "60 g de vegetales de hoja cruda" + }, + { + "imperial" : "½ taza de vegetales sin hoja cruda o cocida", + "metric" : "50 g de vegetales sin hoja cruda o cocida" + }, + { + "imperial" : "½ taza de jugo de vegetales", + "metric" : "125 ml de jugo de vegetales" + }, + { + "imperial" : "¼ taza de champiñones secos", + "metric" : "7 g de champiñones secos" + } + ], + "topic" : "", + "varieties" : [ + { + "text" : "Alcachofas", + "topic" : "es/topics/alcachofas" + }, + { + "text" : "Espárragos", + "topic" : "es/topics/esparragos" + }, + { + "text" : "Remolachas", + "topic" : "es/topics/remolachas-es" + }, + { + "text" : "Pimiento morrón", + "topic" : "es/topics/pimientos" + }, + { + "text" : "Zanahorias", + "topic" : "es/topics/zanahorias" + }, + { + "text" : "Maíz", + "topic" : "es/topics/maiz" + }, + { + "text" : "Ajo", + "topic" : "es/topics/ajo" + }, + { + "text" : "Champiñones (botón, ostra, portobello, y shiitake)", + "topic" : "es/topics/champinones" + }, + { + "text" : "Okra", + "topic" : "es/topics/okra-es" + }, + { + "text" : "Cebollas", + "topic" : "es/topics/cebollas" + }, + { + "text" : "Patatas moradas", + "topic" : "es/topics/patatas-moradas" + }, + { + "text" : "Calabaza", + "topic" : "es/topics/calabaza" + }, + { + "text" : "Vegetales marinos (arame, dulse, y nori)", + "topic" : "es/topics/vegetales-marinos" + }, + { + "text" : "Guisantes", + "topic" : "" + }, + { + "text" : "Calabacines (variedades de delicata, verano, y spaghetti)", + "topic" : "es/topics/calabaza-es" + }, + { + "text" : "Camotes", + "topic" : "es/topics/camotes" + }, + { + "text" : "Tomates", + "topic" : "es/topics/tomates" + }, + { + "text" : "Calabacitas zuchini", + "topic" : "es/topics/calabacin-es" + } + ] + }, + "dozeWholeGrains" : { + "heading" : "Granos integrales", + "servings" : [ + { + "imperial" : "½ taza de cereal caliente o granos, pasta o granos de maíz cocidos", + "metric" : "100 g de cereal caliente o granos, pasta o granos de maíz cocidos" + }, + { + "imperial" : "1 taza de cereal frío", + "metric" : "50 g de cereal frío" + }, + { + "imperial" : "1 tortilla o rodaja de pan", + "metric" : "1 tortilla o rodaja de pan" + }, + { + "imperial" : "½ de un bagel o english muffin", + "metric" : "½ de un bagel o english muffin" + }, + { + "imperial" : "3 tazas de palomitas de maíz reventadas", + "metric" : "30 g de palomitas de maíz reventadas" + } + ], + "topic" : "es/topics/granos", + "varieties" : [ + { + "text" : "Cebada", + "topic" : "es/topics/cebada" + }, + { + "text" : "Alforfón", + "topic" : "es/topics/trigo-sarraceno" + }, + { + "text" : "Mijo", + "topic" : "es/topics/mijo" + }, + { + "text" : "Avena", + "topic" : "es/topics/avena-es" + }, + { + "text" : "Palomitas de maíz", + "topic" : "es/topics/palomitas-de-maiz" + }, + { + "text" : "Quinoa", + "topic" : "es/topics/quinoa-es" + }, + { + "text" : "Centeno", + "topic" : "es/topics/centeno" + }, + { + "text" : "Teff", + "topic" : "es/topics/teff-es" + }, + { + "text" : "Pasta integral", + "topic" : "es/topics/pasta-es" + } + ] + }, + "otherVitaminB12" : { + "heading" : "Vitamina B12", + "servings" : [ + + ], + "topic" : "es/topics/vitamin-b12", + "varieties" : [ + + ] + } + } +} diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/Localizable.strings b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/Localizable.strings new file mode 100644 index 00000000..981206da --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/Localizable.strings @@ -0,0 +1,162 @@ + + +/* Daily Dozen (proper noun) navigation tab */ +"navtab.doze"="Docena Diaria"; +/* 21 Tweaks (proper noun) navigation tab */ +"navtab.tweaks"="21 Ajustes"; +/* More Information navigation tab */ +"navtab.info"="Información"; +/* Preferences (aka Settings, Configuration) navigation tab. Choose word different from 'Tweaks' translation */ +"navtab.preferences"="Configuración"; + +/* Records: Daily Tally */ +"historyRecord.movingMean"="Media móvil"; +"historyRecordDoze.heading"="Historial de porciones"; +"historyRecordDoze.legend"="Porciones"; +"historyRecordTweak.heading"="Historial de ajustes"; +"historyRecordTweak.legend"="Ajustes"; +"historyRecordWeight.heading"="Historial de peso"; +"historyRecordWeight.titleImperial"="Peso (libras)"; +"historyRecordWeight.titleMetric"="Peso (kg)"; +"historyRecordWeight.legendMorning"="La mañana"; +"historyRecordWeight.legendEvening"="La tarde"; + +/* URL path */ +"urlSegment.base"="https://nutritionfacts.org/"; +"urlSegment.lang"="es/"; + +"urlSegmentInfoMenu.videos"="es/videos"; +"urlSegmentInfoMenu.book"="es/comer-para-no-morir"; +"urlSegmentInfoMenu.cookbook"="es/libro-de-recetas"; +"urlSegmentInfoMenu.diet"="es/"; +"urlSegmentInfoMenu.challenge"="es/objetivo-la-docena-diaria"; +"urlSegmentInfoMenu.donate"="es/dona-a-nutritionfacts-org"; +"urlSegmentInfoMenu.subscribe"="es/suscribete"; +"urlSegmentInfoMenu.source"="es/open-source"; + +"urlSegmentInfoMenu.team"="es/equipo"; + +/* Units toggle button text: metric measurement system */ +"unitToggle.metric"="Métrico"; +/* Units toggle button text: imperial measurement system */ +"unitToggle.imperial"="Imperial"; + +/* streak days format */ +"streakDaysFormat"="%d días"; + +/* display heading. */ +"dozeBeans.heading" = "Frijoles"; + +/* display heading. */ +"dozeBerries.heading" = "Bayas"; + +/* display heading */ +"dozeBeverages.heading" = "Bebidas"; + +/* display heading */ +"dozeExercise.heading" = "Ejercicio"; + +/* display heading */ +"dozeFlaxseeds.heading" = "Linaza"; + +/* display heading */ +"dozeFruitsOther.heading" = "Otras frutas"; + +/* display heading */ +"dozeGreens.heading" = "Vegetales de hoja verde"; + +/* display heading */ +"dozeNuts.heading" = "Nueces"; + +/* display heading */ +"dozeSpices.heading" = "Especias"; + +/* display heading */ +"dozeVegetablesCruciferous.heading" = "Vegetales crucíferos"; + +/* display heading */ +"dozeVegetablesOther.heading" = "Otros vegetales"; + +/* display heading */ +"dozeWholeGrains.heading" = "Granos integrales"; + +/* display heading */ +"otherOmega3.heading" = "Omega 3 de origen vegetal"; + +/* display heading */ +"otherVitaminB12.heading" = "Vitamina B12"; + +/* display heading */ +"otherVitaminD.heading" = "Vitamina D"; + +/* doze other info tiltle */ +"dozeOtherInfo.title"="Suplementos"; +/* doze other info message */ +"dozeOtherInfo.message"="La vitamina B12 es esencial para su salud, pero no cuenta para sus porciones diarias.\n\nVitamina B12 se incluye en esta aplicación para proporcionarle una manera fácil de realizar un seguimiento de su ingesta."; +/* doze other info confirm */ +"dozeOtherInfo.confirm"="OK"; + + +/* display heading */ +"tweakCompleteIntentions.heading" = "Complete sus intenciones de implementación"; + +/* display heading */ +"tweakDailyBlackCumin.heading" = "Comino negro (¼ cucharadita)"; + +/* display heading */ +"tweakDailyCumin.heading" = "Comino (½ cucharadita con almuerzo y cena)"; + +/* display heading */ +"tweakDailyDeflourDiet.heading" = "Granos intactos"; + +/* display heading */ +"tweakDailyFrontLoad.heading" = "Carga frontal tus calorías"; + +/* display heading */ +"tweakDailyGarlic.heading" = "Ajo en polvo (¼ cucharadita)"; + +/* display heading */ +"tweakDailyGinger.heading" = "Jengibre molido (1 cucharadita) o pimienta de cayena (½ cucharadita)"; + +/* display heading */ +"tweakDailyGreenTea.heading" = "Té verde (3 tazas)"; + +/* display heading */ +"tweakDailyHydrate.heading" = "Mantente hidratado"; + +/* display heading */ +"tweakDailyNutriYeast.heading" = "Levadura nutricional (2 cucharaditas)"; + +/* display heading */ +"tweakDailyTimeRestrict.heading" = "Restrinja su tiempo de comer"; + +/* display heading */ +"tweakExerciseTiming.heading" = "Optimizar el tiempo de ejercicio"; + +/* display heading */ +"tweakMeal20Minutes.heading" = "Siga la regla de veinte minutos"; + +/* display heading */ +"tweakMealNegCal.heading" = "Precargar con alimentos de \"calorías negativas\""; + +/* display heading */ +"tweakMealUndistracted.heading" = "Disfruta de comidas sin distracciones"; + +/* display heading */ +"tweakMealVinegar.heading" = "Incorporar vinagre (2 cucharaditas con cada comida)"; + +/* display heading */ +"tweakMealWater.heading" = "Precargar con agua"; + +/* display heading */ +"tweakNightlyFast.heading" = "Ayuno después de las 7:00 p.m."; + +/* display heading */ +"tweakNightlySleep.heading" = "Dormir lo suficiente"; + +/* display heading */ +"tweakNightlyTrendelenbrug.heading" = "Experimente con Trendelenburg leve"; + +/* display heading */ +"tweakWeightTwice.heading" = "Pésate dos veces al día"; + diff --git a/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/TweakDetailData.json b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/TweakDetailData.json new file mode 100644 index 00000000..58950da2 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/LocalStrings/es.lproj/TweakDetailData.json @@ -0,0 +1,235 @@ +{ + "itemsDict" : { + "tweakCompleteIntentions" : { + "activity" : { + "imperial" : "Complete sus intenciones de implementación", + "metric" : "Complete sus intenciones de implementación" + }, + "description" : [ + "Cada dos meses, cree tres nuevas intenciones de implementación: \"si es X, entonces Y\" planea realizar un comportamiento particular en un contexto específico, y marque cada una de ellas a medida que las completa todos los días." + ], + "heading" : "Complete sus intenciones de implementación", + "topic" : "" + }, + "tweakDailyBlackCumin" : { + "activity" : { + "imperial" : "Comino negro (Nigella sativa) (¼ cucharadita)", + "metric" : "Comino negro (Nigella sativa) (¼ cucharadita)" + }, + "description" : [ + "Como se señaló en la sección Supresión del apetito, una revisión sistemática y un metanálisis de ensayos aleatorizados y controlados de pérdida de peso encontraron que aproximadamente un cuarto de cucharadita de comino negro en polvo al día parece reducir el índice de masa corporal en un lapso de un par de meses. Tenga en cuenta que el comino negro es diferente del comino normal, para el cual la dosificación es diferente." + ], + "heading" : "Comino negro (¼ cucharadita)", + "topic" : "" + }, + "tweakDailyCumin" : { + "activity" : { + "imperial" : "Comino (Cuminum cyminum) (½ cucharadita con almuerzo y cena)", + "metric" : "Comino (Cuminum cyminum) (½ cucharadita con almuerzo y cena)" + }, + "description" : [ + "Las mujeres con sobrepeso aleatorizadas para agregar media cucharadita de comino a sus almuerzos y cenas vencieron al grupo de control por cuatro libras más y una pulgada extra de sus cinturas. También hay evidencia para apoyar el uso del azafrán de especias, pero una pizca al día costaría un dólar, mientras que una cucharadita de comino cuesta menos de diez centavos." + ], + "heading" : "Comino (½ cucharadita con almuerzo y cena)", + "topic" : "" + }, + "tweakDailyDeflourDiet" : { + "activity" : { + "imperial" : "Deflour Your Diet", + "metric" : "Deflour Your Diet" + }, + "description" : [ + "Marque esta casilla todos los días sus porciones de granos enteros están en forma de granos intactos. La pulverización de incluso 100 por ciento de granos enteros priva a nuestros microbiomas del almidón que de otro modo sería transportado a nuestros dos puntos encerrados en paredes celulares intactas." + ], + "heading" : "Granos intactos", + "topic" : "" + }, + "tweakDailyFrontLoad" : { + "activity" : { + "imperial" : "Carga frontal tus calorías", + "metric" : "Carga frontal tus calorías" + }, + "description" : [ + "Hay beneficios metabólicos en la distribución de más calorías a más temprano en el día, así que haga que el desayuno (idealmente) o el almuerzo sea su comida más grande del día al verdadero estilo rey \/ príncipe \/ mendigo." + ], + "heading" : "Carga frontal tus calorías", + "topic" : "" + }, + "tweakDailyGarlic" : { + "activity" : { + "imperial" : "Ajo en polvo (¼ cucharadita)", + "metric" : "Ajo en polvo (¼ cucharadita)" + }, + "description" : [ + "Estudios aleatorizados, doble ciego, controlados con placebo han encontrado que tan poco como un cuarto de cucharadita diaria de ajo en polvo puede reducir la grasa corporal a un costo de quizás dos centavos por día." + ], + "heading" : "Ajo en polvo (¼ cucharadita)", + "topic" : "" + }, + "tweakDailyGinger" : { + "activity" : { + "imperial" : "Jengibre molido (1 cucharadita) o pimienta de cayena (½ cucharadita)", + "metric" : "Jengibre molido (1 cucharadita) o pimienta de cayena (½ cucharadita)" + }, + "description" : [ + "Ensayos controlados aleatorios han encontrado que ¼ cucharadita a 1½ cucharaditas al día de jengibre molido disminuyeron significativamente el peso corporal por solo centavos al día. Puede ser tan fácil como remover la especia molida en una taza de agua caliente. Nota: el jengibre puede funcionar mejor por la mañana que por la tarde. El té Chai es una forma sabrosa de combinar los trucos de té verde y jengibre en una sola bebida. Alternativamente, para la activación de BAT, puede agregar un chile jalapeño crudo o media cucharadita de polvo de pimiento rojo (o, presumiblemente, hojuelas de pimiento rojo molido) en su dieta diaria. Para ayudar a combatir el calor, puedes cortar en rodajas muy finas o picar finamente el jalapeño para reducir su picadura a pequeños pinchazos, o mezclar el pimiento rojo en la sopa o el batido de verduras de alimentos integrales que presenté en uno de mis videos de cocina en NutritionFacts.org ." + ], + "heading" : "Jengibre molido (1 cucharadita) o pimienta de cayena (½ cucharadita)", + "topic" : "" + }, + "tweakDailyGreenTea" : { + "activity" : { + "imperial" : "Té Verde (3 tazas)", + "metric" : "Té Verde (3 tazas)" + }, + "description" : [ + "Beba tres tazas al día entre comidas (esperando al menos una hora después de una comida para no interferir con la absorción de hierro). Durante las comidas, beba agua, café negro o té de hibisco mezclado 6: 1 con verbena de limón, pero nunca exceda tres tazas de líquido por hora (importante dado mi consejo de precarga de agua). Aproveche el efecto reforzador de la cafeína bebiendo su té verde junto con algo saludable que desearía que le gustara más, pero no consuma grandes cantidades de cafeína dentro de las seis horas antes de acostarse. Lo mejor es tomar el té sin edulcorante, pero si normalmente endulzas tu té con miel o azúcar, prueba con jarabe de yacón." + ], + "heading" : "Té verde (3 tazas)", + "topic" : "" + }, + "tweakDailyHydrate" : { + "activity" : { + "imperial" : "Mantente hidratado", + "metric" : "Mantente hidratado" + }, + "description" : [ + "Marque esta casilla si su orina nunca aparece más oscura que un amarillo pálido.\n\nTenga en cuenta que si está comiendo alimentos fortificados con riboflavina (como levadura nutricional), basar esto en obtener nueve tazas de bebidas sin azúcar al día para las mujeres (que se encargarían de las recomendaciones de precarga de té verde y agua) o trece tazas al día para hombres.\n\nSi tiene problemas cardíacos o renales, no aumente la ingesta de líquidos sin antes hablar con su médico.\n\nRecuerde, el refresco de dieta puede no tener calorías, pero no está libre de consecuencias, como aprendimos en la sección Bajo en azúcar agregada." + ], + "heading" : "Mantente hidratado", + "topic" : "" + }, + "tweakDailyNutriYeast" : { + "activity" : { + "imperial" : "Levadura Nutricional (2 cucharaditas)", + "metric" : "Levadura Nutricional (2 cucharaditas)" + }, + "description" : [ + "Dos cucharaditas de levadura de panadería, de cerveza o nutricional contienen aproximadamente la cantidad de glucanos beta 1,3 \/ 1,6 encontrados en ensayos clínicos aleatorizados, doble ciego, controlados con placebo para facilitar la pérdida de peso." + ], + "heading" : "Levadura nutricional (2 cucharaditas)", + "topic" : "" + }, + "tweakDailyTimeRestrict" : { + "activity" : { + "imperial" : "Restrinja su tiempo de comer", + "metric" : "Restrinja su tiempo de comer" + }, + "description" : [ + "Limite la comida a un período diario de su elección de menos de doce horas de duración que pueda cumplir de manera constante, los siete días de la semana. Dados los beneficios circadianos de reducir la ingesta de alimentos por la noche, la ventana debe finalizar antes de las 7:00 p.m." + ], + "heading" : "Restrinja su tiempo de comer", + "topic" : "" + }, + "tweakExerciseTiming" : { + "activity" : { + "imperial" : "Optimizar el tiempo de ejercicio", + "metric" : "Optimizar el tiempo de ejercicio" + }, + "description" : [ + "La recomendación de Daily Dozen para una duración óptima del ejercicio para la longevidad es de noventa minutos de actividad moderadamente intensa al día, que también es la duración óptima del ejercicio para perder peso. Cualquier momento es bueno, y cuanto más mejor, pero puede haber una ventaja de hacer ejercicio en ayunas, al menos seis horas después de su última comida. Por lo general, esto significaría antes del desayuno, pero si lo cronometró correctamente, podría hacer ejercicio al mediodía antes de un almuerzo tardío o, si el almuerzo se come lo suficientemente temprano, antes de la cena. Este es el momento para los no diabéticos. Los diabéticos y prediabéticos deberían comenzar a hacer ejercicio treinta minutos después del comienzo de una comida e idealmente durante al menos una hora para alcanzar por completo el pico de azúcar en la sangre. Si tuviera que elegir una sola comida para hacer ejercicio después, sería la cena, debido al ritmo circadiano del control del azúcar en la sangre que disminuye durante todo el día. Sin embargo, idealmente, el desayuno sería la comida más grande del día, y después de eso harías ejercicio, o mejor aún, después de cada comida." + ], + "heading" : "Optimizar el tiempo de ejercicio", + "topic" : "" + }, + "tweakMeal20Minutes" : { + "activity" : { + "imperial" : "Siga la regla de veinte minutos", + "metric" : "Siga la regla de veinte minutos" + }, + "description" : [ + "Ya sea a través del aumento de la viscosidad o la cantidad de masticables, o la disminución del tamaño de la mordida y la tasa de alimentación, docenas de estudios han demostrado que, sin importar cómo aumentemos la cantidad de tiempo que los alimentos están en la boca, puede resultar en una menor ingesta calórica. Por lo tanto, extienda la duración de la comida al menos veinte minutos para permitir que sus señales de saciedad natural surtan efecto. ¿Cómo? Al elegir alimentos que tardan más en comer y comerlos de una manera que prolongue el tiempo que permanecen en la boca. Piense en alimentos más voluminosos, más resistentes y masticables en bocados más pequeños y bien masticados." + ], + "heading" : "Siga la regla de veinte minutos", + "topic" : "" + }, + "tweakMealNegCal" : { + "activity" : { + "imperial" : "Precargar con alimentos de \"calorías negativas\"", + "metric" : "Precargar con alimentos de \"calorías negativas\"" + }, + "description" : [ + "Como primer plato, comience cada comida con una manzana o una sopa o ensalada Green Light que contenga menos de cien calorías por taza." + ], + "heading" : "Precargar con alimentos de \"calorías negativas\"", + "topic" : "" + }, + "tweakMealUndistracted" : { + "activity" : { + "imperial" : "Disfruta de comidas sin distracciones", + "metric" : "Disfruta de comidas sin distracciones" + }, + "description" : [ + "No coma mientras mira televisión o juega en su teléfono. Date un cheque por cada comida que puedas comer sin distracciones." + ], + "heading" : "Disfruta de comidas sin distracciones", + "topic" : "" + }, + "tweakMealVinegar" : { + "activity" : { + "imperial" : "Incorporar vinagre (2 cucharaditas con cada comida)", + "metric" : "Incorporar vinagre (2 cucharaditas con cada comida)" + }, + "description" : [ + "Nunca beba vinagre solo. En cambio, condimente las comidas o aderece una ensalada con cualquiera de los vinagres dulces y salados que hay. Si desea beberlo, asegúrese de mezclarlo en un vaso de agua y, luego, asegúrese de enjuagarse la boca con agua para proteger el esmalte dental." + ], + "heading" : "Incorporar vinagre (2 cucharaditas con cada comida)", + "topic" : "" + }, + "tweakMealWater" : { + "activity" : { + "imperial" : "Precargar con agua", + "metric" : "Precargar con agua" + }, + "description" : [ + "Mida dos tazas de agua sin sabor fría o fría antes de cada comida para aumentar su metabolismo y aprovechar sus beneficios de precarga." + ], + "heading" : "Precargar con agua", + "topic" : "" + }, + "tweakNightlyFast" : { + "activity" : { + "imperial" : "Ayuno después de las 7:00 p.m.", + "metric" : "Ayuno después de las 7:00 p.m." + }, + "description" : [ + "Debido a nuestros ritmos circadianos, la comida que se come por la noche engorda más que la misma comida que se come más temprano en el día, tan rápido todas las noches durante al menos doce horas a partir de las 7:00 p.m. Cuantas menos calorías después de la puesta del sol, mejor." + ], + "heading" : "Ayuno después de las 7:00 p.m.", + "topic" : "" + }, + "tweakNightlySleep" : { + "activity" : { + "imperial" : "Dormir lo suficiente", + "metric" : "Dormir lo suficiente" + }, + "description" : [ + "Marque esta casilla si duerme al menos siete horas antes de acostarse." + ], + "heading" : "Dormir lo suficiente", + "topic" : "" + }, + "tweakNightlyTrendelenbrug" : { + "activity" : { + "imperial" : "Experimente con Trendelenburg leve", + "metric" : "Experimente con Trendelenburg leve" + }, + "description" : [ + "Intenta pasar al menos cuatro horas por noche acostado con tu cuerpo inclinado hacia abajo seis grados elevando los postes al pie de tu cama por ocho pulgadas (o nueve pulgadas si tienes un rey de California). Tenga mucho cuidado cuando salga de la cama, ya que esto causa intolerancia ortostática en la mayoría de las personas, incluso si es joven y saludable, lo que significa que si se levanta demasiado rápido, puede sentirse mareado, desmayado o aturdido y podría caer y lastimarse. Así que levántate lentamente. Beber dos tazas de agua fría treinta minutos antes de levantarse también puede ayudar a prevenir este efecto secundario potencialmente peligroso.\n\nIMPORTANTE: no intente esto en casa si tiene problemas cardíacos o pulmonares, reflujo ácido o problemas con su cerebro (como traumatismo craneal) u ojos (incluso un historial familiar de glaucoma lo descalifica). Además, no intente esto hasta que le pregunte a su médico si cree que es seguro para usted dormir en Trendelenburg leve." + ], + "heading" : "Experimente con Trendelenburg leve", + "topic" : "" + }, + "tweakWeightTwice" : { + "activity" : { + "imperial" : "Pésate dos veces al día", + "metric" : "Pésate dos veces al día" + }, + "description" : [ + "El autopeso regular se considera crucial para el control del peso a largo plazo, pero no hay pruebas suficientes para respaldar una frecuencia específica de pesaje. Mi recomendación se basa en el único estudio que encontró que dos veces al día, al despertar y justo antes de acostarse, parecía superior a una vez al día (aproximadamente seis versus dos libras de pérdida de peso durante doce semanas)." + ], + "heading" : "Pésate dos veces al día", + "topic" : "" + } + } +} diff --git a/DailyDozen/DailyDozen/App/Texts/TextsProvider.swift b/DailyDozen/DailyDozen/App/Texts/TextsProvider.swift deleted file mode 100644 index 4f914732..00000000 --- a/DailyDozen/DailyDozen/App/Texts/TextsProvider.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// TextsProvider.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 07.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import Foundation - -class TextsProvider { - - // MARK: - Nested - private struct Keys { - static let details = "Details" - static let sizes = "Sizes" - static let metric = "Metric" - static let imperial = "Imperial" - static let types = "Types" - static let topic = "Topic" - static let plist = "plist" - } - - /// Returns the shared TextsProvider object. - static let shared: TextsProvider = { - guard - let path = Bundle.main.path( - forResource: Keys.details, ofType: Keys.plist) - else { fatalError("There should be a settings file") } - - guard - let dictionary = NSDictionary(contentsOfFile: path) as? [String : Any] - else { fatalError("There should be a dictionary") } - - return TextsProvider(dictionary: dictionary) - }() - - private let dictionary: [String : Any] - - init(dictionary: [String : Any]) { - self.dictionary = dictionary - } - - /// Loads static texts for the current item. - /// - /// - Parameter itemName: The current item name. - /// - Returns: A detail view model for static texts. - func loadDetail(for itemName: String) -> DetailViewModel { - guard - let item = dictionary[itemName] as? [String: Any] - else { fatalError("There should be an item") } - - guard - let sizes = item[Keys.sizes] as? [String: Any], - let metric = sizes[Keys.metric] as? [String], - let imperial = sizes[Keys.imperial] as? [String] - else { fatalError("There should be sizes") } - - guard - let types = item[Keys.types] as? [[String: String]] - else { fatalError("There should be types") } - - guard - let topic = item[Keys.topic] as? String - else { fatalError("There should be a topic") } - - return DetailViewModel(itemName: itemName, topic: topic, metricSizes: metric, imperialSizes: imperial, types: types) - } - - /// Returns the topic for the current item name. - /// - /// - Parameter itemName: The current item name. - /// - Returns: The topic. - func getTopic(for itemName: String) -> String { - guard - let item = dictionary[itemName] as? [String: Any] - else { fatalError("There should be an item") } - - guard - let topic = item[Keys.topic] as? String - else { fatalError("There should be a topic") } - - return topic - } -} diff --git a/DailyDozen/DailyDozen/App/Texts/TweakTextsProvider.swift b/DailyDozen/DailyDozen/App/Texts/TweakTextsProvider.swift new file mode 100644 index 00000000..c155a5ca --- /dev/null +++ b/DailyDozen/DailyDozen/App/Texts/TweakTextsProvider.swift @@ -0,0 +1,67 @@ +// +// TweakTextsProvider.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +class TweakTextsProvider { + + static let shared: TweakTextsProvider = { + let decoder = JSONDecoder() + guard + let path = Bundle.main.path(forResource: "TweakDetailData", ofType: "json"), + let jsonString = try? String(contentsOfFile: path), + let jsonData = jsonString.data(using: .utf8), + let info = try? decoder.decode(TweakDetailInfo.self, from: jsonData) + else { + fatalError("FAIL TweakTextsProvider did not load 'TweakDetailData.json'") + } + return TweakTextsProvider(info: info) + }() + + private let info: TweakDetailInfo + + init(info: TweakDetailInfo) { + self.info = info + } + + /// Loads static texts for the current item. + /// + /// - Parameter itemName: The current item name. + /// - Returns: A detail view model for static texts. + func getDetails(itemTypeKey: String) -> TweakDetailViewModel { + guard + let itemInfo = info.itemsDict[itemTypeKey] + else { fatalError("Tweak getTopic(\(itemTypeKey)) Item not found.") } + return TweakDetailViewModel(itemTypeKey: itemTypeKey, info: itemInfo) + } + + /// Returns the URL topic for the current item name. + /// + /// Use: + /// + /// ``` + /// https://nutritionfacts.org/topics/TOPIC/ + /// ``` + /// + /// - Parameter itemName: The current item name. + /// - Returns: URL path TOPIC component. + func getTopic(itemTypeKey: String) -> String { + guard + let item = info.itemsDict[itemTypeKey] + else { fatalError("Tweak getTopic(\(itemTypeKey)) Item not found.") } + return item.topic + } + + /// Use: do not show toggle button if imperial and metric have the same text. + func isMetricTxtEqualToImperialTxt(itemTypeKey: String) -> Bool { + guard + let item = info.itemsDict[itemTypeKey] + else { fatalError("Tweak getTopic(\(itemTypeKey)) Item not found.") } + return item.activity.imperial == item.activity.metric + } + +} diff --git a/DailyDozen/DailyDozen/App/Views/Base.lproj/FirstLaunch.storyboard b/DailyDozen/DailyDozen/App/Views/Base.lproj/FirstLaunch.storyboard new file mode 100644 index 00000000..cce77a03 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Views/Base.lproj/FirstLaunch.storyboard @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/App/Storyboards/Base.lproj/LaunchScreen.storyboard b/DailyDozen/DailyDozen/App/Views/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from DailyDozen/DailyDozen/App/Storyboards/Base.lproj/LaunchScreen.storyboard rename to DailyDozen/DailyDozen/App/Views/Base.lproj/LaunchScreen.storyboard diff --git a/DailyDozen/DailyDozen/App/Views/Base.lproj/Main.storyboard b/DailyDozen/DailyDozen/App/Views/Base.lproj/Main.storyboard new file mode 100644 index 00000000..c634fb44 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Views/Base.lproj/Main.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/App/Views/UIButtonCheckbox.swift b/DailyDozen/DailyDozen/App/Views/UIButtonCheckbox.swift new file mode 100644 index 00000000..940a5b82 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Views/UIButtonCheckbox.swift @@ -0,0 +1,108 @@ +// +// UIButtonCheckbox.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +@IBDesignable open class UIButtonCheckbox: UIButton { + +// MARK: - Parameters + + /// Checkbox border color + @IBInspectable var borderColor: UIColor = UIColor.greenLightColor { + didSet { + layer.borderColor = borderColor.cgColor + } + } + + /// Checkbox border width + @IBInspectable var borderWidth: CGFloat = 2.0 { + didSet { + layer.borderWidth = borderWidth + } + } + + /// Checkbox corner radius + @IBInspectable var cornerRadius: CGFloat = 5.0 { + didSet { + layer.cornerRadius = cornerRadius + } + } + + /// Checkbox boundary padding + @IBInspectable var padding: CGFloat = CGFloat(15) + + var checkboxImage: UIImage? + + // MARK: - Init + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initDefaultParams() + } + + override init(frame: CGRect) { + super.init(frame: frame) + initDefaultParams() + } + + /// Checked/Unchecked status variable + override open var isSelected: Bool { + didSet { + super.isSelected = isSelected + onSelectStateChanged?(self, isSelected) + + setBackgroundImage(isSelected ? checkboxImage : nil, for: .normal) + } + } + + /// Checkbox status change callback + open var onSelectStateChanged: ((_ checkbox: UIButtonCheckbox, _ selected: Bool) -> Void)? + + /// Increase clickable pointing area + override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + + let newBound = CGRect( + x: self.bounds.origin.x - padding, + y: self.bounds.origin.y - padding, + width: self.bounds.width + 2 * padding, + height: self.bounds.width + 2 * padding + ) + + return newBound.contains(point) + } + + override open func prepareForInterfaceBuilder() { + setTitle("", for: UIControl.State()) + } + +} + +// MARK: - Private methods + +public extension UIButtonCheckbox { + + fileprivate func initDefaultParams() { + addTarget(self, action: #selector(UIButtonCheckbox.checkboxTapped), for: .touchUpInside) + setTitle(nil, for: UIControl.State()) + + clipsToBounds = true + + setCheckboxImage() + } + + fileprivate func setCheckboxImage() { + // Image background color must match `*StateCell` border color + // See `*StateCell` configure(…) for details. + let image = UIImage(named: "ic_checkmark_white_green") + imageView?.contentMode = .scaleAspectFit + self.checkboxImage = image + } + + @objc fileprivate func checkboxTapped(_ sender: UIButtonCheckbox) { + isSelected = !isSelected + } +} diff --git a/DailyDozen/DailyDozen/App/Views/es.lproj/FirstLaunch.strings b/DailyDozen/DailyDozen/App/Views/es.lproj/FirstLaunch.strings new file mode 100644 index 00000000..1dc3b8a1 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Views/es.lproj/FirstLaunch.strings @@ -0,0 +1,12 @@ +/* Class = "UILabel"; text = "For Health Alone"; ObjectID = "atR-un-jgX"; */ +"atR-un-jgX.text" = "Para la salud sola"; + +/* Class = "UIButton"; normalTitle = "Daily Dozen + 21 Tweaks"; ObjectID = "Lef-Iw-Ywr"; */ +"Lef-Iw-Ywr.normalTitle" = "Docena Diaria + 21 Ajustes"; + +/* Class = "UILabel"; text = "For Health and Weight Loss"; ObjectID = "OdG-YQ-lT8"; */ +"OdG-YQ-lT8.text" = "Para la salud y la pérdida de peso"; + +/* Class = "UIButton"; normalTitle = "Daily Dozen Only"; ObjectID = "ReM-AP-Fpp"; */ +"ReM-AP-Fpp.normalTitle" = "Docena Diaria solamente"; + diff --git a/DailyDozen/DailyDozen/App/Views/es.lproj/Main.strings b/DailyDozen/DailyDozen/App/Views/es.lproj/Main.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/DailyDozen/DailyDozen/App/Views/es.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/DailyDozen/DailyDozen/DailyDozen.entitlements b/DailyDozen/DailyDozen/DailyDozen.entitlements new file mode 100644 index 00000000..14aac7d2 --- /dev/null +++ b/DailyDozen/DailyDozen/DailyDozen.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + health-records + + com.apple.developer.icloud-container-identifiers + + + diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DailyTracker.swift b/DailyDozen/DailyDozen/Database/DataRecords/DailyTracker.swift new file mode 100644 index 00000000..e2a7fd96 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DailyTracker.swift @@ -0,0 +1,54 @@ +// +// DailyTracker.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +struct DailyTracker { + + let date: Date + + var itemsDict: [DataCountType: DataCountRecord] + // Weight + var weightAM: DataWeightRecord + var weightPM: DataWeightRecord + + init(date: Date) { + self.date = date + + itemsDict = [DataCountType: DataCountRecord]() + for dataCountType in DataCountType.allCases { + itemsDict[dataCountType] = DataCountRecord(date: date, countType: dataCountType) + } + self.weightAM = DataWeightRecord(date: date, weightType: .am, kg: 0.0) + self.weightPM = DataWeightRecord(date: date, weightType: .pm, kg: 0.0) + } + + func setCount(typeKey: DataCountType, countText: String) { + if let value = Int(countText) { + setCount(typeKey: typeKey, count: value) + } else { + LogService.shared.error( + "DailyTracker setCount() countText \(countText) not convertable" + ) + } + } + + func setCount(typeKey: DataCountType, count: Int) { + if let dataCountRecord = itemsDict[typeKey] { + dataCountRecord.setCount(count) + } else { + LogService.shared.error( + "DailyTracker setCount() type not found \(typeKey.typeKey)" + ) + } + } + + func getPid(typeKey: DataCountType) -> String { + return "\(date.datestampKey).\(typeKey.typeKey)" + } + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataCountAttributes.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataCountAttributes.swift new file mode 100644 index 00000000..cd8fc513 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataCountAttributes.swift @@ -0,0 +1,207 @@ +// +// DataCountAttributes.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation +import UIKit // CGFloat + +struct DataCountAttributes { + + struct Attribute { + let countType: DataCountType + let headingDisplay: String + let headingCSV: String + let maxServings: Int + } + + static let shared = DataCountAttributes() + + let dict: [DataCountType: Attribute] = [ + // Daily Dozen Servings + .dozeBeans: Attribute( + countType: .dozeBeans, + headingDisplay: NSLocalizedString("dozeBeans.heading", comment: "display heading."), + headingCSV: "Beans", + maxServings: 3), + .dozeBerries: Attribute( + countType: .dozeBerries, + headingDisplay: NSLocalizedString("dozeBerries.heading", comment: "display heading."), + headingCSV: "Berries", + maxServings: 1), + .dozeFruitsOther: Attribute( + countType: .dozeFruitsOther, + headingDisplay: NSLocalizedString("dozeFruitsOther.heading", comment: "display heading"), + headingCSV: "Other Fruits", + maxServings: 3), + .dozeVegetablesCruciferous: Attribute( + countType: .dozeVegetablesCruciferous, + headingDisplay: NSLocalizedString("dozeVegetablesCruciferous.heading", comment: "display heading"), + headingCSV: "Cruciferous Vegetables", + maxServings: 1), + .dozeGreens: Attribute( + countType: .dozeGreens, + headingDisplay: NSLocalizedString("dozeGreens.heading", comment: "display heading"), + headingCSV: "Greens", + maxServings: 2), + .dozeVegetablesOther: Attribute( + countType: .dozeVegetablesOther, + headingDisplay: NSLocalizedString("dozeVegetablesOther.heading", comment: "display heading"), + headingCSV: "Other Vegetables", + maxServings: 2), + .dozeFlaxseeds: Attribute( + countType: .dozeFlaxseeds, + headingDisplay: NSLocalizedString("dozeFlaxseeds.heading", comment: "display heading"), + headingCSV: "Flaxseeds", + maxServings: 1), + .dozeNuts: Attribute( + countType: .dozeNuts, + headingDisplay: NSLocalizedString("dozeNuts.heading", comment: "display heading"), + headingCSV: "Nuts", + maxServings: 1), + .dozeSpices: Attribute( + countType: .dozeSpices, + headingDisplay: NSLocalizedString("dozeSpices.heading", comment: "display heading"), + headingCSV: "Spices", + maxServings: 1), + .dozeWholeGrains: Attribute( + countType: .dozeWholeGrains, + headingDisplay: NSLocalizedString("dozeWholeGrains.heading", comment: "display heading"), + headingCSV: "Whole Grains", + maxServings: 3), + .dozeBeverages: Attribute( + countType: .dozeBeverages, + headingDisplay: NSLocalizedString("dozeBeverages.heading", comment: "display heading"), + headingCSV: "Beverages", + maxServings: 5), + .dozeExercise: Attribute( + countType: .dozeExercise, + headingDisplay: NSLocalizedString("dozeExercise.heading", comment: "display heading"), + headingCSV: "Exercise", + maxServings: 1), + .otherVitaminB12: Attribute( + countType: .otherVitaminB12, + headingDisplay: NSLocalizedString("otherVitaminB12.heading", comment: "display heading"), + headingCSV: "Vitamin B12", + maxServings: 1), + .otherVitaminD: Attribute( + countType: .otherVitaminD, + headingDisplay: NSLocalizedString("otherVitaminD.heading", comment: "display heading"), + headingCSV: "Vitamin D", + maxServings: 1), + .otherOmega3: Attribute( + countType: .otherOmega3, + headingDisplay: NSLocalizedString("otherOmega3.heading", comment: "display heading"), + headingCSV: "Omega 3", + maxServings: 1), + // 21 Tweaks + .tweakMealWater: Attribute( + countType: .tweakMealWater, + headingDisplay: NSLocalizedString("tweakMealWater.heading", comment: "display heading"), + headingCSV: "Meal Water", + maxServings: 3), + .tweakMealNegCal: Attribute( + countType: .tweakMealNegCal, + headingDisplay: NSLocalizedString("tweakMealNegCal.heading", comment: "display heading"), + headingCSV: "Meal NegCal", + maxServings: 3), + .tweakMealVinegar: Attribute( + countType: .tweakMealVinegar, + headingDisplay: NSLocalizedString("tweakMealVinegar.heading", comment: "display heading"), + headingCSV: "Meal Vinegar", + maxServings: 3), + .tweakMealUndistracted: Attribute( + countType: .tweakMealUndistracted, + headingDisplay: NSLocalizedString("tweakMealUndistracted.heading", comment: "display heading"), + headingCSV: "Meal Undistracted", + maxServings: 3), + .tweakMeal20Minutes: Attribute( + countType: .tweakMeal20Minutes, + headingDisplay: NSLocalizedString("tweakMeal20Minutes.heading", comment: "display heading"), + headingCSV: "Meal 20 Minutes", + maxServings: 3), + .tweakDailyBlackCumin: Attribute( + countType: .tweakDailyBlackCumin, + headingDisplay: NSLocalizedString("tweakDailyBlackCumin.heading", comment: "display heading"), + headingCSV: "Daily Black Cumin", + maxServings: 1), + .tweakDailyGarlic: Attribute( + countType: .tweakDailyGarlic, + headingDisplay: NSLocalizedString("tweakDailyGarlic.heading", comment: "display heading"), + headingCSV: "Daily Garlic", + maxServings: 1), + .tweakDailyGinger: Attribute( + countType: .tweakDailyGinger, + headingDisplay: NSLocalizedString("tweakDailyGinger.heading", comment: "display heading"), + headingCSV: "Daily Ginger", + maxServings: 1), + .tweakDailyNutriYeast: Attribute( + countType: .tweakDailyNutriYeast, + headingDisplay: NSLocalizedString("tweakDailyNutriYeast.heading", comment: "display heading"), + headingCSV: "Daily NutriYeast", + maxServings: 1), + .tweakDailyCumin: Attribute( + countType: .tweakDailyCumin, + headingDisplay: NSLocalizedString("tweakDailyCumin.heading", comment: "display heading"), + headingCSV: "Daily Cumin", + maxServings: 2), + .tweakDailyGreenTea: Attribute( + countType: .tweakDailyGreenTea, + headingDisplay: NSLocalizedString("tweakDailyGreenTea.heading", comment: "display heading"), + headingCSV: "Daily Green Tea", + maxServings: 3), + .tweakDailyHydrate: Attribute( + countType: .tweakDailyHydrate, + headingDisplay: NSLocalizedString("tweakDailyHydrate.heading", comment: "display heading"), + headingCSV: "Daily Hydrate", + maxServings: 1), + .tweakDailyDeflourDiet: Attribute( + countType: .tweakDailyDeflourDiet, + headingDisplay: NSLocalizedString("tweakDailyDeflourDiet.heading", comment: "display heading"), + headingCSV: "Daily Deflour Diet", + maxServings: 1), + .tweakDailyFrontLoad: Attribute( + countType: .tweakDailyFrontLoad, + headingDisplay: NSLocalizedString("tweakDailyFrontLoad.heading", comment: "display heading"), + headingCSV: "Daily Front-Load", + maxServings: 1), + .tweakDailyTimeRestrict: Attribute( + countType: .tweakDailyTimeRestrict, + headingDisplay: NSLocalizedString("tweakDailyTimeRestrict.heading", comment: "display heading"), + headingCSV: "Daily Time-Restrict", + maxServings: 1), + .tweakExerciseTiming: Attribute( + countType: .tweakExerciseTiming, + headingDisplay: NSLocalizedString("tweakExerciseTiming.heading", comment: "display heading"), + headingCSV: "Exercise Timing", + maxServings: 1), + .tweakWeightTwice: Attribute( + countType: .tweakWeightTwice, + headingDisplay: NSLocalizedString("tweakWeightTwice.heading", comment: "display heading"), + headingCSV: "Weight Twice", + maxServings: 2), + .tweakCompleteIntentions: Attribute( + countType: .tweakCompleteIntentions, + headingDisplay: NSLocalizedString("tweakCompleteIntentions.heading", comment: "display heading"), + headingCSV: "Complete Intentions", + maxServings: 3), + .tweakNightlyFast: Attribute( + countType: .tweakNightlyFast, + headingDisplay: NSLocalizedString("tweakNightlyFast.heading", comment: "display heading"), + headingCSV: "Nightly Fast", + maxServings: 1), + .tweakNightlySleep: Attribute( + countType: .tweakNightlySleep, + headingDisplay: NSLocalizedString("tweakNightlySleep.heading", comment: "display heading"), + headingCSV: "Nightly Sleep", + maxServings: 1), + .tweakNightlyTrendelenbrug: Attribute( + countType: .tweakNightlyTrendelenbrug, + headingDisplay: NSLocalizedString("tweakNightlyTrendelenbrug.heading", comment: "display heading"), + headingCSV: "Nightly Trendelenbrug", + maxServings: 1) + ] + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataCountRecord.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataCountRecord.swift new file mode 100644 index 00000000..4e2f06d3 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataCountRecord.swift @@ -0,0 +1,133 @@ +// +// DataCountRecord.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation +import RealmSwift + +class DataCountRecord: Object { + + // MARK: - RealmDB Persisted Properties + + /// yyyyMMdd.typeKey e.g. 20190101.beansKey + @objc dynamic var pid: String = "" + /// daily servings completed + @objc dynamic var count: Int = 0 + /// consecutive days-to-date with all servings completed + @objc dynamic var streak: Int = 0 + + var pidKeys: (datestampKey: String, typeKey: String) { + let parts = self.pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + var pidParts: (datestamp: Date, countType: DataCountType)? { + guard let date = Date.init(datestampKey: pidKeys.datestampKey), + let countType = DataCountType(itemTypeKey: pidKeys.typeKey) else { + LogService.shared.error( + "DataCountRecord pidParts has invalid datestamp or typeKey" + ) + return nil + } + return (datestamp: date, countType: countType) + } + + // MARK: Class Methods + + static func pid(date: Date, countType: DataCountType) -> String { + return "\(date.datestampKey).\(countType.typeKey)" + } + + static func pid(datestampKey: String, typeKey: String) -> String { + return "\(datestampKey).\(typeKey)" + } + + static func pidKeys(pid: String) -> (datestampKey: String, typeKey: String) { + let parts = pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + // MARK: - Init + + /// CSV Initialer. + convenience init?(datestampKey: String, typeKey: String, count: Int = 0, streak: Int = 0) { + guard let dataCountType = DataCountType(itemTypeKey: typeKey), + Date(datestampKey: datestampKey) != nil else { + return nil + } + + self.init() + self.pid = "\(datestampKey).\(typeKey)" + + self.count = count + if self.count > dataCountType.maxServings { + self.count = dataCountType.maxServings + LogService.shared.error( + "DataCountRecord init datestampKey:\(datestampKey) typekey:\(typeKey) count:\(count) exceeded max servings \(dataCountType.maxServings)" + ) + + } + self.streak = streak + } + + convenience init(date: Date, countType: DataCountType, count: Int = 0, streak: Int = 0) { + self.init() + self.pid = "\(date.datestampKey).\(countType.typeKey)" + + self.count = count + if self.count > countType.maxServings { + self.count = countType.maxServings + LogService.shared.error( + "DataCountRecord init date:\(date.datestampKey) countType:\(countType.typeKey) count:\(count) exceeds max servings \(countType.maxServings)" + ) + } + self.streak = streak + } + + // MARK: - Meta Information + + override static func primaryKey() -> String? { + return "pid" + } + + // MARK: - Data Presentation Methods + + func title() -> String { + guard let tmp = DataCountType(rawValue: self.pidKeys.typeKey) else { + return "Undefined Title (Error)" + } + return tmp.headingDisplay + } + + // MARK: - Data Management Methods + + func setCount(text: String) { + if let value = Int(text) { + setCount(value) + } else { + LogService.shared.error( + "DataCountRecord setCount() not convertable to Int \(text)" + ) + } + } + + func setCount(_ count: Int) { + self.count = count + if let countType = pidParts?.countType { + if self.count > countType.maxServings { + self.count = countType.maxServings + LogService.shared.error( + "DataCountRecord setCount \(pid) \(count) exceeds max servings" + ) + } + } else { + LogService.shared.error( + "DataCountRecord setCount \(pid) \(count) could not range check servings" + ) + } + } + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataCountType.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataCountType.swift new file mode 100644 index 00000000..337e2631 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataCountType.swift @@ -0,0 +1,109 @@ +// +// DataCountType.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation + +enum DataCountType: String, CaseIterable { + + //case date + //case streak + // Daily Dozen Servings NIDs (Native IDs) + case dozeBeans + case dozeBerries + case dozeFruitsOther + case dozeVegetablesCruciferous + case dozeGreens + case dozeVegetablesOther + case dozeFlaxseeds + case dozeNuts + case dozeSpices + case dozeWholeGrains + case dozeBeverages + case dozeExercise + // Daily Dozen Other NIDs + case otherVitaminB12 + case otherVitaminD + case otherOmega3 + // 21 Tweaks NIDs + case tweakMealWater + case tweakMealNegCal + case tweakMealVinegar + case tweakMealUndistracted + case tweakMeal20Minutes + case tweakDailyBlackCumin + case tweakDailyGarlic + case tweakDailyGinger + case tweakDailyNutriYeast + case tweakDailyCumin + case tweakDailyGreenTea + case tweakDailyHydrate + case tweakDailyDeflourDiet + case tweakDailyFrontLoad + case tweakDailyTimeRestrict + case tweakExerciseTiming + case tweakWeightTwice + case tweakCompleteIntentions + case tweakNightlyFast + case tweakNightlySleep + case tweakNightlyTrendelenbrug + + init?(itemTypeKey: String) { + self = DataCountType(rawValue: String(itemTypeKey))! + } + + var typeKey: String { + return self.rawValue + } + + var headingDisplay: String { + return DataCountAttributes.shared.dict[self]!.headingDisplay + } + + var maxServings: Int { + return DataCountAttributes.shared.dict[self]!.maxServings + } + + init?(csvHeading: String) { + let csvHeadingIn = csvHeading + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + for key in DataCountAttributes.shared.dict.keys { + let csvHeadingAttribute = DataCountAttributes.shared.dict[key]! + .headingCSV + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + if csvHeadingIn == csvHeadingAttribute { + self = key + } + } + return nil + } + + var headingCSV: String { + return DataCountAttributes.shared.dict[self]!.headingCSV + } + + var imageName: String { + return "ic_\(self.typeKey)" + } + + /// does not include "other" + var isDailyDozen: Bool { + return self.typeKey.prefix(4) == "doze" + } + + var isOther: Bool { + return self.typeKey.prefix(5) == "other" + } + + var isTweak: Bool { + return self.typeKey.prefix(5) == "tweak" + } + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecord.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecord.swift new file mode 100644 index 00000000..8e5b444a --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecord.swift @@ -0,0 +1,115 @@ +// +// DataWeightRecord.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation +import RealmSwift + +class DataWeightRecord: Object { + + /// yyyyMMdd.typeKey e.g. 20190101.am + @objc dynamic var pid: String = "" + /// kilograms + @objc dynamic var kg: Double = 0.0 + /// time of day 24-hour "HH:mm" format + @objc dynamic var time: String = "" + + var kgStr: String { + return String(format: "%.1f", kg) + } + + var lbs: Double { + return kg * 2.204623 + } + + var lbsStr: String { + let poundValue = kg * 2.204623 + return String(format: "%.1f", poundValue) + } + + /// time of day "hh:mm a" format + var timeAmPm: String { + let fromDateFormatter = DateFormatter() + fromDateFormatter.dateFormat = "HH:mm" + if let fromDate = fromDateFormatter.date(from: time) { + let toDateFormatter = DateFormatter() + toDateFormatter.dateFormat = "hh:mm a" + let fromTime: String = toDateFormatter.string(from: fromDate) + return fromTime + } + return "" + } + + var datetime: Date? { + let datestring = "\(pid.prefix(8)) \(time)" + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd HH:mm" + return dateFormatter.date(from: datestring) + } + + var pidKeys: (datestampKey: String, typeKey: String) { + let parts = self.pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + var pidParts: (datestamp: Date, weightType: DataWeightType)? { + guard let date = Date.init(datestampKey: pidKeys.datestampKey), + let weightType = DataWeightType(typeKey: pidKeys.typeKey) else { + LogService.shared.error( + "DataWeightRecord pidParts has invalid datestamp or weightType" + ) + return nil + } + return (datestamp: date, weightType: weightType) + } + + // MARK: Class Methods + + static func pid(date: Date, weightType: DataWeightType) -> String { + return "\(date.datestampKey).\(weightType.typeKey)" + } + + static func pidKeys(pid: String) -> (datestampKey: String, typeKey: String) { + let parts = pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + // MARK: - Init + + /// CSV Initialer. + convenience init?(datestampKey: String, typeKey: String, kilograms: String, timeHHmm: String) { + guard DataWeightType(typeKey: typeKey) != nil, + Date(datestampKey: datestampKey) != nil, + let kg = Double(kilograms), + timeHHmm.contains(":"), + Int(timeHHmm.dropLast(3)) != nil, + Int(timeHHmm.dropFirst(3)) != nil + else { + return nil + } + + self.init() + self.pid = "\(datestampKey).\(typeKey)" + self.kg = kg + self.time = timeHHmm + } + + convenience init(date: Date, weightType: DataWeightType, kg: Double) { + self.init() + self.pid = "\(date.datestampKey).\(weightType.typeKey)" + self.kg = kg + self.time = date.datestampHHmm + } + + // MARK: - Realm Meta Information + + override static func primaryKey() -> String? { + return "pid" + } + + // MARK: - Data Presentation Methods + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecordCopy.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecordCopy.swift new file mode 100644 index 00000000..4b9f8365 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightRecordCopy.swift @@ -0,0 +1,115 @@ +// +// DataWeightCopy.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +/// DataWeightCopy: DataWeightRecord without Realm Object inheritance +class DataWeightCopy { + + /// yyyyMMdd.typeKey e.g. 20190101.amKey + var pid: String = "" + /// kilograms + var kg: Double = 0.0 + /// time of day 24-hour "HH:mm" format + var time: String = "" + + var kgStr: String { + return String(format: "%.1f", kg) + } + + var lbs: Double { + return kg * 2.204623 + } + + var lbsStr: String { + let poundValue = kg * 2.204623 + return String(format: "%.1f", poundValue) + } + + /// time of day "hh:mm a" format + var timeAmPm: String { + let fromDateFormatter = DateFormatter() + fromDateFormatter.dateFormat = "HH:mm" + if let fromDate = fromDateFormatter.date(from: time) { + let toDateFormatter = DateFormatter() + toDateFormatter.dateFormat = "hh:mm a" + let fromTime: String = toDateFormatter.string(from: fromDate) + return fromTime + } + return "" + } + + var datetime: Date? { + let datestring = "\(pid.prefix(8)) \(time)" + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd HH:mm" + return dateFormatter.date(from: datestring) + } + + var pidKeys: (datestampKey: String, typeKey: String) { + let parts = self.pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + var pidParts: (datestamp: Date, weightType: DataWeightType)? { + guard let date = Date.init(datestampKey: pidKeys.datestampKey), + let weightType = DataWeightType(typeKey: pidKeys.typeKey) else { + LogService.shared.error( + "DataWeightRecord pidParts has invalid datestamp or weightType" + ) + return nil + } + return (datestamp: date, weightType: weightType) + } + + // MARK: Class Methods + + static func pid(date: Date, weightType: DataWeightType) -> String { + return "\(date.datestampKey).\(weightType.typeKey)" + } + + static func pidKeys(pid: String) -> (datestampKey: String, typeKey: String) { + let parts = pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + // MARK: - Init + + /// CSV Initialer. + convenience init?(datestampKey: String, typeKey: String, kilograms: String, timeHHmm: String) { + guard DataWeightType(typeKey: typeKey) != nil, + Date(datestampKey: datestampKey) != nil, + let kg = Double(kilograms), + timeHHmm.contains(":"), + Int(timeHHmm.dropLast(3)) != nil, + Int(timeHHmm.dropFirst(3)) != nil + else { + return nil + } + + self.init() + self.pid = "\(datestampKey).\(typeKey)" + self.kg = kg + self.time = time + } + + convenience init(date: Date, weightType: DataWeightType, kg: Double) { + self.init() + self.pid = "\(date.datestampKey).\(weightType.typeKey)" + self.kg = kg + self.time = date.datestampHHmm + } + + // MARK: - Meta Information + + override static func primaryKey() -> String? { + return "pid" + } + + // MARK: - Data Presentation Methods + +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataWeightType.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightType.swift new file mode 100644 index 00000000..f867dfc5 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightType.swift @@ -0,0 +1,22 @@ +// +// DataWeightType.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation + +enum DataWeightType: String { + + case am + case pm + + init?(typeKey: String) { + self = DataWeightType(rawValue: String(typeKey))! + } + + var typeKey: String { + return self.rawValue + } +} diff --git a/DailyDozen/DailyDozen/Database/DataRecords/DataWeightValues.swift b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightValues.swift new file mode 100644 index 00000000..47d3de3c --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DataRecords/DataWeightValues.swift @@ -0,0 +1,111 @@ +// +// DataWeightValues.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +/// DataWeightValues: DataWeightRecord without Realm `Object` inheritance +struct DataWeightValues { + + /// yyyyMMdd.typeKey e.g. 20190101.am + var pid: String = "" + /// kilograms + var kg: Double = 0.0 + /// time of day 24-hour "HH:mm" format + var time: String = "" + + var kgStr: String { + return String(format: "%.1f", kg) + } + + var lbs: Double { + return kg * 2.204623 + } + + var lbsStr: String { + let poundValue = kg * 2.204623 + return String(format: "%.1f", poundValue) + } + + /// time of day "hh:mm a" format + var timeAmPm: String { + let fromDateFormatter = DateFormatter() + fromDateFormatter.dateFormat = "HH:mm" + if let fromDate = fromDateFormatter.date(from: time) { + let toDateFormatter = DateFormatter() + toDateFormatter.dateFormat = "hh:mm a" + let fromTime: String = toDateFormatter.string(from: fromDate) + return fromTime + } + return "" + } + + var datetime: Date? { + let datestring = "\(pid.prefix(8)) \(time)" + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd HH:mm" + return dateFormatter.date(from: datestring) + } + + var pidKeys: (datestampKey: String, typeKey: String) { + let parts = self.pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + var pidParts: (datestamp: Date, weightType: DataWeightType)? { + guard let date = Date(datestampKey: pidKeys.datestampKey), + let weightType = DataWeightType(typeKey: pidKeys.typeKey) else { + LogService.shared.error( + "DataWeightRecord pidParts has invalid datestamp or weightType" + ) + return nil + } + return (datestamp: date, weightType: weightType) + } + + // MARK: Class Methods + + static func pid(date: Date, weightType: DataWeightType) -> String { + return "\(date.datestampKey).\(weightType.typeKey)" + } + + static func pidKeys(pid: String) -> (datestampKey: String, typeKey: String) { + let parts = pid.components(separatedBy: ".") + return (datestampKey: parts[0], typeKey: parts[1]) + } + + // MARK: - Init + + /// CSV Initialer. + init?(datestampKey: String, typeKey: String, kilograms: String, timeHHmm: String) { + guard DataWeightType(typeKey: typeKey) != nil, + Date(datestampKey: datestampKey) != nil, + let kg = Double(kilograms), + timeHHmm.contains(":"), + Int(timeHHmm.dropLast(3)) != nil, + Int(timeHHmm.dropFirst(3)) != nil + else { + return nil + } + + self.pid = "\(datestampKey).\(typeKey)" + self.kg = kg + self.time = timeHHmm + } + + init(date: Date, weightType: DataWeightType, kg: Double) { + self.pid = "\(date.datestampKey).\(weightType.typeKey)" + self.kg = kg + self.time = date.datestampHHmm + } + + init(record: DataWeightRecord) { + self.pid = record.pid + self.kg = record.kg + self.time = record.time + } + +} diff --git a/DailyDozen/DailyDozen/Database/DatabaseBuiltInTest.swift b/DailyDozen/DailyDozen/Database/DatabaseBuiltInTest.swift new file mode 100644 index 00000000..c7f94f37 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/DatabaseBuiltInTest.swift @@ -0,0 +1,203 @@ +// +// DatabaseBuiltInTest.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import HealthKit + +/// Utilities to support Built-In-Test (BIT). +public struct DatabaseBuiltInTest { + + static public var shared = DatabaseBuiltInTest() + + public let hkHealthStore = HKHealthStore() + + public func runSuite() { // :@@@: + LogService.shared.debug(">>> :DEBUG:WAYPOINT: DatabaseBuiltInTest runSuite()") + LogService.shared.debug(">>> HKHealthStore.isHealthDataAvailable() \(HKHealthStore.isHealthDataAvailable())") + + //HealthManager.shared.exportHKWeight(name: "BIT00") + //DatabaseBuiltInTest.shared.doGenerateDBHistoryBIT(numberOfDays: 3, defaultDB: false) + //DatabaseBuiltInTest.shared.doGenerateDBHistoryBIT(numberOfDays: 365*3, defaultDB: false) // 1095 days, 2190 weight entries + + //doGenerateHKSampleDataBIT() + // :!!!:NYI: BIT runSuite() + } + + /// Clear Documents/Legacy, Documents/V01 and Library/Database/V02 Realm data. + func doClearAllDataInMigrationChainBIT() { + LogService.shared.debug( + "••BEGIN•• UtilityTableViewController doClearAllDataInMigrationChainBIT()" + ) + + let urlLegacy = URL.inDocuments(filename: RealmProviderLegacy.realmFilename) + let realmMngrLegacy = RealmManagerLegacy(fileUrl: urlLegacy) + let realmDbOld = realmMngrLegacy.realmDb + realmDbOld.deleteDBAllLegacy() + + let urlV01 = URL.inDocuments(filename: RealmProvider.realmFilename) + let realmMngrV01 = RealmManager(fileURL: urlV01) + let realmDbV01 = realmMngrV01.realmDb + realmDbV01.deleteDBAll() + + let realmMngrV02 = RealmManager() + let realmDbV02 = realmMngrV02.realmDb + realmDbV02.deleteDBAll() + + LogService.shared.debug( + "••EXIT•• UtilityTableViewController doClearAllDataInMigrationChainBIT()" + ) + } + + func doGenerateDBLegacyDataBIT() { + let urlLegacy = URL.inDocuments(filename: RealmProviderLegacy.realmFilename) + let realmMngrLegacy = RealmManagerLegacy(fileUrl: urlLegacy) + let realmDbLegacy = realmMngrLegacy.realmDb + // World Pasta Day: Oct 25, 1995 + let date1995Pasta = Date(datestampKey: "19951025")! + // Add known content to legacy + let dozeCheck = realmDbLegacy.getDozeLegacy(for: date1995Pasta) + realmDbLegacy.saveStatesLegacy([true, false, true], id: dozeCheck.items[0].id) // Beans + realmDbLegacy.saveStatesLegacy([false, true, false], id: dozeCheck.items[2].id) // Other Fruit + } + + func doGenerateDBV01DataBIT() { + fatalError(":NYI: doGenerateDBV01DataBIT") + } + + func doGenerateDBV02DataBIT() { + fatalError(":NYI: doGenerateDBV02DataBIT") + } + + /// Create initial state for + public func doGenerateHKSampleDataBIT() { + let baseYMD = "20200521" + let date1 = Date(datastampLong: "\(baseYMD)_073000.000")! // yyyyMMdd_HHmmss.SSS + let date2 = Date(datastampLong: "\(baseYMD)_073100.000")! + let date3 = Date(datastampLong: "\(baseYMD)_073100.000")! + + saveHKSampleBIT(date: date1, weight: 22.0, isImperial: false) + saveHKSampleBIT(date: date2, weight: 24.2, isImperial: false) + saveHKSampleBIT(date: date3, weight: 26.4, isImperial: false) + } + + /// Generate Realm data + /// + /// * ~1 month -> 30 days + /// * ~10 months -> 300 days + /// * ~2.7 years or ~33 months -> 1000 days (2000 weight entries) + /// * 3 years (1095 days, 2190 weight entries) -> 365*3 + func doGenerateDBHistoryBIT(numberOfDays: Int, defaultDB: Bool) { + LogService.shared.debug( + "••BEGIN•• doGenerateDBHistoryBIT(\(numberOfDays))" + ) + let urlLegacy = URL.inDocuments(filename: "test_\(numberOfDays)_days.realm") + let realmMngrCheck = RealmManager(fileURL: urlLegacy) + let realmProvider = realmMngrCheck.realmDb + + let calendar = Calendar.current + let today = Date() // today + + let dateComponents = DateComponents( + calendar: calendar, + year: today.year, month: today.month, day: today.day, + hour: 0, minute: 0, second: 0 + ) + var date = calendar.date(from: dateComponents)! + + let weightBase = 65.0 // kg + LogService.shared.debug(" baseWeigh \(weightBase) kg, \(weightBase * 2.2) lbs") + let weightAmplitude = 2.0 // kg + let weightCycleStep = (2 * Double.pi) / (30 * 2) + for i in 0.. Int { + let fm = FileManager.default + // Check for version V02 + if fm.fileExists(atPath: URL.inDatabase(filename: RealmProvider.realmFilename).path) { + return 2 + } + + // Check for version V01 + if fm.fileExists(atPath: URL.inDocuments(filename: RealmProvider.realmFilename).path) { + return 1 + } + + // Check for version Legacy (V00) + if fm.fileExists(atPath: URL.inDocuments(filename: RealmProviderLegacy.realmFilename).path) { + return 0 + } + + // Create Libary/Database directory if not present + let databaseUrl = URL.inLibrary().appendingPathComponent("Database", isDirectory: true) + do { + try fm.createDirectory(at: databaseUrl, withIntermediateDirectories: true) + } catch { + LogService.shared.error(" \(error)") + } + + // If no realm databases are present, then use the most recent version. + return 2 + } + + /// Converts v0 legacy database to v1 database + /// + /// * Array of discrete boolean becomes an integer count. + /// * Does not sync with HealthKit + public func doMigration_A_LegacyToV01() { + // export from "main.realm" + let urlLegacy = URL.inDocuments(filename: RealmProviderLegacy.realmFilename) + let realmMngrLegacy = RealmManagerLegacy(fileUrl: urlLegacy) + let legacyExportFilename = realmMngrLegacy.csvExport() + + // import to NutritionFacts.realm + let urlV01 = URL.inDocuments(filename: RealmProvider.realmFilename) + let realmMngrV01 = RealmManager(fileURL: urlV01) + realmMngrV01.csvImport(filename: legacyExportFilename) + } + + /// PreHKSyncRealm + public func doMigration_B_V02toV03() { + let fm = FileManager.default + // Create Libary/Database directory if not present + let databaseUrl = URL.inLibrary().appendingPathComponent("Database", isDirectory: true) + do { + try fm.createDirectory(at: databaseUrl, withIntermediateDirectories: true) + } catch { + LogService.shared.error(" \(error)") + } + + let fromUrl01 = URL.inDocuments(filename: "NutritionFacts.realm") + let fromUrl02 = URL.inDocuments(filename: "NutritionFacts.realm.lock") + let fromUrl03 = URL.inDocuments(filename: "NutritionFacts.realm.management") + let toUrl01 = URL.inDatabase(filename: "NutritionFacts.realm") + let toUrl02 = URL.inDatabase(filename: "NutritionFacts.realm.lock") + let toUrl03 = URL.inDatabase(filename: "NutritionFacts.realm.management") + + guard + fm.fileExists(atPath: fromUrl01.path), + fm.fileExists(atPath: fromUrl02.path), + fm.fileExists(atPath: fromUrl03.path), + fm.fileExists(atPath: toUrl01.path) == false, + fm.fileExists(atPath: toUrl02.path) == false, + fm.fileExists(atPath: toUrl03.path) == false + else { + LogService.shared.error("doMigration_B_V02toV03 file existance criteria not met.") + return + } + + do { + try fm.copyItem(at: fromUrl01, to: toUrl01) + try fm.copyItem(at: fromUrl02, to: toUrl02) + // Directory copy + try fm.copyItem(at: fromUrl03, to: toUrl03) + } catch { + LogService.shared.error("doMigration_B_V02toV03 '\(error)'") + } + + #if DEBUG + let realmManager = RealmManager() + _ = realmManager.csvExport(marker: "migration_pre_data") + _ = realmManager.csvExportWeight(marker: "migration_pre_weight") + HealthSynchronizer.shared.syncWeightExport(marker: "migration_pre_hk_sync") + #endif + + // clear and re-sync "NutritionFacts.realm" with HealthKit + HealthSynchronizer.shared.resetSyncAll() + } + + /// Move Documents/* to Library/Backup/ + public func doMigration_C_Backup() { + LogService.shared.debug( + "••BEGIN•• UtilityTableViewController doMigration_C_Backup()" + ) + let fm = FileManager.default + let documentsUrl = URL.inDocuments() + + var backupUrl = URL.inBackup() + // Add timestamp subdirectory + backupUrl.appendPathComponent(Date().datestampyyyyMMddHHmmss, isDirectory: true) + + do { + try fm.createDirectory(at: backupUrl, withIntermediateDirectories: true) + + let keys: [URLResourceKey] = [.isDirectoryKey] + let options: FileManager.DirectoryEnumerationOptions = [.skipsHiddenFiles] + let contentUrls = try fm.contentsOfDirectory( + at: documentsUrl, + includingPropertiesForKeys: keys, + options: options) + for fromUrl in contentUrls { + // skip: leave log* files in Documents/ + if fromUrl.lastPathComponent.hasPrefix("log") || + fromUrl.lastPathComponent.hasSuffix("csv") { + continue + } + + let toUrl = backupUrl.appendingPathComponent(fromUrl.lastPathComponent) + LogService.shared.debug("→→→ from: \(fromUrl)") + LogService.shared.debug("→→→ to: \(toUrl)") + // Move file or directory to new location synchronously. + try fm.moveItem(at: fromUrl, to: toUrl) + } + + } catch { + LogService.shared.error( + "DatabaseMaintainer doMigration_C_Backup \(error.localizedDescription)" + ) + } + + LogService.shared.debug( + "••EXIT•• UtilityTableViewController doMigration_C_Backup()" + ) + } + +} diff --git a/DailyDozen/DailyDozen/Servings/Models/Doze.swift b/DailyDozen/DailyDozen/Database/RealmLegacy/Doze.swift similarity index 77% rename from DailyDozen/DailyDozen/Servings/Models/Doze.swift rename to DailyDozen/DailyDozen/Database/RealmLegacy/Doze.swift index b14fe0fb..a3115e11 100644 --- a/DailyDozen/DailyDozen/Servings/Models/Doze.swift +++ b/DailyDozen/DailyDozen/Database/RealmLegacy/Doze.swift @@ -10,18 +10,25 @@ import Foundation import RealmSwift class Doze: Object { - - @objc dynamic var date = Date() + + // MARK: - RealmDB Persisted Properties + @objc dynamic var id = UUID().uuidString - + @objc dynamic var date = Date() let items = List() + + // MARK: - Non-Persisted Properties + // MARK: - Init + convenience init(date: Date, items: [Item]) { self.init() self.date = date self.items.append(objectsIn: items) } - + + // MARK: - RealmDB Meta + override static func primaryKey() -> String? { return "id" } diff --git a/DailyDozen/DailyDozen/Servings/Models/Item.swift b/DailyDozen/DailyDozen/Database/RealmLegacy/Item.swift similarity index 77% rename from DailyDozen/DailyDozen/Servings/Models/Item.swift rename to DailyDozen/DailyDozen/Database/RealmLegacy/Item.swift index 79acb88d..5618fa29 100644 --- a/DailyDozen/DailyDozen/Servings/Models/Item.swift +++ b/DailyDozen/DailyDozen/Database/RealmLegacy/Item.swift @@ -1,5 +1,5 @@ // -// Entity.swift +// Item.swift // DailyDozen // // Created by Konstantin Khokhlov on 23.10.17. @@ -10,19 +10,27 @@ import Foundation import RealmSwift class Item: Object { - - @objc dynamic var name = "" + + // MARK: - RealmDB Persisted Properties + @objc dynamic var id = UUID().uuidString + @objc dynamic var name = "" @objc dynamic var streak = 0 let states = List() + + // MARK: - Non-Persisted Properties + // MARK: - Init + convenience init(name: String, states: [Bool], streak: Int = 0) { self.init() self.name = name self.streak = streak self.states.append(objectsIn: states) } - + + // MARK: - RealmDB Meta + override static func primaryKey() -> String? { return "id" } diff --git a/DailyDozen/DailyDozen/Database/RealmLegacy/RealmManagerLegacy.swift b/DailyDozen/DailyDozen/Database/RealmLegacy/RealmManagerLegacy.swift new file mode 100644 index 00000000..761c7e85 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/RealmLegacy/RealmManagerLegacy.swift @@ -0,0 +1,73 @@ +// +// RealmManagerLegacy.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +class RealmManagerLegacy { + + let realmDb: RealmProviderLegacy + + init(fileUrl: URL) { + realmDb = RealmProviderLegacy(fileURL: fileUrl) + } + + func csvExport() -> String { + let filename = "\(Date.datestampNow())_export_legacy.csv" + csvExport(filename: filename) + return filename + } + + func csvExport(filename: String) { + let outUrl = URL.inDocuments().appendingPathComponent(filename) + var content = RealmManagerLegacy.csvHeader + + let allDozes = realmDb.getDozesLegacy() + for doze in allDozes { + content.append(csvExportLine(doze: doze)) + } + + do { + try content.write(to: outUrl, atomically: true, encoding: .utf8) + } catch { + LogService.shared.error( + "FAIL RealmManagerLegacy csvExport \(error) path:'\(outUrl.path)'" + ) + } + } + + static let csvHeader = "Date," + .appending("Beans,") + .appending("Berries,") + .appending("Other Fruits,") + .appending("Cruciferous Vegetables,") + .appending("Greens,") + .appending("Other Vegetables,") + .appending("Flaxseeds,") + .appending("Nuts and Seeds,") + .appending("Herbs and Spices,") + .appending("Whole Grains,") + .appending("Beverages,") + .appending("Exercise,") + .appending("Vitamin B12,") + .appending("Vitamin D\n") + + private func csvExportLine(doze: Doze) -> String { + var str = "\(doze.date.datestampKey)" + + for item in doze.items { + var stateCountPerItem = 0 + for state in item.states where state { + stateCountPerItem += 1 + } + str.append(",\(stateCountPerItem)") + } + str.append("\n") + + return str + } + +} diff --git a/DailyDozen/DailyDozen/Database/RealmLegacy/RealmProviderLegacy.swift b/DailyDozen/DailyDozen/Database/RealmLegacy/RealmProviderLegacy.swift new file mode 100644 index 00000000..114a92d1 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/RealmLegacy/RealmProviderLegacy.swift @@ -0,0 +1,130 @@ +// +// RealmProviderLegacy.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import RealmSwift + +class RealmProviderLegacy { + + public static let realmFilename = "main.realm" + + private let realm: Realm + private var unsavedDoze: Doze? + +// convenience init() { +// let fileURL: URL = URL.inDocuments(filename: RealmProviderLegacy.realmFilename) +// self.init(fileURL: fileURL) +// } + + init(fileURL: URL) { + let config = Realm.Configuration( + fileURL: fileURL, // local Realm file url + objectTypes: [Doze.self, Item.self]) + guard let realm = try? Realm(configuration: config) + else { + fatalError("FAIL: could not instantiate RealmProviderLegacy") + } + self.realm = realm + } + + /// Returns a Doze object for the current date. + /// + /// - Returns: The doze. + func getDozeLegacy(for date: Date) -> Doze { + let dozeResults: Results = realm.objects(Doze.self) + let dozeFiltered = dozeResults.filter { $0.date.isInCurrentDayWith(date) } + let doze = dozeFiltered.first ?? initialDoze(date: date) + unsavedDoze = doze + return doze + } + + private func initialDoze(date: Date) -> Doze { + let items = [ + Item(name: "Beans", states: [false, false, false]), + Item(name: "Berries", states: [false]), + Item(name: "Other Fruits", states: [false, false, false]), + Item(name: "Cruciferous Vegetables", states: [false]), + Item(name: "Greens", states: [false, false]), + Item(name: "Other Vegetables", states: [false, false]), + Item(name: "Flaxseeds", states: [false]), + Item(name: "Nuts", states: [false]), + Item(name: "Spices", states: [false]), + Item(name: "Whole Grains", states: [false, false, false]), + Item(name: "Beverages", states: [false, false, false, false, false]), + Item(name: "Exercise", states: [false]), + Item(name: "Vitamin B12", states: [false]), + Item(name: "Vitamin D", states: [false]) + ] + return Doze(date: date, items: items) + } + + func getDozesLegacy() -> Results { + return realm.objects(Doze.self) + } + + /// Updates an Item object with an ID for new states. + /// + /// - Parameters: + /// - states: The new state. + /// - id: The ID. + func saveStatesLegacy(_ states: [Bool], id: String) { + saveDozeLegacy() + do { + try realm.write { + realm.create(Item.self, value: ["id": id, "states": states], update: Realm.UpdatePolicy.all) + } + } catch { + LogService.shared.error( + "RealmProviderLegacy saveStatesLegacy \(error.localizedDescription)" + ) + } + } + + func updateStreakLegacy(_ streak: Int, id: String) { + saveDozeLegacy() + do { + try realm.write { + // BUG: id+streak(0) only causing memory leak when called but not later used. + realm.create(Item.self, value: ["id": id, "streak": streak], update: Realm.UpdatePolicy.all) + } + } catch { + LogService.shared.error( + "RealmProviderLegacy updateStreakLegacy \(error.localizedDescription)" + ) + } + } + + /// Saves the unsaved doze. + private func saveDozeLegacy() { + if let doze = unsavedDoze { + do { + try realm.write { + realm.add(doze, update: Realm.UpdatePolicy.all) + unsavedDoze = nil + } + } catch { + LogService.shared.error( + "FAILED RealmProviderLegacy saveDozeLegacy() did not save doze: \(doze), description: \(error.localizedDescription)" + ) + } + + } + } + + func deleteDBAllLegacy() { + do { + try realm.write { + realm.deleteAll() + } + } catch { + LogService.shared.error( + "FAIL RealmProviderLegacy deleteDBAllLegacy() description:\(error.localizedDescription)" + ) + } + } + +} diff --git a/DailyDozen/DailyDozen/Database/RealmManager.swift b/DailyDozen/DailyDozen/Database/RealmManager.swift new file mode 100644 index 00000000..fab10458 --- /dev/null +++ b/DailyDozen/DailyDozen/Database/RealmManager.swift @@ -0,0 +1,268 @@ +// +// RealmManager.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// + +import Foundation + +class RealmManager { + + let realmDb: RealmProvider + + /// + init() { + realmDb = RealmProvider() + } + + init(fileURL: URL) { + realmDb = RealmProvider(fileURL: fileURL) + } + + func csvExport(marker: String) -> String { + let filename = "\(Date.datestampNow())_\(marker).csv" + csvExport(filename: filename) + return filename + } + + func csvExport(filename: String) { + let outUrl = URL.inDocuments().appendingPathComponent(filename) + var content = RealmManager.csvHeader + + let allTrackers = realmDb.getDailyTrackers() + for tracker in allTrackers { + content.append(csvExportLine(tracker: tracker)) + } + + do { + try content.write(to: outUrl, atomically: true, encoding: .utf8) + } catch { + LogService.shared.error( + "FAIL RealmManager csvExport \(error) path:'\(outUrl.path)'" + ) + } + } + + private func csvExportLine(tracker: DailyTracker) -> String { + var str = "" + str.append("\(tracker.date.datestampKey)") + + for dataCountType in DataCountType.allCases { + if let dataCountRecord = tracker.itemsDict[dataCountType] { + str.append(",\(dataCountRecord.count)") + } else { + str.append(",0") + } + } + // Weight + str.append(",\(tracker.weightAM.time)") + str.append(",\(tracker.weightAM.kg)") + str.append(",\(tracker.weightPM.time)") + str.append(",\(tracker.weightPM.kg)") + str.append("\n") + + return str + } + + func csvImport(filename: String) { + let inUrl = URL.inDocuments().appendingPathComponent(filename) + guard let contents = try? String(contentsOf: inUrl) else { + LogService.shared.error( + "FAIL RealmManager csvImport file not found '\(filename)'" + ) + return + } + let lines = contents.components(separatedBy: .newlines) + guard lines.count > 1 else { + LogService.shared.error( + "FAIL RealmManager csvImport CSV has less that 2 lines" + ) + return + } + + if isValidCsvHeader(lines[0]) { + for i in 1.. Bool { + let currentHeaderFiltered = header + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + .appending("\n") + + let legacyHeader = RealmManager.csvHeader + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + + return currentHeaderFiltered == legacyHeader + } + + private func isValidCsvHeaderLegacy(_ header: String) -> Bool { + let currentHeaderFiltered = header + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + .appending("\n") + + let legacyHeader = RealmManagerLegacy.csvHeader + .replacingOccurrences(of: " ", with: "") + .replacingOccurrences(of: "-", with: "") + .lowercased() + + return currentHeaderFiltered == legacyHeader + } + + private func csvProcess(line: String) -> DailyTracker? { + let columns = line + .replacingOccurrences(of: " ", with: "") + .components(separatedBy: ",") + guard columns.count == 1 + DataCountType.allCases.count + 4 else { + return nil + } + + let datastampKey = columns[0] + guard let date = Date(datestampKey: datastampKey) else { + return nil + } + var tracker = DailyTracker(date: date) + + var index = 1 + for dataCountType in DataCountType.allCases { + if let value = Int(columns[index]) { + let dataCountRecord = DataCountRecord( + date: date, + countType: dataCountType, + count: value + ) + tracker.itemsDict[dataCountType] = dataCountRecord + } else { + LogService.shared.error( + "FAIL RealmManager csvProcess \(index) in \(line)" + ) + } + index += 1 + } + + let weightIndexOffset = 1 + DataCountType.allCases.count + let weightAM = DataWeightRecord( + datestampKey: datastampKey, + typeKey: DataWeightType.am.typeKey, + kilograms: columns[weightIndexOffset], + timeHHmm: columns[weightIndexOffset+1] + ) + if let weight = weightAM { + tracker.weightAM = weight + } + let weightPM = DataWeightRecord( + datestampKey: datastampKey, + typeKey: DataWeightType.pm.typeKey, + kilograms: columns[weightIndexOffset+2], + timeHHmm: columns[weightIndexOffset+3] + ) + if let weight = weightPM { + tracker.weightPM = weight + } + + return tracker + } + + private func csvProcessLegacy(line: String) -> DailyTracker? { + let columns = line + .replacingOccurrences(of: " ", with: "") + .components(separatedBy: ",") + // Expected count: 1x date plus 14x legacy fields + guard columns.count == 1 + 14 else { + if columns.count > 1 { // line with at least one `,` + LogService.shared.warning( + "WARN RealmManager csvProcess incorrect column count (\(columns.count)) '\(line)'" + ) + } + return nil + } + + let datastampKey = columns[0] + guard let date = Date(datestampKey: datastampKey) else { + return nil + } + let tracker = DailyTracker(date: date) + + tracker.setCount(typeKey: .dozeBeans, countText: columns[1]) + tracker.setCount(typeKey: .dozeBerries, countText: columns[2]) + tracker.setCount(typeKey: .dozeFruitsOther, countText: columns[3]) + tracker.setCount(typeKey: .dozeVegetablesCruciferous, countText: columns[4]) + tracker.setCount(typeKey: .dozeGreens, countText: columns[5]) + tracker.setCount(typeKey: .dozeVegetablesOther, countText: columns[6]) + tracker.setCount(typeKey: .dozeFlaxseeds, countText: columns[7]) + tracker.setCount(typeKey: .dozeNuts, countText: columns[8]) + tracker.setCount(typeKey: .dozeSpices, countText: columns[9]) + tracker.setCount(typeKey: .dozeWholeGrains, countText: columns[10]) + tracker.setCount(typeKey: .dozeBeverages, countText: columns[11]) + tracker.setCount(typeKey: .dozeExercise, countText: columns[12]) + tracker.setCount(typeKey: .otherVitaminB12, countText: columns[13]) + tracker.setCount(typeKey: .otherVitaminD, countText: columns[14]) + + return tracker + } + + private static var csvHeader: String { + var str = "Date" + for dataCountType in DataCountType.allCases { + str.append(",\(dataCountType.headingCSV)") + } + // Weight + str.append(",Weight AM Time") + str.append(",Weight AM Value") + str.append(",Weight PM Time") + str.append(",Weight PM Value") + + str.append("\n") + return str + } + + // MARK: - Weight Only + + func csvExportWeight(marker: String) -> String { + let filename = "\(Date.datestampNow())_\(marker).csv" + csvExportWeight(filename: filename) + return filename + } + + func csvExportWeight(filename: String) { + let outUrl = URL.inDocuments().appendingPathComponent(filename) + var content = "DB_PID,time,kg,lbs\n" + + let allWeights = realmDb.getDailyWeightsArray() + for record in allWeights { + content.append("\(record.pid),\(record.time),\(record.kgStr),\(record.lbsStr)\n") + } + + do { + try content.write(to: outUrl, atomically: true, encoding: .utf8) + } catch { + LogService.shared.error( + "FAIL RealmManager csvExport \(error) path:'\(outUrl.path)'" + ) + } + } + +} diff --git a/DailyDozen/DailyDozen/Database/RealmProvider.swift b/DailyDozen/DailyDozen/Database/RealmProvider.swift new file mode 100644 index 00000000..cfd4427b --- /dev/null +++ b/DailyDozen/DailyDozen/Database/RealmProvider.swift @@ -0,0 +1,446 @@ +// +// RealmProvider.swift +// DatabaseMigration +// +// Copyright © 2019 NutritionFacts.org. All rights reserved. +// +// swiftlint:disable cyclomatic_complexity +// swiftlint:disable function_body_length +// swiftlint:disable type_body_length +// swiftlint:disable file_length + +import Foundation +import RealmSwift + +protocol RealmDelegate: AnyObject { + func didUpdateFile() +} + +class RealmProvider { + + public static let realmFilename = "NutritionFacts.realm" + + private let realm: Realm + private var unsavedDailyTracker: DailyTracker? + + /// Default current local Realm at Library/Database/defaultName + convenience init() { + let fileURL: URL = URL.inDatabase(filename: RealmProvider.realmFilename) + self.init(fileURL: fileURL) + } + + /// Prefer `init()` for default current local Realm. + /// Use `init(fileURL: URL)` to access a local Realm which _is not the current local default_. + init(fileURL: URL) { + let config = Realm.Configuration( + fileURL: fileURL, // local Realm file url + objectTypes: [DataCountRecord.self, DataWeightRecord.self]) + Realm.Configuration.defaultConfiguration = config + guard let realm = try? Realm() else { + fatalError("FAIL: could not instantiate RealmProvider.") + } + self.realm = realm + } + + func initialDailyTracker(date: Date) -> DailyTracker { + return DailyTracker(date: date) + } + + /// Use: weight entry + func getDBWeight(date: Date, ampm: DataWeightType) -> DataWeightRecord? { + let datestampKey = date.datestampKey + let pid = ampm == .am ? "\(datestampKey).am" : "\(datestampKey).pm" + + let weightRecord = realm.object(ofType: DataWeightRecord.self, forPrimaryKey: pid) + return weightRecord + } + + // Use: datetime list to sync with HealthKit + func getDBWeightDatetimes() -> Results { + return realm.objects(DataWeightRecord.self) + } + + /// Use: TBD + func getDailyWeights(fromDate: Date, toDate: Date) -> (am: [DataWeightRecord], pm: [DataWeightRecord]) { + var amRecords = [DataWeightRecord]() + var pmRecords = [DataWeightRecord]() + let weightResults = realm.objects(DataWeightRecord.self) + let weightResultsById = weightResults.sorted(byKeyPath: "pid") + + let fromDateKey = fromDate.datestampKey + let toDateKey = toDate.datestampKey + + for dataWeightRecord in weightResultsById { + let pidKeys = dataWeightRecord.pidKeys + if pidKeys.datestampKey >= fromDateKey && + pidKeys.datestampKey <= toDateKey { + if pidKeys.typeKey == "am" { + amRecords.append(dataWeightRecord) + } else { + pmRecords.append(dataWeightRecord) + } + } + } + + return (amRecords, pmRecords) + } + + /// Use: history view + func getDailyWeights() -> (am: [DataWeightRecord], pm: [DataWeightRecord]) { + var amRecords = [DataWeightRecord]() + var pmRecords = [DataWeightRecord]() + let weightResults = realm.objects(DataWeightRecord.self) + let weightResultsById = weightResults.sorted(byKeyPath: "pid") + + for dataWeightRecord in weightResultsById { + let pidKeys = dataWeightRecord.pidKeys + if pidKeys.typeKey == "am" { + amRecords.append(dataWeightRecord) + } else { + pmRecords.append(dataWeightRecord) + } + } + + return (amRecords, pmRecords) + } + + /// Use: weight export + func getDailyWeightsArray() -> [DataWeightRecord] { + var records = [DataWeightRecord]() + let weightResults = realm.objects(DataWeightRecord.self) + let weightResultsById = weightResults.sorted(byKeyPath: "pid") + + for dataWeightRecord in weightResultsById { + records.append(dataWeightRecord) + } + + return records + } + + func getDailyTracker(date: Date) -> DailyTracker { + let datestampKey = date.datestampKey + + var dailyTracker = DailyTracker(date: date) + + for dataCountType in DataCountType.allCases { + let pid = DataCountRecord.pid(datestampKey: datestampKey, typeKey: dataCountType.typeKey) + if let item = realm.object(ofType: DataCountRecord.self, forPrimaryKey: pid) { + dailyTracker.itemsDict[dataCountType] = item + } else { + dailyTracker.itemsDict[dataCountType] = DataCountRecord(date: date, countType: dataCountType) + } + } + + unsavedDailyTracker = dailyTracker + return dailyTracker + } + + /// Note: minimal checked. Expects stored database values to be valid. Exists on first data error. + func getDailyTrackers() -> [DailyTracker] { + // Daily Dozen & Tweaks Counters + let counterResultsById = realm.objects(DataCountRecord.self) + .sorted(byKeyPath: "pid") + + // Weight History + let weightResultsById = realm.objects(DataWeightRecord.self) + .sorted(byKeyPath: "pid") + + if counterResultsById.count > 0 && weightResultsById.count > 0 { + return getDailyTrackersAll(counterResults: counterResultsById, weightResults: weightResultsById) + } else if counterResultsById.count > 0 { + return getDailyTrackersCountersOnly(counterResults: counterResultsById) + } else if weightResultsById.count > 0 { + return getDailyTrackersWeightOnly(weightResults: weightResultsById) + } else { + return [DailyTracker]() + } + } + + /// requires presort from lower to higher datestamps + private func getDailyTrackersAll(counterResults: Results, weightResults: Results) -> [DailyTracker] { + var allTrackers = [DailyTracker]() + + var thisCounterRecord: DataCountRecord = counterResults[0] + var thisCounterDatestamp = thisCounterRecord.pidKeys.datestampKey + guard let thisCounterDate = Date(datestampKey: thisCounterDatestamp) else { return allTrackers } + + var thisWeightRecord: DataWeightRecord = weightResults[0] + var thisWeightDatestamp = thisWeightRecord.pidKeys.datestampKey + guard let thisWeightDate = Date(datestampKey: thisWeightDatestamp) else { return allTrackers } + + var counterIndex = 0 + var weightIndex = 0 + var lastDatestamp = thisCounterDatestamp + var tracker = DailyTracker(date: thisCounterDate) + + if thisWeightDatestamp > thisCounterDatestamp { + lastDatestamp = thisWeightDatestamp + tracker = DailyTracker(date: thisWeightDate) + } + while counterIndex < counterResults.count && weightIndex < weightResults.count { + thisCounterRecord = counterResults[counterIndex] + thisCounterDatestamp = thisCounterRecord.pidKeys.datestampKey + thisWeightRecord = weightResults[weightIndex] + thisWeightDatestamp = thisWeightRecord.pidKeys.datestampKey + + if thisCounterDatestamp == lastDatestamp { + guard let countType = thisCounterRecord.pidParts?.countType else { return allTrackers } + tracker.itemsDict[countType] = thisCounterRecord + counterIndex += 1 + } else if thisWeightDatestamp == lastDatestamp { + if thisWeightRecord.pidKeys.typeKey == DataWeightType.am.typeKey { + tracker.weightAM = thisWeightRecord + } else { + tracker.weightPM = thisWeightRecord + } + weightIndex += 1 + } else { + allTrackers.append(tracker) + // take the lesser of CounterDatestamp or WeightDatestamp + if thisCounterDatestamp <= thisWeightDatestamp { + lastDatestamp = thisCounterDatestamp + if let date = Date(datestampKey: thisCounterDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } else { + lastDatestamp = thisWeightDatestamp + if let date = Date(datestampKey: thisWeightDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } + + } + } + // Append remaining counters + if counterIndex < counterResults.count { + while counterIndex < counterResults.count { + thisCounterRecord = counterResults[counterIndex] + thisCounterDatestamp = thisCounterRecord.pidKeys.datestampKey + + if thisCounterDatestamp > lastDatestamp { + allTrackers.append(tracker) + if let date = Date(datestampKey: thisCounterDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } + guard let countType = thisCounterRecord.pidParts?.countType else { return allTrackers } + tracker.itemsDict[countType] = thisCounterRecord + + counterIndex += 1 + lastDatestamp = thisCounterDatestamp + } + } + // Append remaining weight records + else if weightIndex < weightResults.count { + while weightIndex < weightResults.count { + thisWeightRecord = weightResults[weightIndex] + thisWeightDatestamp = thisWeightRecord.pidKeys.datestampKey + + if thisWeightDatestamp > lastDatestamp { + allTrackers.append(tracker) + if let date = Date(datestampKey: thisWeightDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } + if thisWeightRecord.pidKeys.typeKey == DataWeightType.am.typeKey { + tracker.weightAM = thisWeightRecord + } else { + tracker.weightPM = thisWeightRecord + } + + weightIndex += 1 + lastDatestamp = thisWeightDatestamp + } + } + + allTrackers.append(tracker) + + return allTrackers + } + + /// requires presort from lower to higher datestamps + private func getDailyTrackersCountersOnly(counterResults: Results) -> [DailyTracker] { + var allTrackers = [DailyTracker]() + + var thisCounterRecord: DataCountRecord = counterResults[0] + var thisCounterDatestamp = thisCounterRecord.pidKeys.datestampKey + guard let thisDate = Date(datestampKey: thisCounterDatestamp) else { return allTrackers } + + var counterIndex = 0 + var lastDatestamp = thisCounterDatestamp + var tracker = DailyTracker(date: thisDate) + while counterIndex < counterResults.count { + thisCounterRecord = counterResults[counterIndex] + thisCounterDatestamp = thisCounterRecord.pidKeys.datestampKey + + if thisCounterDatestamp > lastDatestamp { + allTrackers.append(tracker) + if let date = Date(datestampKey: thisCounterDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } + guard let countType = thisCounterRecord.pidParts?.countType else { return allTrackers } + tracker.itemsDict[countType] = thisCounterRecord + + counterIndex += 1 + lastDatestamp = thisCounterDatestamp + } + allTrackers.append(tracker) + + return allTrackers + } + + /// requires presort from lower to higher datestamps + private func getDailyTrackersWeightOnly(weightResults: Results) -> [DailyTracker] { + var allTrackers = [DailyTracker]() + + var thisWeightRecord: DataWeightRecord = weightResults[0] + var thisWeightDatestamp = thisWeightRecord.pidKeys.datestampKey + guard let thisDate = Date(datestampKey: thisWeightDatestamp) else { return allTrackers } + + var weightIndex = 0 + var lastDatestamp = thisWeightDatestamp + var tracker = DailyTracker(date: thisDate) + while weightIndex < weightResults.count { + thisWeightRecord = weightResults[weightIndex] + thisWeightDatestamp = thisWeightRecord.pidKeys.datestampKey + + if thisWeightDatestamp > lastDatestamp { + allTrackers.append(tracker) + if let date = Date(datestampKey: thisWeightDatestamp) { + tracker = DailyTracker(date: date) + } else { return allTrackers } // early fail if datestamp invalid + } + if thisWeightRecord.pidKeys.typeKey == DataWeightType.am.typeKey { + tracker.weightAM = thisWeightRecord + } else { + tracker.weightPM = thisWeightRecord + } + + weightIndex += 1 + lastDatestamp = thisWeightDatestamp + } + allTrackers.append(tracker) + + return allTrackers + } + + func saveCount(_ count: Int, date: Date, countType: DataCountType) { + let id = DataCountRecord.pid(date: date, countType: countType) + saveCount(count, pid: id) + } + + func saveCount(_ count: Int, pid: String) { + saveDailyTracker() + do { + try realm.write { + realm.create( + DataCountRecord.self, + value: ["pid": pid, "count": count], + update: Realm.UpdatePolicy.all) + } + } catch { + LogService.shared.error( + "RealmProvider saveCount \(error.localizedDescription)" + ) + } + } + + func saveDBWeight(date: Date, ampm: DataWeightType, kg: Double) { + // DataWeightRecord(date: date, weightType: weightType, kg: kg) + guard kg > 0.0 else { return } + let pid = "\(date.datestampKey).\(ampm.typeKey)" + do { + try realm.write { + realm.create( + DataWeightRecord.self, + value: ["pid": pid, "kg": kg, "time": date.datestampHHmm], + update: Realm.UpdatePolicy.all) + } + } catch { + LogService.shared.error( + "RealmProvider saveDBWeight \(error.localizedDescription)" + ) + } + } + + func deleteDBWeight(date: Date, ampm: DataWeightType) { + let pid = "\(date.datestampKey).\(ampm.typeKey)" + if let record = realm.object(ofType: DataWeightRecord.self, forPrimaryKey: pid) { + do { + try realm.write { + realm.delete(record) + } + } catch { + LogService.shared.error( + "RealmProvider deleteWeight \(error.localizedDescription)" + ) + } + } + } + + func updateStreak(_ streak: Int, date: Date, countType: DataCountType) { + let pid = DataCountRecord.pid(date: date, countType: countType) + updateStreak(streak, pid: pid) + } + + /// :!!!:NYI: updateStreak() needs to do more than a single value + func updateStreak(_ streak: Int, pid: String) { + saveDailyTracker() + do { + try realm.write { + realm.create( + DataCountRecord.self, + value: ["pid": pid, "streak": streak], + update: Realm.UpdatePolicy.all + ) + } + } catch { + LogService.shared.error( + "RealmProvider updateStreak \(error.localizedDescription)" + ) + } + } + + func saveDailyTracker() { + guard let tracker = unsavedDailyTracker else { + //LogService.shared.debug( + // "RealmProvider saveDailyTracker unsavedDailyTracker is nil" + //) + return + } + saveDailyTracker(tracker: tracker) + } + + func saveDailyTracker(tracker: DailyTracker) { + do { + try realm.write { + let trackerDict = tracker.itemsDict + for key in trackerDict.keys { + realm.add( + trackerDict[key]!, // DataCountRecord + update: Realm.UpdatePolicy.all + ) + } + unsavedDailyTracker = nil + } + } catch { + LogService.shared.error( + "FAIL RealmProvider saveDailyTracker() tracker:\(tracker) description:\(error.localizedDescription)" + ) + } + } + + func deleteDBAll() { + do { + try realm.write { + realm.deleteAll() + } + } catch { + LogService.shared.error( + "FAIL RealmProvider deleteDBAll() description:\(error.localizedDescription)" + ) + } + } + +} diff --git a/DailyDozen/DailyDozen/Details/.DS_Store b/DailyDozen/DailyDozen/Details/.DS_Store deleted file mode 100644 index df592eaa..00000000 Binary files a/DailyDozen/DailyDozen/Details/.DS_Store and /dev/null differ diff --git a/DailyDozen/DailyDozen/Details/Controllers/DetailsDataProvider.swift b/DailyDozen/DailyDozen/Details/Controllers/DetailsDataProvider.swift deleted file mode 100644 index 0b733dba..00000000 --- a/DailyDozen/DailyDozen/Details/Controllers/DetailsDataProvider.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// DetailsDataProvider.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 31.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit - -class DetailsDataProvider: NSObject, UITableViewDataSource { - - // MARK: - Nested - private struct Keys { - static let sizesID = "detailsSizesCell" - static let typesID = "detailsTypesCell" - } - - var viewModel: DetailViewModel! - - // MARK: - UITableViewDataSource - func numberOfSections(in tableView: UITableView) -> Int { - return 2 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let sectionType = DetailsSection(rawValue: section) else { - fatalError("There should be a section type") - } - switch sectionType { - case .sizes: - return viewModel.sizesCount - case .types: - return viewModel.typesCount - } - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let sectionType = DetailsSection(rawValue: indexPath.section) else { - fatalError("There should be a section type") - } - switch sectionType { - - case .sizes: - guard - let cell = tableView - .dequeueReusableCell(withIdentifier: Keys.sizesID) as? SizesCell - else { return UITableViewCell() } - - cell - .configure(title: viewModel.sizeDescription(for: indexPath.row)) - return cell - - case .types: - guard - let cell = tableView - .dequeueReusableCell(withIdentifier: Keys.typesID) as? TypesCell - else { return UITableViewCell() } - - cell - .configure(title: viewModel.typeData(for: indexPath.row).name, - useLink: viewModel.typeData(for: indexPath.row).hasLink, - tag: indexPath.row) - return cell - } - } -} diff --git a/DailyDozen/DailyDozen/Details/Controllers/DetailsViewController.swift b/DailyDozen/DailyDozen/Details/Controllers/DetailsViewController.swift deleted file mode 100644 index 24d40ea6..00000000 --- a/DailyDozen/DailyDozen/Details/Controllers/DetailsViewController.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// DetailsViewController.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 31.10.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit - -// MARK: - Builder -class DetailsBuilder { - - // MARK: - Nested - struct Keys { - static let storyboard = "Details" - } - - // MARK: - Methods - /// Instantiates and returns the initial view controller for a storyboard. - /// - /// - Parameter item: An item name. - /// - Returns: The initial view controller in the storyboard. - static func instantiateController(with item: String) -> DetailsViewController { - let storyboard = UIStoryboard(name: Keys.storyboard, bundle: nil) - guard - let viewController = storyboard - .instantiateInitialViewController() as? DetailsViewController - else { fatalError("There should be a controller") } - - viewController.setViewModel(for: item) - - return viewController - } -} - -// MARK: - Controller -class DetailsViewController: UIViewController { - - // MARK: - Nested - private struct Keys { - static let videos = "VIDEOS" - } - - // MARK: - Outlets - @IBOutlet private weak var tableView: UITableView! - @IBOutlet private weak var dataProvider: DetailsDataProvider! - @IBOutlet private weak var titleLabel: UILabel! - @IBOutlet private weak var imageView: UIImageView! - - // MARK: - UIViewController - override func viewDidLoad() { - super.viewDidLoad() - - tableView.dataSource = dataProvider - tableView.delegate = self - - navigationItem - .rightBarButtonItem = UIBarButtonItem(title: Keys.videos, - style: .done, - target: self, - action: #selector(barItemPressed)) - - imageView.image = dataProvider.viewModel.image - titleLabel.text = dataProvider.viewModel.itemTitle - } - - // MARK: - Methods - /// Sets a view model for the current item. - /// - /// - Parameter item: The current item name. - func setViewModel(for item: String) { - dataProvider.viewModel = TextsProvider.shared.loadDetail(for: item) - } - - /// Opens the main topic url in the browser. - @objc private func barItemPressed() { - UIApplication.shared - .open(dataProvider.viewModel.topicURL, - options: [:], - completionHandler: nil) - } - - // MARK: - Actions - /// Updates the tableView for the current unit type. - /// - /// - Parameter sender: The button. - @IBAction private func unitsChanged(_ sender: UIButton) { - let sectionIndex = DetailsSection.sizes.rawValue - guard - let text = sender.titleLabel?.text?.lowercased(), - let currentUnitsType = UnitsType(rawValue: text), - let indexPaths = tableView.indexPathsForRows(in: tableView.rect(forSection: sectionIndex)) - else { return } - - let newUnitsType = currentUnitsType.toggledType - let title = newUnitsType.title - sender.setTitle(title, for: .normal) - dataProvider.viewModel.unitsType = newUnitsType - tableView.reloadRows(at: indexPaths, with: .fade) - } - - /// Opens the type topic url in the browser. - /// - /// - Parameter sender: The button. - @IBAction private func linkButtonPressed(_ sender: UIButton) { - guard let url = dataProvider.viewModel.typeTopicURL(for: sender.tag) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } -} - -// MARK: - UITableViewDelegate -extension DetailsViewController: UITableViewDelegate { - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - guard let sectionType = DetailsSection(rawValue: indexPath.section) else { - fatalError("There should be a section type") - } - return sectionType.rowHeight - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - guard let sectionType = DetailsSection(rawValue: section) else { - fatalError("There should be a section type") - } - return sectionType.headerHeigh - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let sectionType = DetailsSection(rawValue: section) else { - fatalError("There should be a section type") - } - return sectionType.headerView - } -} diff --git a/DailyDozen/DailyDozen/Details/Models/Detail.swift b/DailyDozen/DailyDozen/Details/Models/Detail.swift deleted file mode 100644 index 54cf37f3..00000000 --- a/DailyDozen/DailyDozen/Details/Models/Detail.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Detail.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 07.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import Foundation - -struct Detail { - - // MARK: - Properties - var metricSizes: [String] - var imperialSizes: [String] - var types: [[String: String]] - - // MARK: - Inits - init(metricSizes: [String], imperialSizes: [String], types: [[String: String]]) { - self.metricSizes = metricSizes - self.imperialSizes = imperialSizes - self.types = types - } -} diff --git a/DailyDozen/DailyDozen/Details/SupportingFiles/DetailsSection.swift b/DailyDozen/DailyDozen/Details/SupportingFiles/DetailsSection.swift deleted file mode 100644 index 1cb4a48b..00000000 --- a/DailyDozen/DailyDozen/Details/SupportingFiles/DetailsSection.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// SectionType.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 01.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit - -enum DetailsSection: Int { - - private struct Nibs { - static let sizesHeader = "SizesHeader" - static let typesHeader = "TypesHeader" - } - - private struct Strings { - static let sizesHeader = "Serving Sizes" - static let typesHeader = "Types" - } - - case sizes, types - - var rowHeight: CGFloat { - switch self { - case .sizes, .types: - return 75 - } - } - - var headerHeigh: CGFloat { - switch self { - case .sizes: - return 75 - case .types: - return 50 - } - } - - var headerView: UIView? { - switch self { - case .sizes: - return Bundle.main - .loadNibNamed(Nibs.sizesHeader, owner: nil)?.first as? UIView - case .types: - return Bundle.main - .loadNibNamed(Nibs.typesHeader, owner: nil)?.first as? UIView - } - } - - var title: String? { - switch self { - case .sizes: - return Strings.sizesHeader - case .types: - return Strings.typesHeader - } - } - -} diff --git a/DailyDozen/DailyDozen/Details/SupportingFiles/UnitsType.swift b/DailyDozen/DailyDozen/Details/SupportingFiles/UnitsType.swift deleted file mode 100644 index 2f035a08..00000000 --- a/DailyDozen/DailyDozen/Details/SupportingFiles/UnitsType.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// UnitsType.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 13.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import Foundation - -enum UnitsType: String { - - case metric, imperial - - /// Returns an uppercase version of the rawValue for the current type. - var title: String { - return self.rawValue.uppercased() - } - - /// Returns toggled type for the current type. - var toggledType: UnitsType { - return self == .metric ? UnitsType.imperial : UnitsType.metric - } -} diff --git a/DailyDozen/DailyDozen/Details/ViewModels/DetailViewModel.swift b/DailyDozen/DailyDozen/Details/ViewModels/DetailViewModel.swift deleted file mode 100644 index 96b4f491..00000000 --- a/DailyDozen/DailyDozen/Details/ViewModels/DetailViewModel.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// DetailViewModel.swift -// DailyDozen -// -// Created by Konstantin Khokhlov on 07.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. -// - -import UIKit - -struct DetailViewModel { - - // MARK: - Properties - private let detail: Detail - private let itemName: String - private let topic: String - - var unitsType = UnitsType.metric - - /// Returns the main topic url. - var topicURL: URL { - return LinksService.shared.link(forTopic: topic) - } - - /// Returns the number of items in the metric sizes. - var sizesCount: Int { - return detail.metricSizes.count - } - - /// Returns the number of items in the types. - var typesCount: Int { - return detail.types.count - } - /// Returns the item name. - var itemTitle: String { - return itemName - } - - /// Returns an image of the item. - var image: UIImage? { - return UIImage(named: itemName.lowercased().replacingOccurrences(of: " ", with: "_")) - } - - // MARK: - Inits - init(itemName: String, topic: String, metricSizes: [String], imperialSizes: [String], types: [[String: String]]) { - detail = Detail(metricSizes: metricSizes, imperialSizes: imperialSizes, types: types) - self.itemName = itemName - self.topic = topic - } - - // MARK: - Methods - /// Returns a size description for the current index. - /// - /// - Parameter index: The current index. - /// - Returns: A description string. - func sizeDescription(for index: Int) -> String { - return unitsType == .metric ? detail.metricSizes[index] : detail.imperialSizes[index] - } - - /// Returns a tuple of the type name and type link state for the current index. - /// - /// - Parameter index: The current index. - /// - Returns: A tuple of the type name and type link. - func typeData(for index: Int) -> (name: String, hasLink: Bool) { - let name = detail.types[index].keys.first ?? "" - let hasLink = detail.types[index].values.first == "" - return (name, hasLink) - } - - /// Returns the type topic for the current index. - /// - /// - Parameter index: The current index. - /// - Returns: The type toipic url. - func typeTopicURL(for index: Int) -> URL? { - guard let topic = detail.types[index].values.first else { return nil } - return LinksService.shared.link(forTopic: topic) - } -} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailSizeCell.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailSizeCell.swift new file mode 100644 index 00000000..3f165c69 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailSizeCell.swift @@ -0,0 +1,22 @@ +// +// DozeDetailSizeCell.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class DozeDetailSizeCell: UITableViewCell { + + // MARK: - Outlets + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: - Methods. + /// Sets the new title text. + /// + /// - Parameter title: The new title text. + func configure(title: String) { + titleLabel.text = title + } +} diff --git a/DailyDozen/DailyDozen/Details/View/TypesCell.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailTypeCell.swift similarity index 69% rename from DailyDozen/DailyDozen/Details/View/TypesCell.swift rename to DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailTypeCell.swift index 2da135d6..d7fe3f3e 100644 --- a/DailyDozen/DailyDozen/Details/View/TypesCell.swift +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailTypeCell.swift @@ -1,14 +1,13 @@ // -// TypesCell.swift +// DozeDetailTypeCell.swift // DailyDozen // -// Created by Konstantin Khokhlov on 10.11.17. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// Copyright © 2019 Nutritionfacts.org. All rights reserved. // import UIKit -class TypesCell: UITableViewCell { +class DozeDetailTypeCell: UITableViewCell { // MARK: - Outlets @IBOutlet private weak var titleLabel: UILabel! @@ -26,4 +25,10 @@ class TypesCell: UITableViewCell { linkButton.isHidden = useLink linkButton.tag = tag } + + // Use: 21 Tweaks + func configure(title: String) { + titleLabel.text = title + } + } diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailViewController.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailViewController.swift new file mode 100644 index 00000000..2d499779 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Controllers/DozeDetailViewController.swift @@ -0,0 +1,154 @@ +// +// DozeDetailViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +// MARK: - Builder +class DozeDetailBuilder { + + // MARK: - Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Parameter itemTypeKey: An item type key string. + /// - Returns: The initial view controller in the storyboard. + static func instantiateController(itemTypeKey: String) -> DozeDetailViewController { + + let storyboard = UIStoryboard(name: "DozeDetailLayout", bundle: nil) + guard + let viewController = storyboard + .instantiateInitialViewController() as? DozeDetailViewController + else { fatalError("Did not instantiate `DozeDetailViewController`") } + + viewController.setViewModel(itemTypeKey: itemTypeKey) + + return viewController + } +} + +// MARK: - Controller +class DozeDetailViewController: UIViewController { + + // MARK: - Nested + private struct Strings { + static let videos = "VIDEOS" // :NYI:NSLocalizedString: + } + + // MARK: - Outlets + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var dataProvider: DozeDetailDataProvider! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var detailsImageView: UIImageView! + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + tableView.dataSource = dataProvider + tableView.delegate = self + tableView.estimatedRowHeight = DozeDetailSections.amount.estimatedRowHeight + tableView.rowHeight = UITableView.automaticDimension // dynamic height + + if let dataCountType = dataProvider.dataCountType { + if dataCountType.typeKey.prefix(4) == "doze" { + let topicUrl = dataProvider.viewModel.topicURL + // "urlSegment.base"="https://nutritionfacts.org/"; + if topicUrl.path != "/" { + // DozeDetailViewController add "VIDEOS" navigation + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: Strings.videos, + style: .done, + target: self, + action: #selector(barItemPressed) + ) + } + } + } + + detailsImageView.image = dataProvider.viewModel.detailsImage + titleLabel.text = dataProvider.viewModel.itemTitle + } + + // MARK: - Methods + /// Sets a view model for the current item. + /// + /// - Parameter item: The current item name. + func setViewModel(itemTypeKey: String) { + dataProvider.dataCountType = DataCountType(itemTypeKey: itemTypeKey) + dataProvider.viewModel = DozeTextsProvider.shared.getDetails(itemTypeKey: itemTypeKey) + } + + /// Opens the main topic url in the browser. + @objc private func barItemPressed() { + UIApplication.shared + .open(dataProvider.viewModel.topicURL, + options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), + completionHandler: nil) + } + + // MARK: - Actions + /// Updates the tableView for the current unit type. + /// + /// - Parameter sender: The button. + @IBAction private func unitsChanged(_ sender: UIButton) { + let sectionIndex = DozeDetailSections.amount.rawValue + guard + let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let currentUnitsType = UnitsType(rawValue: unitsTypePrefStr), + let indexPaths = tableView.indexPathsForRows(in: tableView.rect(forSection: sectionIndex)) + else { return } + + let newUnitsType: UnitsType = currentUnitsType.toggledType + UserDefaults.standard.set(newUnitsType.rawValue, forKey: SettingsKeys.unitsTypePref) + let title = newUnitsType.title + sender.setTitle(title, for: .normal) + dataProvider.viewModel.unitsType = newUnitsType + tableView.reloadRows(at: indexPaths, with: .fade) + NotificationCenter.default.post( + name: Notification.Name(rawValue: "NoticeChangedUnitsType"), + object: SettingsManager.isImperial(), + userInfo: nil) + } + + /// Opens the type topic url in the browser. + /// + /// - Parameter sender: The button. + @IBAction private func linkButtonPressed(_ sender: UIButton) { + guard let url = dataProvider.viewModel.typeTopicURL(index: sender.tag) else { return } + UIApplication.shared.open(url, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil) + } +} + +// MARK: - UITableViewDelegate +extension DozeDetailViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard let sectionType = DozeDetailSections(rawValue: section) else { + fatalError("There should be a section type") + } + return sectionType.headerHeight + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let sectionType = DozeDetailSections(rawValue: section) else { + fatalError("There should be a doze section type") + } + if let dataCountType = dataProvider.dataCountType { + if dataCountType.typeKey.prefix(5) == "tweak" { + return sectionType.headerView + } + } + return sectionType.headerView + } +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { + return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)}) +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailDataProvider.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailDataProvider.swift new file mode 100644 index 00000000..2c9f5a03 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailDataProvider.swift @@ -0,0 +1,74 @@ +// +// DozeDetailDataProvider.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class DozeDetailDataProvider: NSObject, UITableViewDataSource { + + // MARK: - Nested + private struct Strings { + static let sizeCellID = "dozeDetailSizeCell" + static let typeCellID = "dozeDetailTypeCell" + } + + var viewModel: DozeDetailViewModel! + var dataCountType: DataCountType! + + // MARK: - UITableViewDataSource + func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let sectionType = DozeDetailSections(rawValue: section) else { + fatalError("There should be a section type") + } + switch sectionType { + case .amount: + return viewModel.amountCount + case .example: + return viewModel.exampleCount + } + } + + // Row Cell At Index + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let sectionType = DozeDetailSections(rawValue: indexPath.section) else { + fatalError("There should be a section type") + } + switch sectionType { + + case .amount: + guard let cell = tableView + .dequeueReusableCell(withIdentifier: Strings.sizeCellID) as? DozeDetailSizeCell + else { return UITableViewCell() } + + cell.configure( + title: viewModel.sizeDescription(index: indexPath.row) + ) + return cell + + case .example: + guard let cell = tableView + .dequeueReusableCell(withIdentifier: Strings.typeCellID) as? DozeDetailTypeCell + else { return UITableViewCell() } + let typeData = viewModel.typeData(index: indexPath.row) + + if dataCountType.isTweak { + cell.configure(title: typeData.name) + } else { + cell.configure( + title: typeData.name, + useLink: typeData.hasLink, + tag: indexPath.row + ) + } + + return cell + } + } +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailInfo.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailInfo.swift new file mode 100644 index 00000000..69325592 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailInfo.swift @@ -0,0 +1,32 @@ +// +// DozeDetailInfo.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// +// swiftlint:disable nesting + +import Foundation + +struct DozeDetailInfo: Codable { + + struct Item: Codable { + + struct Serving: Codable { // Display Subheading: Size + var imperial: String + var metric: String + } + + struct Variety: Codable { // Display Subheading: Type + var text: String + var topic: String // URL path fragment + } + + var heading: String + var servings: [Serving] // AKA size + var varieties: [Variety] // AKA type + var topic: String // item level URL path fragment + } + + var itemsDict: [String: Item] +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailViewModel.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailViewModel.swift new file mode 100644 index 00000000..792d25f2 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Models/DozeDetailViewModel.swift @@ -0,0 +1,92 @@ +// +// DozeDetailViewModel.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +struct DozeDetailViewModel { + + // MARK: - Properties + private let info: DozeDetailInfo.Item + private let detailItemTypeKey: String + + var unitsType: UnitsType + + /// Returns the main topic url. + var topicURL: URL { + return LinksService.shared.link(topic: info.topic) + } + + /// Returns the number of items in the "amounts" (aka "serving size"). + var amountCount: Int { + return info.servings.count + } + + /// Returns the number of items in the "examples" (aka "serving varieties" or "serving types"). + var exampleCount: Int { + return info.varieties.count + } + /// Returns the item name. + var itemTitle: String { + return info.heading + } + + /// Returns an image of the item. + var detailsImage: UIImage? { + return UIImage(named: "detail_\(detailItemTypeKey)") + } + + // MARK: - Inits + init(itemTypeKey: String, info: DozeDetailInfo.Item) { + self.detailItemTypeKey = itemTypeKey + self.info = info + + if let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let unitsTypePref = UnitsType(rawValue: unitsTypePrefStr) { + self.unitsType = unitsTypePref + } else { + // :NYI:ToBeLocalized: set initial default based on device language + self.unitsType = UnitsType.imperial + UserDefaults.standard.set(self.unitsType.rawValue, forKey: SettingsKeys.unitsTypePref) + } + } + + // MARK: - Methods + /// Returns a size description for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: A description string. + func sizeDescription(index: Int) -> String { + if unitsType == .metric { + return info.servings[index].metric + } else { + return info.servings[index].imperial + } + } + + /// Returns a tuple of the type name and type link state for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: A tuple of the type name and type link. + func typeData(index: Int) -> (name: String, hasLink: Bool) { + let name = info.varieties[index].text + let hasLink = info.varieties[index].topic == "" // :???:!!!: correct logic? + return (name, hasLink) + } + + /// Returns the type topic for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: The type toipic url. + func typeTopicURL(index: Int) -> URL? { + if info.varieties[index].topic.isEmpty { // :???:!!!: review logic + return nil + } + let topic = info.varieties[index].topic + return LinksService.shared.link(topic: topic) + } + +} diff --git a/DailyDozen/DailyDozen/Details/Storyboards/Details.storyboard b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailLayout.storyboard similarity index 90% rename from DailyDozen/DailyDozen/Details/Storyboards/Details.storyboard rename to DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailLayout.storyboard index 1644c264..0df381ac 100644 --- a/DailyDozen/DailyDozen/Details/Storyboards/Details.storyboard +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailLayout.storyboard @@ -1,46 +1,37 @@ - - - - + + - - + - - - HelveticaNeue - HelveticaNeue-Medium - - - + - + - + - + - - + + - + - + - - + + - + - + + @@ -157,12 +149,12 @@ - - + + - + - + - + diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeHeader.xib b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeHeader.xib new file mode 100644 index 00000000..5908ddb3 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeHeader.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeUnitHeader.xib b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeUnitHeader.xib new file mode 100644 index 00000000..9c98ffdc --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailSizeUnitHeader.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/Details/Storyboards/TypesHeader.xib b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailTypeHeader.xib similarity index 88% rename from DailyDozen/DailyDozen/Details/Storyboards/TypesHeader.xib rename to DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailTypeHeader.xib index 7d7270ed..a8f4691f 100644 --- a/DailyDozen/DailyDozen/Details/Storyboards/TypesHeader.xib +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/Base.lproj/DozeDetailTypeHeader.xib @@ -1,19 +1,12 @@ - - - - + + - + - - - HelveticaNeue-Bold - - diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/DozeDetailSections.swift b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/DozeDetailSections.swift new file mode 100644 index 00000000..599aa732 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/DozeDetailSections.swift @@ -0,0 +1,72 @@ +// +// DozeDetailSections.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +enum DozeDetailSections: Int { + + private struct Nibs { + static let amountHeaderNib = "DozeDetailSizeHeader" + static let amountHeaderUnitNib = "DozeDetailSizeUnitHeader" + static let exampleHeaderNib = "DozeDetailTypeHeader" + } + + case amount, example + + var headerHeight: CGFloat { + switch self { + case .amount: + // :UNITS_VISIBILITY: Handle imperial|metric unit button visibility + let shouldShowTypeToggle = UserDefaults.standard.bool(forKey: SettingsKeys.unitsTypeToggleShowPref) + if shouldShowTypeToggle { + return 75 // Height: "Serving Sizes" + "Units" + } else { + return 50 // Height: "Serving Sizes" only + } + case .example: + return 50 + } + } + + var estimatedRowHeight: CGFloat { + switch self { + case .amount: + return 75 + case .example: + return 75 + } + } + + var headerView: UIView? { + switch self { + case .amount: + // :UNITS_VISIBILITY: Handle imperial|metric unit button visibility + let shouldShowTypeToggle = UserDefaults.standard.bool(forKey: SettingsKeys.unitsTypeToggleShowPref) + if shouldShowTypeToggle == false { + return Bundle.main + .loadNibNamed(Nibs.amountHeaderNib, owner: nil)?.first as? UIView + } + if let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let currentUnitsType = UnitsType(rawValue: unitsTypePrefStr), + let uiView: UIView = Bundle.main + .loadNibNamed(Nibs.amountHeaderUnitNib, owner: nil)? + .first as? UIView { + // Handle imperial vs. metric units + let buttons = uiView.subviews(ofType: UIButton.self) + for btn in buttons { + btn.setTitle(currentUnitsType.title, for: .normal) + } + return uiView + } + return nil + case .example: + return Bundle.main + .loadNibNamed(Nibs.exampleHeaderNib, owner: nil)?.first as? UIView + } + } + +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailLayout.strings b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailLayout.strings new file mode 100644 index 00000000..536a8ce4 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailLayout.strings @@ -0,0 +1,12 @@ +/* Class = "UILabel"; text = "Beans"; ObjectID = "iJb-Vn-TS0"; */ +"iJb-Vn-TS0.text" = "Frijoles"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "m5s-0r-St1"; */ +"m5s-0r-St1.text" = "marcador de posición"; + +/* Class = "UIButton"; normalTitle = "VIDEOS"; ObjectID = "xcg-0a-oqY"; */ +"xcg-0a-oqY.normalTitle" = "Videos"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "yct-Iw-M3s"; */ +"yct-Iw-M3s.text" = "marcador de posición"; + diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeHeader.strings b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeHeader.strings new file mode 100644 index 00000000..972277d5 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeHeader.strings @@ -0,0 +1,3 @@ +/* Class = "UILabel"; text = "Serving Sizes"; ObjectID = "eU6-je-hVN"; */ +"eU6-je-hVN.text" = "Tamaño de las Porciones"; + diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeUnitHeader.strings b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeUnitHeader.strings new file mode 100644 index 00000000..d1d88674 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailSizeUnitHeader.strings @@ -0,0 +1,8 @@ +/* Class = "UILabel"; text = "Serving Sizes"; ObjectID = "eU6-je-hVN"; */ +"eU6-je-hVN.text" = "Tamaño de las Porciones"; + +/* Class = "UILabel"; text = "Units:"; ObjectID = "3bd-8h-kcX"; */ +"3bd-8h-kcX.text" = "Unidades:"; + +/* Class = "UIButton"; normalTitle = "METRIC"; ObjectID = "1K3-d9-Hfb"; */ +"1K3-d9-Hfb.normalTitle" = "Métrico"; diff --git a/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailTypeHeader.strings b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailTypeHeader.strings new file mode 100644 index 00000000..7a4a20a3 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeDetail/Views/es.lproj/DozeDetailTypeHeader.strings @@ -0,0 +1,3 @@ +/* Class = "UILabel"; text = "Types"; ObjectID = "YVI-sV-TDX"; */ +"YVI-sV-TDX.text" = "Tipos"; + diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryPagerViewController.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryPagerViewController.swift new file mode 100644 index 00000000..5e57b2e9 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryPagerViewController.swift @@ -0,0 +1,139 @@ +// +// DozeEntryPagerViewController.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import SimpleAnimation + +// MARK: - Builder + +class DozeEntryPagerBuilder { + + // MARK: - Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Returns: The initial view controller in the storyboard. + static func instantiateController() -> UIViewController { + let storyboard = UIStoryboard(name: "DozeEntryPagerLayout", bundle: nil) + guard + let viewController = storyboard.instantiateInitialViewController() + else { fatalError("Did not instantiate `DozeEntryPagerViewController`") } + + return viewController + } +} + +// MARK: - Controller +class DozeEntryPagerViewController: UIViewController { + + // MARK: - Properties + private var currentDate = Date() { + didSet { + if currentDate.isInCurrentDayWith(Date()) { + backButton.superview?.isHidden = true + dateButton.setTitle("Today", for: .normal) + } else { + backButton.superview?.isHidden = false + dateButton.setTitle(datePicker.date.dateString(for: .long), for: .normal) + } + } + } + + // MARK: - Outlets + @IBOutlet private weak var dateButton: UIButton! { + didSet { + dateButton.layer.borderWidth = 1 + dateButton.layer.borderColor = dateButton.titleColor(for: .normal)?.cgColor + dateButton.layer.cornerRadius = 5 + } + } + @IBOutlet private weak var datePicker: UIDatePicker! { + didSet { + datePicker.maximumDate = Date() + } + } + + @IBOutlet private weak var backButton: UIButton! + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + title = NSLocalizedString("navtab.doze", comment: "Daily Dozen (proper noun) navigation tab") + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + + if UserDefaults.standard.bool(forKey: SettingsKeys.hasSeenFirstLaunch) == false { + UserDefaults.standard.set(true, forKey: SettingsKeys.hasSeenFirstLaunch) + let viewController = FirstLaunchBuilder.instantiateController() + navigationController?.pushViewController(viewController, animated: true) + } + + } + + // MARK: - Methods + /// Updates UI for the current date. + /// + /// - Parameter date: The current date. + func updateDate(_ date: Date) { + currentDate = date + datePicker.setDate(date, animated: false) + + guard let viewController = children.first as? DozeEntryViewController else { return } + viewController.view.fadeOut().fadeIn() + viewController.setViewModel(date: currentDate) + } + + // MARK: - Actions + @IBAction private func dateButtonPressed(_ sender: UIButton) { + datePicker.isHidden = false + dateButton.isHidden = true + } + + @IBAction private func dateChanged(_ sender: UIDatePicker) { + dateButton.isHidden = false + datePicker.isHidden = true + currentDate = datePicker.date + + guard let viewController = children.first as? DozeEntryViewController else { return } + viewController.view.fadeOut().fadeIn() + viewController.setViewModel(date: datePicker.date) + } + + @IBAction private func viewSwipped(_ sender: UISwipeGestureRecognizer) { + let interval = sender.direction == .left ? -1 : 1 + let currentDate = datePicker.date.adding(.day, value: interval) + + let today = Date() + + guard let date = currentDate, date <= today else { return } + + datePicker.setDate(date, animated: false) + + self.currentDate = datePicker.date + + guard let viewController = children.first as? DozeEntryViewController else { return } + + if sender.direction == .left { + viewController.view.slideOut(x: -view.frame.width).slideIn(x: view.frame.width) + } else { + viewController.view.slideOut(x: view.frame.width).slideIn(x: -view.frame.width) + } + + viewController.setViewModel(date: datePicker.date) + } + + @IBAction private func backButtonPressed(_ sender: UIButton) { + updateDate(Date()) + } +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryStateCell.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryStateCell.swift new file mode 100644 index 00000000..32bd750b --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryStateCell.swift @@ -0,0 +1,27 @@ +// +// DozeEntryStateCell.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class DozeEntryStateCell: UICollectionViewCell { + + // MARK: - Outlets + @IBOutlet private weak var checkbox: UIButtonCheckbox! + + // MARK: - Methods + + /// Sets the checkbox with the current state. Toggle border color. + /// + /// - Parameter state: The current state. + func configure(with state: Bool) { + checkbox.isSelected = state + // Border color must match image background in UIButtonCheckbox setCheckboxImage() + // UIColor.greenColor "ic_checkmark_white_green" + // UIColor.redCheckmarkColor "ic_checkmark_white_red" + checkbox.layer.borderColor = state ? UIColor.greenColor.cgColor : UIColor.grayLightColor.cgColor + } +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryTableViewCell.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryTableViewCell.swift new file mode 100644 index 00000000..88c675e9 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryTableViewCell.swift @@ -0,0 +1,59 @@ +// +// DozeEntryTableViewCell.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class DozeEntryTableViewCell: UITableViewCell { + + // MARK: - Outlets + @IBOutlet private weak var itemImage: UIImageView! + @IBOutlet private weak var streakLabel: UILabel! + @IBOutlet private weak var itemHeadingLabel: UILabel! + @IBOutlet weak var stateCollection: UICollectionView! + @IBOutlet private weak var infoButton: UIButton! + @IBOutlet private weak var calendarButton: UIButton! + + private let oneDay = 1 + private let oneWeek = 7 + private let twoWeeks = 14 + + // MARK: - Methods + /// Sets the servings cell with the current heading, image name and row index tag. + /// + /// - Parameter heading: The current heading. + /// - Parameter tag: The current row index tag. + /// - Parameter imageName: The image filename tag. + func configure(heading: String, tag: Int, imageName: String, streak: Int = 0) { + itemHeadingLabel.text = heading + stateCollection.tag = tag + infoButton.tag = tag + calendarButton.tag = tag + itemImage.image = UIImage(named: imageName) + + if let superview = streakLabel.superview { + if streak > oneDay { + let streakFormat = NSLocalizedString("streakDaysFormat", comment: "streak days format") + streakLabel.text = String(format: streakFormat, streak) + superview.isHidden = false + + if streak < oneWeek { + superview.backgroundColor = UIColor.streakBronzeColor + streakLabel.textColor = UIColor.white + } else if streak < twoWeeks { + superview.backgroundColor = UIColor.streakSilverColor + streakLabel.textColor = UIColor.black + } else { + superview.backgroundColor = UIColor.streakGoldColor + streakLabel.textColor = UIColor.black + } + + } else { + superview.isHidden = true + } + } + } +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryViewController.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryViewController.swift new file mode 100644 index 00000000..a8046882 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Controllers/DozeEntryViewController.swift @@ -0,0 +1,221 @@ +// +// DozeEntryViewController.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import StoreKit // Used to request app store reivew by user. + +class DozeEntryViewController: UIViewController { + + // MARK: - Outlets + @IBOutlet private weak var dataProvider: DozeEntryDataProvider! + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var countLabel: UILabel! + @IBOutlet private weak var starImage: UIImageView! + + // MARK: - Properties + private let realm = RealmProvider() + private let dozeDailyStateCountMaximum = 24 + + /// Number of 'checked' states for the viewed date. + private var dozeDailyStateCount = 0 { + didSet { + countLabel.text = statesCountString + if dozeDailyStateCount == dozeDailyStateCountMaximum { + starImage.popIn() // Show daily achievement star + // Ask the user for ratings and reviews in the App Store + SKStoreReviewController.requestReview() + } else { + starImage.popOut() // Hide daily achievement star + } + } + } + private var statesCountString: String { + return "\(dozeDailyStateCount) / \(dozeDailyStateCountMaximum)" + } + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + + setViewModel(date: Date()) + + tableView.dataSource = dataProvider + tableView.delegate = self + tableView.estimatedRowHeight = DozeEntrySections.main.dozeEstimatedRowHeight + tableView.rowHeight = UITableView.automaticDimension // dynamic height + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + appDelegate.realmDelegate = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setViewModel(date: dataProvider.viewModel.trackerDate) + } + + // MARK: - Methods + /// Sets a view model for the current date. + /// + /// - Parameter item: The current date. + func setViewModel(date: Date) { + dataProvider.viewModel = DozeEntryViewModel(tracker: realm.getDailyTracker(date: date)) + + // Update N/MAX daily checked items count + dozeDailyStateCount = 0 + let mainItemCount = dataProvider.viewModel.count - DozeEntrySections.supplementsCount + for i in 0 ..< mainItemCount { + let itemStates: [Bool] = dataProvider.viewModel.dozeItemStates(rowIndex: i) + for state in itemStates where state { + dozeDailyStateCount += 1 + } + } + tableView.reloadData() + } + + // MARK: - Actions + + /// DozeEntryTableViewCell infoButton + @IBAction private func dozeInfoPressed(_ sender: UIButton) { + let itemInfo = dataProvider.viewModel.itemInfo(rowIndex: sender.tag) + + guard !itemInfo.isSupplemental else { + let url = dataProvider.viewModel.topicURL(itemTypeKey: itemInfo.itemType.typeKey) + UIApplication.shared + .open(url, + options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), + completionHandler: nil) + return + } + let viewController = DozeDetailBuilder.instantiateController(itemTypeKey: itemInfo.itemType.typeKey) + navigationController?.pushViewController(viewController, animated: true) + } + + /// DozeEntryTableViewCell calendarButton + @IBAction private func dozeCalendarPressed(_ sender: UIButton) { + let heading = dataProvider.viewModel.itemInfo(rowIndex: sender.tag).itemType.headingDisplay + let itemType = dataProvider.viewModel.itemType(rowIndex: sender.tag) + let viewController = ItemHistoryBuilder.instantiateController(heading: heading, itemType: itemType) + navigationController?.pushViewController(viewController, animated: true) + } + + @IBAction private func supplementsHeaderInfoBtnPressed(_ sender: UIButton) { + let alert = AlertBuilder.instantiateController(for: .dietarySupplement) + present(alert, animated: true, completion: nil) + } + + @IBAction private func dozeHistoryPressed(_ sender: UIButton) { + let viewController = DozeHistoryBuilder.instantiateController() + navigationController?.pushViewController(viewController, animated: true) + } +} + +// MARK: - Servings UITableViewDelegate + +extension DozeEntryViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let dozeTableViewCell = cell as? DozeEntryTableViewCell else { return } + dozeTableViewCell.stateCollection.delegate = self + dozeTableViewCell.stateCollection.dataSource = dataProvider + dozeTableViewCell.stateCollection.reloadData() + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard let servingsSection = DozeEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return servingsSection.headerHeight + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return DozeEntrySections.main.footerHeight + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let servingsSection = DozeEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return servingsSection.headerView + } +} + +// MARK: - States UICollectionViewDelegate +extension DozeEntryViewController: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let rowIndex = collectionView.tag // which item + let checkmarkIndex = indexPath.row // which checkmark + var checkmarkStates = dataProvider.viewModel.dozeItemStates(rowIndex: rowIndex) + let itemPid = dataProvider.viewModel.itemPid(rowIndex: rowIndex) + + var stateTrueCounterOld = 0 + for state in checkmarkStates where state { + stateTrueCounterOld += 1 + } + + // Update States + let stateNew = !checkmarkStates[checkmarkIndex] // toggle state + checkmarkStates[checkmarkIndex] = stateNew + // 0 is the rightmost item checkbox + // fill true to the right. + for index in 0 ..< checkmarkIndex { + checkmarkStates[index] = true + } + // fill false to the left. + for index in checkmarkIndex+1 ..< checkmarkStates.count { + checkmarkStates[index] = false + } + + guard let cell = collectionView.cellForItem(at: indexPath) as? DozeEntryStateCell else { + fatalError("There should be a cell") + } + cell.configure(with: checkmarkStates[indexPath.row]) + let itemType = dataProvider.viewModel.itemType(rowIndex: rowIndex) + + // Update Streak + let countMax = checkmarkStates.count + let countNow = checkmarkStates.filter { $0 }.count + var streak = countMax == countNow ? 1 : 0 + realm.saveCount(countNow, pid: itemPid) + + // :!!!: streak needs to include more than today+yesterday + if streak > 0 { + let yesterday = dataProvider.viewModel.trackerDate.adding(.day, value: -1)! + // previous day's streak +1 + let yesterdayTracker = realm.getDailyTracker(date: yesterday) + if let yesterdayStreak = yesterdayTracker.itemsDict[itemType]?.streak { + streak += yesterdayStreak + } + } + + realm.updateStreak(streak, pid: itemPid) + + tableView.reloadData() + + guard !dataProvider.viewModel.itemInfo(rowIndex: rowIndex).isSupplemental else { + return + } + + let stateTrueCounterNew = stateNew ? checkmarkIndex+1 : checkmarkIndex + + dozeDailyStateCount += stateTrueCounterNew - stateTrueCounterOld + } +} + +extension DozeEntryViewController: RealmDelegate { + + func didUpdateFile() { + navigationController?.popViewController(animated: false) + } +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { + return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)}) +} diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryDataProvider.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryDataProvider.swift new file mode 100644 index 00000000..003d1fc0 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryDataProvider.swift @@ -0,0 +1,100 @@ +// +// DozeEntryDataProvider.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class DozeEntryDataProvider: NSObject, UITableViewDataSource { + + // MARK: - Nested + private struct Strings { + static let dozeTableViewCell = "dozeTableViewCell" + static let dozeStateCell = "dozeStateCell" + } + + var viewModel: DozeEntryViewModel! + + // MARK: - Servings UITableViewDataSource + func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let servingsSection = DozeEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return servingsSection.numberOfRowsInSection(with: viewModel.count) + } + + // Row Cell At Index + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let realm = RealmProvider() + guard + let dozeTableViewCell = tableView + .dequeueReusableCell(withIdentifier: Strings.dozeTableViewCell) as? DozeEntryTableViewCell else { + fatalError("Expected `DozeEntryTableViewCell`") + } + guard + let servingsSection = DozeEntrySections(rawValue: indexPath.section) else { + fatalError("Expected `servingsSection`") + } + var rowIndex = indexPath.row + if servingsSection == .supplements { + rowIndex += tableView.numberOfRows(inSection: 0) + } + + let states = viewModel.dozeItemStates(rowIndex: rowIndex) + let countMax = states.count + let countNow = states.filter { $0 }.count + var streak = countMax == countNow ? 1 : 0 + + if streak > 0 { + let yesterday = viewModel.trackerDate.adding(.day, value: -1)! + // previous streak +1 + let yesterdayItems = realm.getDailyTracker(date: yesterday).itemsDict + let itemType = viewModel.itemType(rowIndex: rowIndex) + if let yesterdayStreak = yesterdayItems[itemType]?.streak { + streak += yesterdayStreak + } + } + + let itemType = viewModel.itemInfo(rowIndex: rowIndex).itemType + dozeTableViewCell.configure( + heading: itemType.headingDisplay, + tag: rowIndex, + imageName: itemType.imageName, + streak: streak) + + // viewModel: DozeEntryViewModel tracker + // tracker: DailyTracker getPid + + let itemPid = viewModel.itemPid(rowIndex: rowIndex) + realm.updateStreak(streak, pid: itemPid) + + return dozeTableViewCell + } +} + +// MARK: - States UICollectionViewDataSource +extension DozeEntryDataProvider: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return viewModel.itemInfo(rowIndex: collectionView.tag).itemType.maxServings + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: Strings.dozeStateCell, + for: indexPath) + guard let stateCell = cell as? DozeEntryStateCell else { + fatalError("There should be a cell") + } + + let states = viewModel.dozeItemStates(rowIndex: collectionView.tag) + stateCell.configure(with: states[indexPath.row]) + return stateCell // individual checkbox + } +} diff --git a/DailyDozen/DailyDozen/Servings/SupportingFiles/ServingsSection.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntrySections.swift similarity index 51% rename from DailyDozen/DailyDozen/Servings/SupportingFiles/ServingsSection.swift rename to DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntrySections.swift index 112616a5..01b8e9fb 100644 --- a/DailyDozen/DailyDozen/Servings/SupportingFiles/ServingsSection.swift +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntrySections.swift @@ -1,26 +1,23 @@ // -// ServingsSection.swift +// DozeEntrySections.swift // DailyDozen // -// Created by Konstantin Khokhlov on 15.11.17. // Copyright © 2017 Nutritionfacts.org. All rights reserved. // import UIKit -enum ServingsSection: Int { +enum DozeEntrySections: Int { - private struct Keys { - static let vitaminsHeader = "VitaminsHeader" + static let supplementsCount: Int = 1 + + private struct Strings { + static let supplementsHeader = "DozeEntryExtrasHeader" } - case main, vitamin + case main, supplements - private var vitaminsCount: Int { - return 2 - } - - var rowHeight: CGFloat { + var dozeEstimatedRowHeight: CGFloat { return 100 } @@ -28,7 +25,7 @@ enum ServingsSection: Int { switch self { case .main: return 0.1 - case .vitamin: + case .supplements: return 50 } } @@ -41,18 +38,18 @@ enum ServingsSection: Int { switch self { case .main: return nil - case .vitamin: + case .supplements: return Bundle.main - .loadNibNamed(Keys.vitaminsHeader, owner: nil)?.first as? UIView + .loadNibNamed(Strings.supplementsHeader, owner: nil)?.first as? UIView } } func numberOfRowsInSection(with count: Int) -> Int { switch self { case .main: - return count - vitaminsCount - case .vitamin: - return vitaminsCount + return count - DozeEntrySections.supplementsCount + case .supplements: + return DozeEntrySections.supplementsCount } } } diff --git a/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryViewModel.swift b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryViewModel.swift new file mode 100644 index 00000000..b4332b79 --- /dev/null +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Models/DozeEntryViewModel.swift @@ -0,0 +1,126 @@ +// +// DozeEntryViewModel.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +class DozeEntryViewModel { + + static let rowTypeArray: [DataCountType] = [ + .dozeBeans, + .dozeBerries, + .dozeFruitsOther, + .dozeVegetablesCruciferous, + .dozeGreens, + .dozeVegetablesOther, + .dozeFlaxseeds, + .dozeNuts, + .dozeSpices, + .dozeWholeGrains, + .dozeBeverages, + .dozeExercise, + .otherVitaminB12 + ] + + // MARK: - Properties + private let tracker: DailyTracker + + /// Returns Daily Dozen item count. + var count: Int { + return DozeEntryViewModel.rowTypeArray.count + } + + var trackerDate: Date { + tracker.date + } + + // MARK: - Inits + init(tracker: DailyTracker) { + self.tracker = tracker + } + + // MARK: - Methods + /// Returns an item name and type in the doze for the current index. + /// + /// - Parameter index: The current table row index. + /// - Returns: A tuple with the item heading, image name and supplemental flag. + func itemInfo(rowIndex: Int) -> (itemType: DataCountType, isSupplemental: Bool) { + let rowType: DataCountType = DozeEntryViewModel.rowTypeArray[rowIndex] + let heading = rowType.headingDisplay + let isSupplemental = heading.contains("Vitamin") + || heading.contains("Omega") + + return (rowType, isSupplemental) + } + + /// Returns an item streak count for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: The streak count. + func itemStreak(rowIndex: Int) -> Int { + let itemType = DozeEntryViewModel.rowTypeArray[rowIndex] + if let dataCountRecord = tracker.itemsDict[itemType] { + return dataCountRecord.streak + } else { + return 0 + } + } + + /// Returns a url for the current item name. + /// + /// - Parameter itemName: The item name. + /// - Returns: A NutritionFacts topic url. + func topicURL(itemTypeKey: String) -> URL { + let topic = DozeTextsProvider.shared.getTopic(itemTypeKey: itemTypeKey) + return LinksService.shared.link(topic: topic) + } + + /// Returns item states in the doze for the current index. + /// + /// - Parameter index: The current row index. + /// - Returns: The states booland array. + func dozeItemStates(rowIndex: Int) -> [Bool] { + let rowType = DozeEntryViewModel.rowTypeArray[rowIndex] + let maxServings = rowType.maxServings + var states = [Bool](repeating: false, count: maxServings) + if let count = tracker.itemsDict[rowType]?.count { + for i in 0.. DataCountType { + return DozeEntryViewModel.rowTypeArray[rowIndex] + } + + /// Returns an item type key in the tracker for the current index. + /// + /// - Parameter index: The current row index. + /// - Returns: The item type key string. + func itemTypeKey(rowIndex: Int) -> String { + return DozeEntryViewModel.rowTypeArray[rowIndex].typeKey + } + + func itemPid(rowIndex: Int) -> String { + let itemType = DozeEntryViewModel.rowTypeArray[rowIndex] + return tracker.getPid(typeKey: itemType) + } + + /// Returns an image name for the current index. + /// + /// - Parameter index: The current table row index. + /// - Returns: The image name. + func imageName(rowIndex: Int) -> String { + return DozeEntryViewModel.rowTypeArray[rowIndex].imageName + } + +} diff --git a/DailyDozen/DailyDozen/Servings/Storyboards/VitaminsHeader.xib b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryExtrasHeader.xib similarity index 73% rename from DailyDozen/DailyDozen/Servings/Storyboards/VitaminsHeader.xib rename to DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryExtrasHeader.xib index 5bf1b0f3..089146bf 100644 --- a/DailyDozen/DailyDozen/Servings/Storyboards/VitaminsHeader.xib +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryExtrasHeader.xib @@ -1,40 +1,31 @@ - - - - + - - + - - - HelveticaNeue - - - + - + - @@ -47,6 +38,7 @@ + diff --git a/DailyDozen/DailyDozen/Servings/Storyboards/Servings.storyboard b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryLayout.storyboard similarity index 83% rename from DailyDozen/DailyDozen/Servings/Storyboards/Servings.storyboard rename to DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryLayout.storyboard index 31a88619..77bf4b43 100644 --- a/DailyDozen/DailyDozen/Servings/Storyboards/Servings.storyboard +++ b/DailyDozen/DailyDozen/DozeSection/DozeEntry/Views/Base.lproj/DozeEntryLayout.storyboard @@ -1,34 +1,23 @@ - - - - + + - + - - - Helvetica-Bold - - - HelveticaNeue - HelveticaNeue-Medium - - - + - + - + - + + + - - - + - + + @@ -86,59 +76,78 @@ - + - + - - + + - + - - + + - - + + + - + - - + + - - - - + + + + + + + + + + + + + + + @@ -151,14 +160,14 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/Settings/Views/es.lproj/SettingsLayout.strings b/DailyDozen/DailyDozen/Settings/Views/es.lproj/SettingsLayout.strings new file mode 100644 index 00000000..78cd0924 --- /dev/null +++ b/DailyDozen/DailyDozen/Settings/Views/es.lproj/SettingsLayout.strings @@ -0,0 +1,48 @@ +/* Class = "UIButton"; normalTitle = "Advanced Utilities"; ObjectID = "Ebp-Lw-fWk"; */ +"Ebp-Lw-fWk.normalTitle" = "Utilidades Avanzadas"; + +/* Class = "UITableViewSection"; headerTitle = "Daily Reminder"; ObjectID = "GiY-ao-2ee"; */ +"GiY-ao-2ee.headerTitle" = "Recordatorio diario"; + +/* Class = "UITableViewSection"; footerTitle = "Set to always be one unit type or enable the 'Units:' toggle button."; ObjectID = "IFs-g0-SPV"; */ +"IFs-g0-SPV.footerTitle" = "Configure siempre un tipo de unidad o active el botón de alternancia 'Unidades:'."; + +/* Class = "UITableViewSection"; headerTitle = "Measurement Units"; ObjectID = "IFs-g0-SPV"; */ +"IFs-g0-SPV.headerTitle" = "Unidades de medida"; + +/* Class = "UILabel"; text = "Enable Reminders"; ObjectID = "itH-G7-Kal"; */ +"itH-G7-Kal.text" = "Configuración del Recordatorio Diario"; + +/* Class = "UILabel"; text = "Remind me at:"; ObjectID = "kR3-Sa-Fpt"; */ +"kR3-Sa-Fpt.text" = "Recuerdame a las"; + +/* Class = "UISegmentedControl"; OZO-Xa-v5G.segmentTitles[0] = "Daily Dozen Only"; ObjectID = "OZO-Xa-v5G"; */ +"OZO-Xa-v5G.segmentTitles[0]" = "Docena Diaria\nsolamente"; + +/* Class = "UISegmentedControl"; OZO-Xa-v5G.segmentTitles[1] = "Daily Dozen + 21 Tweaks"; ObjectID = "OZO-Xa-v5G"; */ +"OZO-Xa-v5G.segmentTitles[1]" = "Docena Diaria +\n21 Ajustes"; + +/* Class = "UILabel"; text = "Enable Reminders"; ObjectID = "pjz-An-Ls5"; */ +"pjz-An-Ls5.text" = "Activa el Recordatorio Diario"; + +/* Class = "UISegmentedControl"; RLb-GV-4hh.segmentTitles[0] = "Imperial"; ObjectID = "RLb-GV-4hh"; */ +"RLb-GV-4hh.segmentTitles[0]" = "Imperial"; + +/* Class = "UISegmentedControl"; RLb-GV-4hh.segmentTitles[1] = "Metric"; ObjectID = "RLb-GV-4hh"; */ +"RLb-GV-4hh.segmentTitles[1]" = "Métrico"; + +/* Class = "UISegmentedControl"; RLb-GV-4hh.segmentTitles[2] = "Toggle Units"; ObjectID = "RLb-GV-4hh"; */ +"RLb-GV-4hh.segmentTitles[2]" = "Palanca"; + +/* Class = "UITableViewSection"; footerTitle = "For health alone, use \"Daily Dozen Only\". For health and weight loss use \"Daily Dozen + 21 Tweaks\"."; ObjectID = "WdR-XV-IyP"; */ +"WdR-XV-IyP.footerTitle" = "Solo para la salud, use \"Docena Diaria solamente\". Para la salud y la pérdida de peso, use \"Docena Diaria 21 Ajustes\"."; + +/* Class = "UITableViewSection"; headerTitle = "21 Tweaks Visibility"; ObjectID = "WdR-XV-IyP"; */ +"WdR-XV-IyP.headerTitle" = "Visibilidad de 21 Ajustes"; + +/* Class = "UILabel"; text = "On"; ObjectID = "XFw-2q-BdT"; */ +"XFw-2q-BdT.text" = "En"; + +/* Class = "UILabel"; text = "Play Sound"; ObjectID = "YP6-dP-Y62"; */ +"YP6-dP-Y62.text" = "Sonido"; + diff --git a/DailyDozen/DailyDozen/Details/View/SizesCell.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailActivityCell.swift similarity index 78% rename from DailyDozen/DailyDozen/Details/View/SizesCell.swift rename to DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailActivityCell.swift index a63b5fc2..dd40ca4d 100644 --- a/DailyDozen/DailyDozen/Details/View/SizesCell.swift +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailActivityCell.swift @@ -1,14 +1,13 @@ // -// SizesCell.swift +// TweakDetailActivityCell.swift // DailyDozen // -// Created by Konstantin Khokhlov on 10.11.17. // Copyright © 2017 Nutritionfacts.org. All rights reserved. // import UIKit -class SizesCell: UITableViewCell { +class TweakDetailActivityCell: UITableViewCell { // MARK: - Outlets @IBOutlet private weak var titleLabel: UILabel! diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailDescriptionCell.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailDescriptionCell.swift new file mode 100644 index 00000000..434c8869 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailDescriptionCell.swift @@ -0,0 +1,34 @@ +// +// TweakDetailDescriptionCell.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class TweakDetailDescriptionCell: UITableViewCell { // :!!!:TweakDetailDescriptionCell: + + // MARK: - Outlets + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var linkButton: UIButton! + + // MARK: - Methods + /// Sets the cell data. + /// + /// - Parameters: + /// - title: The title text. + /// - useLink: The new state for the link button. + /// - tag: The button tag. + func configure(title: String, useLink: Bool, tag: Int) { + titleLabel.text = title + linkButton.isHidden = useLink + linkButton.tag = tag + } + + // Use: 21 Tweaks + func configure(title: String) { + titleLabel.text = title + } + +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailViewController.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailViewController.swift new file mode 100644 index 00000000..b4527f0a --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Controllers/TweakDetailViewController.swift @@ -0,0 +1,144 @@ +// +// TweakDetailViewController.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +// MARK: - Builder +class TweakDetailBuilder { + + // MARK: - Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Parameter itemTypeKey: An item type key string. + /// - Returns: The initial view controller in the storyboard. + static func instantiateController(itemTypeKey: String) -> TweakDetailViewController { + + let storyboard = UIStoryboard(name: "TweakDetailLayout", bundle: nil) + guard + let viewController = storyboard + .instantiateInitialViewController() as? TweakDetailViewController + else { fatalError("Did not instantiate `TweakDetailViewController`") } + + viewController.setViewModel(itemTypeKey: itemTypeKey) + + return viewController + } +} + +// MARK: - Controller +class TweakDetailViewController: UIViewController { + + // MARK: - Nested + private struct Strings { + static let videos = "VIDEOS" // :NYI:NSLocalizedString: + } + + // MARK: - Outlets + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var dataProvider: TweakDetailDataProvider! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var detailsImageView: UIImageView! + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + tableView.dataSource = dataProvider + tableView.delegate = self + tableView.estimatedRowHeight = TweakDetailSections.activity.estimatedRowHeight + tableView.rowHeight = UITableView.automaticDimension // dynamic height + + if let dataCountType = dataProvider.dataCountType { + if dataCountType.typeKey.prefix(4) == "doze" { + // TweakDetailViewController VIDEOS + navigationItem.rightBarButtonItem = UIBarButtonItem( + title: Strings.videos, + style: .done, + target: self, + action: #selector(barItemPressed) + ) + } + } + + detailsImageView.image = dataProvider.viewModel.detailsImage + titleLabel.text = dataProvider.viewModel.itemTitle + } + + // MARK: - Methods + /// Sets a view model for the current item. + /// + /// - Parameter item: The current item name. + func setViewModel(itemTypeKey: String) { + dataProvider.viewModel = TweakTextsProvider.shared.getDetails(itemTypeKey: itemTypeKey) + dataProvider.dataCountType = DataCountType(itemTypeKey: itemTypeKey) + } + + /// Opens the main topic url in the browser. + @objc private func barItemPressed() { + UIApplication.shared + .open(dataProvider.viewModel.topicURL, + options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), + completionHandler: nil) + } + + // MARK: - Actions + /// Updates the tableView for the current unit type. + /// + /// - Parameter sender: The button. + @IBAction private func unitsChanged(_ sender: UIButton) { + let sectionIndex = TweakDetailSections.activity.rawValue + guard + let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let currentUnitsType = UnitsType(rawValue: unitsTypePrefStr), + let indexPaths = tableView.indexPathsForRows(in: tableView.rect(forSection: sectionIndex)) + else { return } + + let newUnitsType: UnitsType = currentUnitsType.toggledType + UserDefaults.standard.set(newUnitsType.rawValue, forKey: SettingsKeys.unitsTypePref) + let title = newUnitsType.title + sender.setTitle(title, for: .normal) + dataProvider.viewModel.unitsType = newUnitsType + tableView.reloadRows(at: indexPaths, with: .fade) + NotificationCenter.default.post( + name: Notification.Name(rawValue: "NoticeChangedUnitsType"), + object: SettingsManager.isImperial(), + userInfo: nil) + } + +} + +// MARK: - UITableViewDelegate +extension TweakDetailViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard + let sectionType = TweakDetailSections(rawValue: section), + let dataCountType = dataProvider.dataCountType + else { + fatalError("There should be a tweak section header height") + } + return sectionType.headerHeight(itemTypeKey: dataCountType.typeKey) + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard + let sectionType = TweakDetailSections(rawValue: section), + let dataCountType = dataProvider.dataCountType + else { + fatalError("There should be a tweak section type") + } + return sectionType.headerTweaksView(itemTypeKey: dataCountType.typeKey) + } +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { + return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)}) +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailDataProvider.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailDataProvider.swift new file mode 100644 index 00000000..38bedf4e --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailDataProvider.swift @@ -0,0 +1,66 @@ +// +// TweakDetailDataProvider.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class TweakDetailDataProvider: NSObject, UITableViewDataSource { + + // MARK: - Nested + private struct Strings { + static let activityCellID = "tweakDetailActivityCell" + static let descriptionCellID = "tweakDetailDescriptionCell" + } + + var viewModel: TweakDetailViewModel! + var dataCountType: DataCountType! + + // MARK: - UITableViewDataSource + func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let sectionType = TweakDetailSections(rawValue: section) else { + fatalError("There should be a section type") + } + switch sectionType { + case .activity: + return viewModel.activityCount + case .description: + return viewModel.descriptionParagraphCount + } + } + + // Row Cell At Index + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let sectionType = TweakDetailSections(rawValue: indexPath.section) else { + fatalError("There should be a section type") + } + switch sectionType { + + case .activity: + guard let cell = tableView + .dequeueReusableCell(withIdentifier: Strings.activityCellID) as? TweakDetailActivityCell + else { return UITableViewCell() } + + cell.configure( + title: viewModel.activity(index: indexPath.row) + ) + return cell + + case .description: + guard let cell = tableView + .dequeueReusableCell(withIdentifier: Strings.descriptionCellID) as? TweakDetailDescriptionCell + else { return UITableViewCell() } + let descriptionParagraph = viewModel.descriptionParagraph(index: indexPath.row) + + cell.configure(title: descriptionParagraph) + + return cell + } + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailInfo.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailInfo.swift new file mode 100644 index 00000000..84a1dd75 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailInfo.swift @@ -0,0 +1,27 @@ +// +// TweakDetailInfo.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// +// swiftlint:disable nesting + +import Foundation + +struct TweakDetailInfo: Codable { + + struct Item: Codable { + + struct Activity: Codable { // Display Subheading + var imperial: String + var metric: String + } + + var heading: String + var activity: Activity // like doze size + var description: [String] // like doze type + var topic: String // item level URL path fragment + } + + var itemsDict: [String: Item] +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailViewModel.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailViewModel.swift new file mode 100644 index 00000000..f3199ed4 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Models/TweakDetailViewModel.swift @@ -0,0 +1,78 @@ +// +// TweakDetailViewModel.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +struct TweakDetailViewModel { + + // MARK: - Properties + private let info: TweakDetailInfo.Item + private let detailItemTypeKey: String + + var unitsType: UnitsType + + /// Returns the main topic url. + var topicURL: URL { + return LinksService.shared.link(topic: info.topic) + } + + /// Returns the number of activities. + var activityCount: Int { + return 1 + } + + /// Returns the number of description paragraphs. + var descriptionParagraphCount: Int { + return info.description.count + } + /// Returns the item name. + var itemTitle: String { + return info.heading + } + + /// Returns an image of the item. + var detailsImage: UIImage? { + return UIImage(named: "detail_\(detailItemTypeKey)") + } + + // MARK: - Inits + init(itemTypeKey: String, info: TweakDetailInfo.Item) { + self.detailItemTypeKey = itemTypeKey + self.info = info + + if let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let unitsTypePref = UnitsType(rawValue: unitsTypePrefStr) { + self.unitsType = unitsTypePref + } else { + // :NYI:ToBeLocalized: set initial default based on device language + self.unitsType = UnitsType.imperial + UserDefaults.standard.set(self.unitsType.rawValue, forKey: SettingsKeys.unitsTypePref) + } + } + + // MARK: - Methods + /// Returns a size description for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: A description string. + func activity(index: Int) -> String { + if unitsType == .metric { + return info.activity.metric + } else { + return info.activity.imperial + } + } + + /// Returns a tuple of the type name and type link state for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: A tuple of the type name and type link. + func descriptionParagraph(index: Int) -> String { + return info.description[index] + } + +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityHeader.xib b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityHeader.xib new file mode 100644 index 00000000..3506148f --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityHeader.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/Details/Storyboards/SizesHeader.xib b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityUnitHeader.xib similarity index 66% rename from DailyDozen/DailyDozen/Details/Storyboards/SizesHeader.xib rename to DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityUnitHeader.xib index e5774c73..54b28f35 100644 --- a/DailyDozen/DailyDozen/Details/Storyboards/SizesHeader.xib +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailActivityUnitHeader.xib @@ -1,51 +1,42 @@ - - - - + + - + - - - HelveticaNeue - HelveticaNeue-Bold - HelveticaNeue-Medium - - - + - + - - + + - - + + - - + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailDescriptionHeader.xib b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailDescriptionHeader.xib new file mode 100644 index 00000000..c6578597 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailDescriptionHeader.xib @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailLayout.storyboard b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailLayout.storyboard new file mode 100644 index 00000000..896295ef --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/Base.lproj/TweakDetailLayout.storyboard @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/TweakDetailSections.swift b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/TweakDetailSections.swift new file mode 100644 index 00000000..354f5b03 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/TweakDetailSections.swift @@ -0,0 +1,74 @@ +// +// TweakDetailSections.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +enum TweakDetailSections: Int { + + private struct Nibs { + static let activityHeaderNib = "TweakDetailActivityHeader" + static let activityHeaderUnitNib = "TweakDetailActivityUnitHeader" + static let descriptionHeaderNib = "TweakDetailDescriptionHeader" + } + + case activity, description + + func headerHeight(itemTypeKey: String) -> CGFloat { + let metricTxtEqualsImperialTxt = TweakTextsProvider.shared.isMetricTxtEqualToImperialTxt(itemTypeKey: itemTypeKey) + switch self { + case .activity: + // :UNITS_VISIBILITY: Handle imperial|metric unit button visibility + let shouldShowTypeToggle = UserDefaults.standard.bool(forKey: SettingsKeys.unitsTypeToggleShowPref) + if shouldShowTypeToggle && !metricTxtEqualsImperialTxt { + return 75 // Height: "Activity" + "Units" + } else { + return 50 // Height: "Activity" only + } + case .description: + return 50 + } + } + + var estimatedRowHeight: CGFloat { + return 75 + } + + func headerTweaksView(itemTypeKey: String) -> UIView? { + switch self { + case .activity: + // :UNITS_VISIBILITY: Handle imperial|metric unit button visibility + let shouldShowTypeToggle = UserDefaults.standard.bool(forKey: SettingsKeys.unitsTypeToggleShowPref) + if shouldShowTypeToggle == false { + return Bundle.main + .loadNibNamed(Nibs.activityHeaderNib, owner: nil)?.first as? UIView + } + if TweakTextsProvider.shared.isMetricTxtEqualToImperialTxt(itemTypeKey: itemTypeKey) { + return Bundle.main + .loadNibNamed(Nibs.activityHeaderNib, owner: nil)?.first as? UIView + } + // Handle imperial vs. metric units + if + let unitsTypePrefStr = UserDefaults.standard.string(forKey: SettingsKeys.unitsTypePref), + let currentUnitsType = UnitsType(rawValue: unitsTypePrefStr), + let uiView: UIView = Bundle.main + .loadNibNamed(Nibs.activityHeaderUnitNib, owner: nil)? + .first as? UIView { + // Handle imperial vs. metric units + let buttons = uiView.subviews(ofType: UIButton.self) + for btn in buttons { + btn.setTitle(currentUnitsType.title, for: .normal) + } + return uiView + } + return nil + case .description: + return Bundle.main + .loadNibNamed(Nibs.descriptionHeaderNib, owner: nil)?.first as? UIView + } + } + +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityHeader.strings b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityHeader.strings new file mode 100644 index 00000000..5fadfe77 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityHeader.strings @@ -0,0 +1,3 @@ +/* Class = "UILabel"; text = "Activity"; ObjectID = "YVI-sV-TDA"; */ +"YVI-sV-TDA.text" = "Actividad"; + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityUnitHeader.strings b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityUnitHeader.strings new file mode 100644 index 00000000..48740faa --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailActivityUnitHeader.strings @@ -0,0 +1,8 @@ +/* Class = "UILabel"; text = "Activity"; ObjectID = "YVI-sV-TDA"; */ +"YVI-sV-TDA.text" = "Actividad"; + +/* Class = "UILabel"; text = "Units:"; ObjectID = "kho-9G-OWe"; */ +"kho-9G-OWe.text" = "Unidades"; + +/* Class = "UIButton"; normalTitle = "METRIC"; ObjectID = "M75-CQ-NVP"; */ +"M75-CQ-NVP.normalTitle" = "Métrico"; diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailDescriptionHeader.strings b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailDescriptionHeader.strings new file mode 100644 index 00000000..b0ba3ef6 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailDescriptionHeader.strings @@ -0,0 +1,3 @@ +/* Class = "UILabel"; text = "Description"; ObjectID = "YVI-sV-TDD"; */ +"YVI-sV-TDD.text" = "Descripción"; + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailLayout.strings b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailLayout.strings new file mode 100644 index 00000000..1acb9c9c --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakDetail/Views/es.lproj/TweakDetailLayout.strings @@ -0,0 +1,9 @@ +/* Class = "UILabel"; text = "Negative Calorie Load"; ObjectID = "6m3-hV-PBi"; */ +"6m3-hV-PBi.text" = "Título de Ejemplo"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "dxC-TB-3hz"; */ +"dxC-TB-3hz.text" = "marcador de posición"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFE-S4-S9b"; */ +"OFE-S4-S9b.text" = "marcador de posición"; + diff --git a/DailyDozen/DailyDozen/Servings/Controllers/PagerViewController.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryPagerViewController.swift similarity index 69% rename from DailyDozen/DailyDozen/Servings/Controllers/PagerViewController.swift rename to DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryPagerViewController.swift index f02d558e..29e33fdc 100644 --- a/DailyDozen/DailyDozen/Servings/Controllers/PagerViewController.swift +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryPagerViewController.swift @@ -1,8 +1,7 @@ // -// PagerViewController.swift +// TweakEntryPagerViewController.swift // DailyDozen // -// Created by Konstantin Khokhlov on 18.10.17. // Copyright © 2017 Nutritionfacts.org. All rights reserved. // @@ -10,30 +9,25 @@ import UIKit import SimpleAnimation // MARK: - Builder -class PagerBuilder { - // MARK: - Nested - struct Keys { - static let storyboard = "Pager" - } +class TweakEntryPagerBuilder { // MARK: - Methods /// Instantiates and returns the initial view controller for a storyboard. /// /// - Returns: The initial view controller in the storyboard. static func instantiateController() -> UIViewController { - let storyboard = UIStoryboard(name: Keys.storyboard, bundle: nil) + let storyboard = UIStoryboard(name: "TweakEntryPagerLayout", bundle: nil) guard - let viewController = storyboard - .instantiateInitialViewController() - else { fatalError("There should be a controller") } + let viewController = storyboard.instantiateInitialViewController() + else { fatalError("Did not instantiate `TweakEntryPagerViewController`") } return viewController } } // MARK: - Controller -class PagerViewController: UIViewController { +class TweakEntryPagerViewController: UIViewController { // MARK: - Properties private var currentDate = Date() { @@ -67,14 +61,18 @@ class PagerViewController: UIViewController { // MARK: - UIViewController override func viewDidLoad() { super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white - title = "Servings" + title = NSLocalizedString("navtab.tweaks", comment: "21 Tweaks (proper noun) navigation tab") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] } // MARK: - Methods @@ -85,9 +83,9 @@ class PagerViewController: UIViewController { currentDate = date datePicker.setDate(date, animated: false) - guard let viewController = childViewControllers.first as? ServingsViewController else { return } + guard let viewController = children.first as? TweakEntryViewController else { return } viewController.view.fadeOut().fadeIn() - viewController.setViewModel(for: currentDate) + viewController.setViewModel(date: currentDate) } // MARK: - Actions @@ -101,9 +99,9 @@ class PagerViewController: UIViewController { datePicker.isHidden = true currentDate = datePicker.date - guard let viewController = childViewControllers.first as? ServingsViewController else { return } + guard let viewController = children.first as? TweakEntryViewController else { return } viewController.view.fadeOut().fadeIn() - viewController.setViewModel(for: datePicker.date) + viewController.setViewModel(date: datePicker.date) } @IBAction private func viewSwipped(_ sender: UISwipeGestureRecognizer) { @@ -118,7 +116,7 @@ class PagerViewController: UIViewController { self.currentDate = datePicker.date - guard let viewController = childViewControllers.first as? ServingsViewController else { return } + guard let viewController = children.first as? TweakEntryViewController else { return } if sender.direction == .left { viewController.view.slideOut(x: -view.frame.width).slideIn(x: view.frame.width) @@ -126,7 +124,7 @@ class PagerViewController: UIViewController { viewController.view.slideOut(x: view.frame.width).slideIn(x: -view.frame.width) } - viewController.setViewModel(for: datePicker.date) + viewController.setViewModel(date: datePicker.date) } @IBAction private func backButtonPressed(_ sender: UIButton) { diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryStateCell.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryStateCell.swift new file mode 100644 index 00000000..e9ab5d7a --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryStateCell.swift @@ -0,0 +1,27 @@ +// +// TweakEntryStateCell.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class TweakEntryStateCell: UICollectionViewCell { + + // MARK: - Outlets + @IBOutlet private weak var checkbox: UIButtonCheckbox! + + // MARK: - Methods + + /// Sets the checkbox with the current state. Toggle border color. + /// + /// - Parameter state: The current state. + func configure(with state: Bool) { + checkbox.isSelected = state + // Border color must match image background in UIButtonCheckbox setCheckboxImage() + // UIColor.greenColor "ic_checkmark_white_green" + // UIColor.redCheckmarkColor "ic_checkmark_white_red" + checkbox.layer.borderColor = state ? UIColor.greenColor.cgColor : UIColor.grayLightColor.cgColor + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryTableViewCell.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryTableViewCell.swift new file mode 100644 index 00000000..bb0b0ed9 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryTableViewCell.swift @@ -0,0 +1,61 @@ +// +// TweakEntryTableViewCell.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class TweakEntryTableViewCell: UITableViewCell { + + // MARK: - Outlets + + @IBOutlet private weak var itemImage: UIImageView! + @IBOutlet private weak var streakLabel: UILabel! + @IBOutlet private weak var itemHeadingLabel: UILabel! + @IBOutlet weak var stateCollection: UICollectionView! + @IBOutlet private weak var infoButton: UIButton! + @IBOutlet private weak var calendarButton: UIButton! + + private let oneDay = 1 + private let oneWeek = 7 + private let twoWeeks = 14 + + // MARK: - Methods + + /// Sets the servings cell with the current heading, image name and row index tag. + /// + /// - Parameter heading: The current heading. + /// - Parameter tag: The current row index tag. + /// - Parameter imageName: The image filename tag. + func configure(heading: String, tag: Int, imageName: String, streak: Int = 0) { + itemHeadingLabel.text = heading + stateCollection.tag = tag + infoButton.tag = tag + calendarButton.tag = tag + itemImage.image = UIImage(named: imageName) + + if let superview = streakLabel.superview { + if streak > oneDay { + let streakFormat = NSLocalizedString("streakDaysFormat", comment: "streak days format") + streakLabel.text = String(format: streakFormat, streak) + superview.isHidden = false + + if streak < oneWeek { + superview.backgroundColor = UIColor.streakBronzeColor + streakLabel.textColor = UIColor.white + } else if streak < twoWeeks { + superview.backgroundColor = UIColor.streakSilverColor + streakLabel.textColor = UIColor.black + } else { + superview.backgroundColor = UIColor.streakGoldColor + streakLabel.textColor = UIColor.black + } + + } else { + superview.isHidden = true + } + } + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryViewController.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryViewController.swift new file mode 100644 index 00000000..f83200ad --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Controllers/TweakEntryViewController.swift @@ -0,0 +1,247 @@ +// +// TweakEntryViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import HealthKit +import StoreKit // Used to request app store reivew by user. + +class TweakEntryViewController: UIViewController { + + // MARK: - Outlets + @IBOutlet private weak var dataProvider: TweakEntryDataProvider! + @IBOutlet private weak var tableView: UITableView! + @IBOutlet private weak var countLabel: UILabel! + @IBOutlet private weak var starImage: UIImageView! + + // MARK: - Properties + private let realm = RealmProvider() + private let tweakDailyStateCountMaximum = 37 + + /// Number of 'checked' states for the viewed date. + private var tweakDailyStateCount = 0 { + didSet { + countLabel.text = statesCountString + if tweakDailyStateCount == tweakDailyStateCountMaximum { + starImage.popIn() // Show show achievement star + // Ask the user for ratings and reviews in the App Store + SKStoreReviewController.requestReview() + } else { + starImage.popOut() // Hide daily achievement star + } + } + } + private var statesCountString: String { + return "\(tweakDailyStateCount) / \(tweakDailyStateCountMaximum)" + } + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + + setViewModel(date: Date()) + + tableView.dataSource = dataProvider + tableView.delegate = self + tableView.estimatedRowHeight = TweakEntrySections.main.tweakEstimatedRowHeight + tableView.rowHeight = UITableView.automaticDimension // dynamic height + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + appDelegate.realmDelegate = self + + // :HealthKit: + if HKHealthStore.isHealthDataAvailable() { + // add code to use HealthKit here... + //LogService.shared.debug("Yes, HealthKit is Available") + let healthManager = HealthManager() + healthManager.requestPermissions() + } else { + //LogService.shared.debug("There is a problem accessing HealthKit") + } + + NotificationCenter.default.addObserver( + self, + selector: #selector(changedWeight(notification:)), + name: Notification.Name(rawValue: "NoticeChangedWeight"), + object: nil) + } + + override func viewWillAppear(_ animated: Bool) { + LogService.shared.debug("TweakEntryViewController viewWillAppear") + super.viewWillAppear(animated) + setViewModel(date: dataProvider.viewModel.trackerDate) + } + + // MARK: - Methods + + /// Updates the view model for the current date. + @objc func changedWeight(notification: Notification) { + guard let dateChanged = notification.object as? Date else { return } + let dateViewed = dataProvider.viewModel.trackerDate + if dateChanged.datestampKey == dateViewed.datestampKey { + setViewModel(date: dateViewed) + } + } + + /// Sets a view model for the current date. + /// + /// - Parameter item: The current date. + func setViewModel(date: Date) { + LogService.shared.debug("TweakEntryViewController setViewModel \(date.datestampyyyyMMddHHmmss)") + dataProvider.viewModel = TweakEntryViewModel(tracker: realm.getDailyTracker(date: date)) + + // Update N/MAX daily checked items count + tweakDailyStateCount = 0 + let mainItemCount = dataProvider.viewModel.count + for i in 0 ..< mainItemCount { + let itemStates: [Bool] = dataProvider.viewModel.tweakItemStates(rowIndex: i) + for state in itemStates where state { + tweakDailyStateCount += 1 + } + } + + tableView.reloadData() + } + + // MARK: - Actions + + /// TweakEntryTableViewCell infoButton + @IBAction private func tweakInfoPressed(_ sender: UIButton) { + let itemInfo = dataProvider.viewModel.itemInfo(rowIndex: sender.tag) + + let viewController = TweakDetailBuilder.instantiateController(itemTypeKey: itemInfo.typeKey) + navigationController?.pushViewController(viewController, animated: true) + } + + /// TweakEntryTableViewCell calendarButton + @IBAction private func tweakCalendarPressed(_ sender: UIButton) { + let heading = dataProvider.viewModel.itemInfo(rowIndex: sender.tag).headingDisplay + let dataCountType = dataProvider.viewModel.itemType(rowIndex: sender.tag) + + var viewController = ItemHistoryBuilder.instantiateController(heading: heading, itemType: dataCountType) + if dataCountType == .tweakWeightTwice { + viewController = WeightHistoryBuilder.instantiateController() + } + navigationController?.pushViewController(viewController, animated: true) + } + + @IBAction private func tweakHistoryPressed(_ sender: UIButton) { + let viewController = TweakHistoryBuilder.instantiateController() + navigationController?.pushViewController(viewController, animated: true) + } +} + +// MARK: - Tweaks UITableViewDelegate + +extension TweakEntryViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let tweakTableViewCell = cell as? TweakEntryTableViewCell else { return } + tweakTableViewCell.stateCollection.delegate = self + tweakTableViewCell.stateCollection.dataSource = dataProvider + tweakTableViewCell.stateCollection.reloadData() + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard let tweakSection = TweakEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return tweakSection.headerHeight + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return TweakEntrySections.main.footerHeight + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let tweakSection = TweakEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return tweakSection.headerView + } +} + +// MARK: - States UICollectionViewDelegate +extension TweakEntryViewController: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let rowIndex = collectionView.tag // which item + let checkmarkIndex = indexPath.row // which checkmark + var checkmarkStates = dataProvider.viewModel.tweakItemStates(rowIndex: rowIndex) + let itemPid = dataProvider.viewModel.itemPid(rowIndex: rowIndex) + + var stateTrueCounterOld = 0 + for state in checkmarkStates where state { + stateTrueCounterOld += 1 + } + + // Update States + let stateNew = !checkmarkStates[checkmarkIndex] // toggle state + checkmarkStates[checkmarkIndex] = stateNew + // 0 is the rightmost item checkbox + // fill true to the right. + for index in 0 ..< checkmarkIndex { + checkmarkStates[index] = true + } + // fill false to the left. + for index in checkmarkIndex+1 ..< checkmarkStates.count { + checkmarkStates[index] = false + } + + guard let cell = collectionView.cellForItem(at: indexPath) as? TweakEntryStateCell else { + fatalError("There should be a cell") + } + cell.configure(with: checkmarkStates[indexPath.row]) + let dataCountType = dataProvider.viewModel.itemType(rowIndex: rowIndex) + + // Update Streak + let countMax = checkmarkStates.count + let countNow = checkmarkStates.filter { $0 }.count + var streak = countMax == countNow ? 1 : 0 + realm.saveCount(countNow, pid: itemPid) + + // :!!!: streak needs to include more than today+yesterday + if streak > 0 { + let yesterday = dataProvider.viewModel.trackerDate.adding(.day, value: -1)! + // previous day's streak +1 + let yesterdayTracker = realm.getDailyTracker(date: yesterday) + if let yesterdayStreak = yesterdayTracker.itemsDict[dataCountType]?.streak { + streak += yesterdayStreak + } + } + + realm.updateStreak(streak, pid: itemPid) + + tableView.reloadData() + + let stateTrueCounterNew = stateNew ? checkmarkIndex+1 : checkmarkIndex + + tweakDailyStateCount += stateTrueCounterNew - stateTrueCounterOld + + // If state was toggled on then go to weight editor + if stateNew && HealthManager.shared.isAuthorized() { + let dataCountType = dataProvider.viewModel.itemType(rowIndex: rowIndex) + if dataCountType == .tweakWeightTwice { + let viewController = WeightEntryPagerBuilder.instantiateController() + navigationController?.pushViewController(viewController, animated: true) + } + } + } +} + +extension TweakEntryViewController: RealmDelegate { + + func didUpdateFile() { + navigationController?.popViewController(animated: false) + } +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { + return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)}) +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryDataProvider.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryDataProvider.swift new file mode 100644 index 00000000..889c92ec --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryDataProvider.swift @@ -0,0 +1,90 @@ +// +// TweakEntryDataProvider.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +class TweakEntryDataProvider: NSObject, UITableViewDataSource { + + // MARK: - Nested + private struct Strings { + static let tweakTableViewCell = "tweakTableViewCell" + static let tweakStateCell = "tweakStateCell" + } + + var viewModel: TweakEntryViewModel! + + // MARK: - Tweaks UITableViewDataSource + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let tweakSection = TweakEntrySections(rawValue: section) else { + fatalError("There should be a section type") + } + return tweakSection.numberOfRowsInSection(with: viewModel.count) + } + + // Row Cell At Index + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let realm = RealmProvider() + guard + let tweakTableViewCell = tableView + .dequeueReusableCell(withIdentifier: Strings.tweakTableViewCell) as? TweakEntryTableViewCell else { + fatalError("Expected `TweakEntryTableViewCell`") + } + + let rowIndex = indexPath.row + + let states = viewModel.tweakItemStates(rowIndex: rowIndex) + let countMax = states.count + let countNow = states.filter { $0 }.count + var streak = countMax == countNow ? 1 : 0 + + let itemType = viewModel.itemType(rowIndex: rowIndex) + if streak > 0 { + let yesterday = viewModel.trackerDate.adding(.day, value: -1)! + // previous streak +1 + let yesterdayItems = realm.getDailyTracker(date: yesterday).itemsDict + if let yesterdayStreak = yesterdayItems[itemType]?.streak { + streak += yesterdayStreak + } + } + + tweakTableViewCell.configure( + heading: itemType.headingDisplay, + tag: rowIndex, + imageName: itemType.imageName, + streak: streak) + + let itemPid = viewModel.itemPid(rowIndex: rowIndex) + realm.updateStreak(streak, pid: itemPid) + + return tweakTableViewCell + } +} + +// MARK: - States UICollectionViewDataSource +extension TweakEntryDataProvider: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return viewModel.itemInfo(rowIndex: collectionView.tag).maxServings + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: Strings.tweakStateCell, + for: indexPath) + guard let stateCell = cell as? TweakEntryStateCell else { + fatalError("There should be a cell") + } + + let states = viewModel.tweakItemStates(rowIndex: collectionView.tag) + stateCell.configure(with: states[indexPath.row]) + return stateCell // individual checkbox + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntrySections.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntrySections.swift new file mode 100644 index 00000000..c2124491 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntrySections.swift @@ -0,0 +1,42 @@ +// +// TweakEntrySections.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +enum TweakEntrySections: Int { + + case main + + var tweakEstimatedRowHeight: CGFloat { + return 100 + } + + var headerHeight: CGFloat { + switch self { + case .main: + return 0.1 + } + } + + var footerHeight: CGFloat { + return 0.1 + } + + var headerView: UIView? { + switch self { + case .main: + return nil + } + } + + func numberOfRowsInSection(with count: Int) -> Int { + switch self { + case .main: + return count + } + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryViewModel.swift b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryViewModel.swift new file mode 100644 index 00000000..8cf5d1cd --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Models/TweakEntryViewModel.swift @@ -0,0 +1,133 @@ +// +// TweakEntryViewModel.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +class TweakEntryViewModel { + + static let rowTypeArray: [DataCountType] = [ + .tweakMealWater, + .tweakMealNegCal, + .tweakMealVinegar, + .tweakMealUndistracted, + .tweakMeal20Minutes, + .tweakDailyBlackCumin, + .tweakDailyGarlic, + .tweakDailyGinger, + .tweakDailyNutriYeast, + .tweakDailyCumin, + .tweakDailyGreenTea, + .tweakDailyHydrate, + .tweakDailyDeflourDiet, + .tweakDailyFrontLoad, + .tweakDailyTimeRestrict, + .tweakExerciseTiming, + .tweakWeightTwice, + .tweakCompleteIntentions, + .tweakNightlyFast, + .tweakNightlySleep, + .tweakNightlyTrendelenbrug + ] + + // MARK: - Properties + private let tracker: DailyTracker + + /// Returns 21 Tweak item count. + var count: Int { + return TweakEntryViewModel.rowTypeArray.count + } + + var trackerDate: Date { + tracker.date + } + + // MARK: - Inits + init(tracker: DailyTracker) { + self.tracker = tracker + } + + // MARK: - Methods + /// Returns an item name and type in the doze for the current index. + /// + /// - Parameter index: The current table row index. + /// - Returns: DataCountType + func itemInfo(rowIndex: Int) -> DataCountType { + return TweakEntryViewModel.rowTypeArray[rowIndex] + } + + /// Returns an item streak count for the current index. + /// + /// - Parameter index: The current index. + /// - Returns: The streak count. + func itemStreak(rowIndex: Int) -> Int { + let itemType = TweakEntryViewModel.rowTypeArray[rowIndex] + if let dataCountRecord = tracker.itemsDict[itemType] { + return dataCountRecord.streak + } else { + return 0 + } + } + + /// Returns a url for the current item name. + /// + /// - Parameter itemName: The item name. + /// - Returns: A NutritionFacts topic url. + func topicURL(itemTypeKey: String) -> URL { + let topic = TweakTextsProvider.shared.getTopic(itemTypeKey: itemTypeKey) + return LinksService.shared.link(topic: topic) + } + + /// Returns item states in the doze for the current index. + /// + /// - Parameter index: The current row index. + /// - Returns: The states booland array. + func tweakItemStates(rowIndex: Int) -> [Bool] { + let rowType = TweakEntryViewModel.rowTypeArray[rowIndex] + var states = [Bool](repeating: false, count: rowType.maxServings) + if let count = tracker.itemsDict[rowType]?.count { + for i in 0.. DataCountType { + return TweakEntryViewModel.rowTypeArray[rowIndex] + } + + /// Returns an item type key in the tracker for the current index. + /// + /// - Parameter index: The current row index. + /// - Returns: The item type key string. + func itemTypeKey(rowIndex: Int) -> String { + return TweakEntryViewModel.rowTypeArray[rowIndex].typeKey + } + + func itemPid(rowIndex: Int) -> String { + let itemType = TweakEntryViewModel.rowTypeArray[rowIndex] + return tracker.getPid(typeKey: itemType) + } + + /// Returns an image name for the current index. + /// + /// - Parameter index: The current table row index. + /// - Returns: The image name. + func imageName(rowIndex: Int) -> String { + return TweakEntryViewModel.rowTypeArray[rowIndex].imageName + } + +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryLayout.storyboard b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryLayout.storyboard new file mode 100644 index 00000000..71723b83 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryLayout.storyboard @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryPagerLayout.storyboard b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryPagerLayout.storyboard new file mode 100644 index 00000000..d115babc --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/Base.lproj/TweakEntryPagerLayout.storyboard @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryLayout.strings b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryLayout.strings new file mode 100644 index 00000000..38a36581 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryLayout.strings @@ -0,0 +1,12 @@ +/* Class = "UILabel"; text = "21 Tweaks"; ObjectID = "5po-De-kCi"; */ +"5po-De-kCi.text" = "21 Ajustes"; + +/* Class = "UILabel"; text = "0 / 37"; ObjectID = "NPC-if-gUf"; */ +"NPC-if-gUf.text" = "0 de 37"; + +/* Class = "UILabel"; text = "100 days"; ObjectID = "Vbn-R9-kuu"; */ +"Vbn-R9-kuu.text" = "%d days"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "YGb-Uj-J69"; */ +"YGb-Uj-J69.text" = "marcador de posición"; + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryPagerLayout.strings b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryPagerLayout.strings new file mode 100644 index 00000000..e05381c0 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakEntry/Views/es.lproj/TweakEntryPagerLayout.strings @@ -0,0 +1,6 @@ +/* Class = "UIButton"; normalTitle = "Back to today"; ObjectID = "7XY-Lo-Hwf"; */ +"7XY-Lo-Hwf.normalTitle" = "Volver al día de hoy"; + +/* Class = "UIButton"; normalTitle = "Today"; ObjectID = "iHh-5a-01X"; */ +"iHh-5a-01X.normalTitle" = "Hoy"; + diff --git a/DailyDozen/DailyDozen/ServingsHistory/Controllers/ServingsHistoryViewController.swift b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Controllers/TweakHistoryViewController.swift similarity index 81% rename from DailyDozen/DailyDozen/ServingsHistory/Controllers/ServingsHistoryViewController.swift rename to DailyDozen/DailyDozen/TweakSection/TweakHistory/Controllers/TweakHistoryViewController.swift index 36db074c..3b715a0e 100644 --- a/DailyDozen/DailyDozen/ServingsHistory/Controllers/ServingsHistoryViewController.swift +++ b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Controllers/TweakHistoryViewController.swift @@ -1,43 +1,39 @@ // -// ServingsHistoryViewController.swift +// TweakHistoryViewController.swift // DailyDozen // -// Created by Konstantin Khokhlov on 22.11.2017. -// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// Copyright © 2019 Nutritionfacts.org. All rights reserved. // import UIKit import Charts +import RealmSwift // MARK: - Nested -enum TimeScale: Int { - case day, month, year -} - -class ServingsHistoryBuilder { +//enum TimeScale: Int { +// case day, month, year +//} - // MARK: - Nested - private struct Keys { - static let storyboard = "ServingsHistory" - } +class TweakHistoryBuilder { // MARK: - Methods /// Instantiates and returns the initial view controller for a storyboard. /// /// - Returns: The initial view controller in the storyboard. - static func instantiateController() -> ServingsHistoryViewController { - let storyboard = UIStoryboard(name: Keys.storyboard, bundle: nil) + static func instantiateController() -> TweakHistoryViewController { + let storyboard = UIStoryboard(name: "TweakHistoryLayout", bundle: nil) guard let viewController = storyboard - .instantiateInitialViewController() as? ServingsHistoryViewController - else { fatalError("There should be a controller") } - viewController.title = "Servings History" + .instantiateInitialViewController() as? TweakHistoryViewController + else { fatalError("Did not instantiate `TweakHistoryViewController`") } + viewController.title = NSLocalizedString("historyRecordTweak.heading", comment: "Tweaks History") return viewController } } -class ServingsHistoryViewController: UIViewController { +/// Historic record of daily checkbox tally. +class TweakHistoryViewController: UIViewController { // MARK: - Outlets @IBOutlet private weak var chartView: ChartView! @@ -45,13 +41,14 @@ class ServingsHistoryViewController: UIViewController { @IBOutlet private weak var scaleControl: UISegmentedControl! // MARK: - Properties - private var viewModel: ServingsHistoryViewModel! + private var viewModel: TweakHistoryViewModel! private var currentTimeScale = TimeScale.day private var chartSettings: (year: Int, month: Int)! { didSet { chartView.clear() + let legendTweaksText = NSLocalizedString("historyRecordTweak.legend", comment: "Tweaks") if currentTimeScale == .day { controlPanel.isHidden = false controlPanel.superview?.isHidden = false @@ -74,7 +71,7 @@ class ServingsHistoryViewController: UIViewController { controlPanel.setLabels(month: data.month, year: viewModel.yearName(yearIndex: chartSettings.year)) - chartView.configure(with: data.map, for: currentTimeScale) + chartView.configure(with: data.map, for: currentTimeScale, label: legendTweaksText) } else if currentTimeScale == .month { controlPanel.isHidden = false controlPanel.superview?.isHidden = false @@ -87,11 +84,11 @@ class ServingsHistoryViewController: UIViewController { controlPanel.setLabels(year: data.year) - chartView.configure(with: data.map, for: currentTimeScale) + chartView.configure(with: data.map, for: currentTimeScale, label: legendTweaksText) } else { controlPanel.isHidden = true controlPanel.superview?.isHidden = true - chartView.configure(with: viewModel.fullDataMap(), for: currentTimeScale) + chartView.configure(with: viewModel.fullDataMap(), for: currentTimeScale, label: legendTweaksText) } } } @@ -108,17 +105,15 @@ class ServingsHistoryViewController: UIViewController { private func setViewModel() { let realm = RealmProvider() - let results = realm - .getDozes() - .sorted(byKeyPath: "date") - guard results.count > 0 else { + let trackers: [DailyTracker] = realm.getDailyTrackers() + guard trackers.count > 0 else { controlPanel.isHidden = true scaleControl.isEnabled = false controlPanel.superview?.isHidden = true return } - viewModel = ServingsHistoryViewModel(results) + viewModel = TweakHistoryViewModel(trackers) let lastYearIndex = viewModel.lastYearIndex chartSettings = (lastYearIndex, viewModel.lastMonthIndex(for: lastYearIndex)) @@ -162,20 +157,17 @@ class ServingsHistoryViewController: UIViewController { case .day: let lastYearIndex = viewModel.lastYearIndex chartSettings = (lastYearIndex, viewModel.lastMonthIndex(for: lastYearIndex)) - break case .month: let lastYearIndex = viewModel.lastYearIndex chartSettings = (lastYearIndex, 0) - break case .year: chartSettings = (0, 0) - break } } } // MARK: - IAxisValueFormatter -extension ServingsHistoryViewController: IAxisValueFormatter { +extension TweakHistoryViewController: IAxisValueFormatter { func stringForValue(_ value: Double, axis: AxisBase?) -> String { let labels: [String] diff --git a/DailyDozen/DailyDozen/TweakSection/TweakHistory/Models/TweakHistoryViewModel.swift b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Models/TweakHistoryViewModel.swift new file mode 100644 index 00000000..0cf5bc7a --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Models/TweakHistoryViewModel.swift @@ -0,0 +1,77 @@ +// +// TweakHistoryViewModel.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import RealmSwift + +struct TweakHistoryViewModel { + + // MARK: - Properties + private let report: Report + + var lastYearIndex: Int { + return report.data.count - 1 + } + + // MARK: - Inits + init(_ trackers: [DailyTracker]) { + report = Report(trackers, isDailyDozen: false) + } + + // MARK: - Methods + func lastMonthIndex(for yearIndex: Int) -> Int { + return report.yearlyReport(for: yearIndex).months.count - 1 + } + + func monthData(yearIndex: Int, monthIndex: Int) -> (month: String, map: [Int]) { + let monthReport = report + .yearlyReport(for: yearIndex) + .monthReport(for: monthIndex) + + let month = monthReport.month + let map = monthReport.daily.map { $0.statesCount } + return (month, map) + } + + func yearlyData(yearIndex: Int) -> (year: String, map: [Int]) { + let yearlyReport = report.yearlyReport(for: yearIndex) + let year = String(yearlyReport.year) + let map = yearlyReport.months.map { $0.statesCount } + return (year, map) + } + + func fullDataMap() -> [Int] { + return report + .data + .map { $0.statesCount } + } + + func yearName(yearIndex: Int) -> String { + return String(report.yearlyReport(for: yearIndex).year) + } + + func datesLabels(yearIndex: Int, monthIndex: Int) -> [String] { + return report + .yearlyReport(for: yearIndex) + .monthReport(for: monthIndex) + .daily + .map { "\($0.date.day)" } + } + + func monthsLabels(yearIndex: Int) -> [String] { + return report + .yearlyReport(for: yearIndex) + .months + .map { $0.month } + } + + func fullDataLabels() -> [String] { + return report + .data + .map { String($0.year) } + } +} diff --git a/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/Base.lproj/TweakHistoryLayout.storyboard b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/Base.lproj/TweakHistoryLayout.storyboard new file mode 100644 index 00000000..10a49c69 --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/Base.lproj/TweakHistoryLayout.storyboard @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/es.lproj/TweakHistoryLayout.strings b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/es.lproj/TweakHistoryLayout.strings new file mode 100644 index 00000000..58689c0a --- /dev/null +++ b/DailyDozen/DailyDozen/TweakSection/TweakHistory/Views/es.lproj/TweakHistoryLayout.strings @@ -0,0 +1,18 @@ +/* Class = "UILabel"; text = "2017"; ObjectID = "5eb-qC-Ke5"; */ +"5eb-qC-Ke5.text" = "2017"; + +/* Class = "UILabel"; text = "Month"; ObjectID = "UCg-Rc-mLf"; */ +"UCg-Rc-mLf.text" = "Mes"; + +/* Class = "UISegmentedControl"; vXy-0q-Ind.segmentTitles[0] = "Day"; ObjectID = "vXy-0q-Ind"; */ +"vXy-0q-Ind.segmentTitles[0]" = "Día"; + +/* Class = "UISegmentedControl"; vXy-0q-Ind.segmentTitles[1] = "Month"; ObjectID = "vXy-0q-Ind"; */ +"vXy-0q-Ind.segmentTitles[1]" = "Mes"; + +/* Class = "UISegmentedControl"; vXy-0q-Ind.segmentTitles[2] = "Year"; ObjectID = "vXy-0q-Ind"; */ +"vXy-0q-Ind.segmentTitles[2]" = "Año"; + +/* Class = "UILabel"; text = "Time Scale"; ObjectID = "XdW-g4-ZUO"; */ +"XdW-g4-ZUO.text" = "Escala de tiempo"; + diff --git a/DailyDozen/DailyDozen/Utility/Controllers/UtilityTableViewController.swift b/DailyDozen/DailyDozen/Utility/Controllers/UtilityTableViewController.swift new file mode 100644 index 00000000..bcc75d0d --- /dev/null +++ b/DailyDozen/DailyDozen/Utility/Controllers/UtilityTableViewController.swift @@ -0,0 +1,245 @@ +// +// UtilityTableViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit + +// MARK: - Builder +class UtilityBuilder { + + // MARK: - Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Returns: The initial view controller in the storyboard. + static func instantiateController() -> UIViewController { + let storyboard = UIStoryboard(name: "UtilityLayout", bundle: nil) + guard + let viewController = storyboard.instantiateInitialViewController() + else { fatalError("Did not instantiate `UtilityTableViewController`") } + viewController.title = "Utility" + + return viewController + } +} + +// MARK: - Controller +class UtilityTableViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + } + + // MARK: - Table view data source + + // Simple static storyboard table + + // MARK: - Actions + + @IBAction func doUtilityDBExportDataBtn(_ sender: UIButton) { + doUtilityDBExportData() + } + + /// Presents share services. + private func doUtilityDBExportData() { // see also presentShareServices() { // Backup + let realmMngr = RealmManager() + let backupFilename = realmMngr.csvExport(marker: "export_data") + #if DEBUG + _ = realmMngr.csvExportWeight(marker: "export_weight") + HealthSynchronizer.shared.syncWeightExport(marker: "export_weight_hk") + #endif + + let str = "\(Strings.utilityDbExportMsg): \"\(backupFilename)\"." + + let alert = UIAlertController(title: "", message: str, preferredStyle: .actionSheet) + let okAction = UIAlertAction(title: Strings.utilityConfirmOK, style: .default, handler: nil) + alert.addAction(okAction) + present(alert, animated: true, completion: nil) + + //let activityViewController = UIActivityViewController( + // activityItems: [URL.inDocuments(filename: backupFilename)], + // applicationActivities: nil) + //activityViewController.popoverPresentationController?.sourceView = view + //present(activityViewController, animated: true, completion: nil) + } + + @IBAction func doUtilityDBImportDataBtn(_ sender: UIButton) { + //PopupPickerView.show( + // items: <#T##[String]#>, + // doneBottonCompletion: <#T##PopupPickerView.CompletionBlock?##PopupPickerView.CompletionBlock?##(String?, String?) -> Void#>, + // didSelectCompletion: <#T##PopupPickerView.CompletionBlock?##PopupPickerView.CompletionBlock?##(String?, String?) -> Void#>, + // cancelBottonCompletion: <#T##PopupPickerView.CompletionBlock?##PopupPickerView.CompletionBlock?##(String?, String?) -> Void#> + //) + + //PopupPickerView.show( + // items: ["item1", "item2", "item3"], + // itemIds: ["id1", "id2", "id3"], + // selectedValue: "item3", + // doneBottonCompletion: { (item: String?, index: String?) in + // LogService.shared.debug("done", item ?? "nil", index ?? "nil")}, + // didSelectCompletion: { (item: String?, index: String?) in + // LogService.shared.debug("selection", item ?? "nil", index ?? "nil") }, + // cancelBottonCompletion: { (item: String?, index: String?) in + // LogService.shared.debug("cancelled", item ?? "nil", index ?? "nil") } + //) + } + + @IBAction func doUtilitySettingsClearBtn(_ sender: UIButton) { + let alert = UIAlertController(title: "", message: Strings.utilitySettingsClearMsg, preferredStyle: .actionSheet) + let cancelAction = UIAlertAction(title: Strings.utilityConfirmCancel, style: .cancel, handler: nil) + alert.addAction(cancelAction) + let clearAction = UIAlertAction(title: Strings.utilityConfirmClear, style: .destructive) { (_: UIAlertAction) -> Void in + self.doUtilitySettingsClear() + } + alert.addAction(clearAction) + present(alert, animated: true, completion: nil) + } + + func doUtilitySettingsClear() { + let defaults = UserDefaults.standard + defaults.set(nil, forKey: SettingsKeys.reminderCanNotify) + defaults.set(nil, forKey: SettingsKeys.reminderHourPref) + defaults.set(nil, forKey: SettingsKeys.reminderMinutePref) + defaults.set(nil, forKey: SettingsKeys.reminderSoundPref) + defaults.set(nil, forKey: SettingsKeys.imgID) + defaults.set(nil, forKey: SettingsKeys.requestID) + defaults.set(nil, forKey: SettingsKeys.unitsTypePref) + defaults.set(nil, forKey: SettingsKeys.unitsTypeToggleShowPref) + defaults.set(nil, forKey: SettingsKeys.show21TweaksPref) + defaults.set(nil, forKey: SettingsKeys.hasSeenFirstLaunch) + } + + @IBAction func doUtilitySettingsShowBtn(_ sender: UIButton) { + doUtilitySettingsShow() + } + + func doUtilitySettingsShow() { + var str = "" + str.append(contentsOf: "reminderCanNotify: ") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.reminderCanNotify) ?? "nil")\n") + + str.append(contentsOf: "reminderHourPref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.reminderHourPref) ?? "nil")\n") + + str.append(contentsOf: "reminderMinutePref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.reminderMinutePref) ?? "nil")\n") + + str.append(contentsOf: "reminderSoundPref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.reminderSoundPref) ?? "nil")\n") + + //str.append(contentsOf: "imgID") + //str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.imgID) ?? "nil")\n") + + //str.append(contentsOf: "requestID") + //str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.requestID) ?? "nil")\n") + + str.append(contentsOf: "unitsTypePref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.unitsTypePref) ?? "nil")\n") + + str.append(contentsOf: "unitsTypeToggleShowPref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.unitsTypeToggleShowPref) ?? "nil")\n") + + str.append(contentsOf: "show21TweaksPref") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.show21TweaksPref) ?? "nil")\n") + + str.append(contentsOf: "hasSeenFirstLaunch") + str.append(contentsOf: ": \(UserDefaults.standard.object(forKey: SettingsKeys.hasSeenFirstLaunch) ?? "nil")\n") + + #if DEBUG + LogService.shared.debug( + """ + UtilityTableViewController doUtilitySettingsShow()…\n + ••• UserDefaults Values •••\n + \(str) + """ + ) + #endif + + let alert = UIAlertController(title: "", message: str, preferredStyle: .actionSheet) + let okAction = UIAlertAction(title: Strings.utilityConfirmOK, style: .default, handler: nil) + alert.addAction(okAction) + present(alert, animated: true, completion: nil) + } + + // MARK: - Test Database + + @IBAction func doUtilityTestClearHistoryBtn(_ sender: UIButton) { + let alert = UIAlertController(title: "", message: Strings.utilityTestHistoryClearMsg, preferredStyle: .actionSheet) + let cancelAction = UIAlertAction(title: Strings.utilityConfirmCancel, style: .cancel, handler: nil) + alert.addAction(cancelAction) + let clearAction = UIAlertAction(title: Strings.utilityConfirmClear, style: .destructive) { (_: UIAlertAction) -> Void in + //self.doUtilityTestClearHistoryMigrationsChain() + DatabaseBuiltInTest.shared.doClearAllDataInMigrationChainBIT() + } + alert.addAction(clearAction) + present(alert, animated: true, completion: nil) + } + + /// Note: When writing a large number of data entries AND the database is open + /// in the Realm browser is open, then some value(s) may not be written. + /// Do not have the Realm browser open when writing data in simulator to + /// avoid this is situation. The root cause of this issue is unknown. + @IBAction func doUtilityTestGenerateHistoryBtn(_ sender: UIButton) { + // half month + DatabaseBuiltInTest.shared.doGenerateDBHistoryBIT(numberOfDays: 15, defaultDB: true) + } + + @IBAction func doUtilityTestGenerateLegacyBtn(_ sender: UIButton) { + DatabaseBuiltInTest.shared.doGenerateDBLegacyDataBIT() + } + + // MARK: - UI + + private struct Strings { // :NYI:LOCALIZE: localize utility strings + static let utilityConfirmCancel = "Cancel" + static let utilityConfirmClear = "Clear" + static let utilityConfirmOK = "OK" + static let utilityDbExportMsg = "Exported file named " + static let utilitySettingsClearMsg = "Clear (erase) all settings?\n\nThis cannot be undone." + static let utilityTestHistoryClearMsg = "Export the history data to create an importable backup file before clearing the history data. The clear action cannot be undone.\n\nClear (erase) all history data from the database?" + } + + private func alertTwoButton() { + let alertController = UIAlertController(title: "Default Style", message: "A standard alert.", preferredStyle: .alert) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action: UIAlertAction) -> Void in + LogService.shared.debug( + "••CANCEL•• UtilityTableViewController alertTwoButton() \(action)" + ) + } + alertController.addAction(cancelAction) + + let okAction = UIAlertAction(title: "OK", style: .default) { (action: UIAlertAction) -> Void in + LogService.shared.debug( + "••OK•• UtilityTableViewController alertTwoButton() \(action)" + ) + } + alertController.addAction(okAction) + + let destroyAction = UIAlertAction(title: "Destroy", style: .destructive) { (action: UIAlertAction) -> Void in + LogService.shared.debug( + "••DESTROY•• UtilityTableViewController alertTwoButton() \(action)" + ) + } + alertController.addAction(destroyAction) + + self.present(alertController, animated: true, completion: nil) // (() -> Void)? + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/DailyDozen/DailyDozen/Utility/Views/PopupPickerView.swift b/DailyDozen/DailyDozen/Utility/Views/PopupPickerView.swift new file mode 100644 index 00000000..1633368b --- /dev/null +++ b/DailyDozen/DailyDozen/Utility/Views/PopupPickerView.swift @@ -0,0 +1,249 @@ +// +// UtilityPickerView.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// +// swiftlint:disable function_body_length + +import UIKit + +class PopupPickerView: NSObject, UIPickerViewDelegate, UIPickerViewDataSource { // UIPickerView + + // MARK: - UIPickerView + + /* UIPickerView + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + + //Theme colors + var itemTextColor = UIColor.black + var backgroundColor = UIColor.orange.withAlphaComponent(0.5) + var toolBarColor = UIColor.blue + var font = UIFont.systemFont(ofSize: 16) + + private static var shared: PopupPickerView! + var bottomAnchorOfPickerView: NSLayoutConstraint! + var heightOfPickerView: CGFloat = UIDevice.current.userInterfaceIdiom == .pad ? 160 : 120 + var heightOfToolbar: CGFloat = UIDevice.current.userInterfaceIdiom == .pad ? 50 : 40 + + var dataSource: (items: [String]?, itemIds: [String]?) + + typealias CompletionBlock = (_ item: String?, _ id: String?) -> Void + var didSelectCompletion: CompletionBlock? + var doneBottonCompletion: CompletionBlock? + var cancelBottonCompletion: CompletionBlock? + + lazy var pickerView: UIPickerView = { + let pv = UIPickerView() + pv.translatesAutoresizingMaskIntoConstraints = false + pv.delegate = self + pv.dataSource = self + pv.showsSelectionIndicator = true + pv.backgroundColor = self.backgroundColor + return pv + }() + + lazy var disablerView: UIView={ + let view = UIView() + view.backgroundColor = UIColor.black.withAlphaComponent(0.1) + view.translatesAutoresizingMaskIntoConstraints = false + view.alpha = 0 + return view + }() + + lazy var tooBar: UIView={ + let view = UIView() + view.backgroundColor = self.toolBarColor + view.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(buttonDone) + buttonDone.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true + buttonDone.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true + buttonDone.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true + buttonDone.widthAnchor.constraint(equalToConstant: 65).isActive = true + + view.addSubview(buttonCancel) + buttonCancel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true + buttonCancel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true + buttonCancel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true + buttonCancel.widthAnchor.constraint(equalToConstant: 65).isActive = true + + return view + }() + + lazy var buttonDone: UIButton={ + let button = UIButton(type: .system) + button.setTitle("Done", for: .normal) + button.tintColor = self.itemTextColor + button.titleLabel?.font = self.font + button.translatesAutoresizingMaskIntoConstraints = false + button.titleLabel?.adjustsFontSizeToFitWidth = true + button.addTarget(self, action: #selector(self.buttonDoneClicked), for: .touchUpInside) + return button + }() + + lazy var buttonCancel: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Cancel", for: .normal) + button.tintColor = self.itemTextColor + button.titleLabel?.font = self.font + button.translatesAutoresizingMaskIntoConstraints = false + button.titleLabel?.adjustsFontSizeToFitWidth = true + button.addTarget(self, action: #selector(self.buttonCancelClicked), for: .touchUpInside) + return button + }() + + static func show(items: [String], itemIds: [String]? = nil, selectedValue: String? = nil, doneBottonCompletion: CompletionBlock?, didSelectCompletion: CompletionBlock?, cancelBottonCompletion: CompletionBlock?) { + + if PopupPickerView.shared == nil { + shared = PopupPickerView() + } else { + return + } + + if let appDelegate = UIApplication.shared.delegate as? AppDelegate, + let keyWindow = appDelegate.window { + + shared.cancelBottonCompletion = cancelBottonCompletion + shared.didSelectCompletion = didSelectCompletion + shared.doneBottonCompletion = doneBottonCompletion + shared.dataSource.items = items + + if let idsVal = itemIds, items.count == idsVal.count { //ids can not be less or more than items + shared?.dataSource.itemIds = itemIds + } + + shared?.heightOfPickerView += shared.heightOfToolbar + + keyWindow.addSubview(shared.disablerView) + shared.disablerView.leftAnchor.constraint(equalTo: keyWindow.leftAnchor, constant: 0).isActive = true + shared.disablerView.rightAnchor.constraint(equalTo: keyWindow.rightAnchor, constant: 0).isActive = true + shared.disablerView.topAnchor.constraint(equalTo: keyWindow.topAnchor, constant: 0).isActive = true + shared.disablerView.bottomAnchor.constraint(equalTo: keyWindow.bottomAnchor, constant: 0).isActive = true + + shared.disablerView.addSubview(shared.pickerView) + shared.pickerView.leftAnchor.constraint(equalTo: shared.disablerView.leftAnchor, constant: 0).isActive = true + shared.pickerView.rightAnchor.constraint(equalTo: shared.disablerView.rightAnchor, constant: 0).isActive = true + shared.pickerView.heightAnchor.constraint(equalToConstant: shared.heightOfPickerView).isActive = true + shared.bottomAnchorOfPickerView = shared.pickerView.topAnchor.constraint(equalTo: shared.disablerView.bottomAnchor, constant: 0) + shared.bottomAnchorOfPickerView.isActive = true + + shared.disablerView.addSubview(shared.tooBar) + shared.tooBar.heightAnchor.constraint(equalToConstant: shared.heightOfToolbar).isActive = true + shared.tooBar.leftAnchor.constraint(equalTo: shared.disablerView.leftAnchor, constant: 0).isActive = true + shared.tooBar.rightAnchor.constraint(equalTo: shared.disablerView.rightAnchor, constant: 0).isActive = true + shared.tooBar.bottomAnchor.constraint(equalTo: shared.pickerView.topAnchor, constant: 0).isActive = true + + keyWindow.layoutIfNeeded() + + if let selectedVal = selectedValue { + for (index, itemName) in items.enumerated() { + if itemName.lowercased() == selectedVal.lowercased() { + shared.pickerView.selectRow(index, inComponent: 0, animated: false) + break + } + } + } + + shared.bottomAnchorOfPickerView.constant = -shared.heightOfPickerView + + UIView.animate( + withDuration: 0.5, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 1, + options: .curveEaseOut, + animations: { + keyWindow.layoutIfNeeded() + shared.disablerView.alpha = 1}, + completion: { (_: Bool) in } + ) + + } + } + + // MARK: - UIPickerViewDataSource + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + if let count = dataSource.items?.count { + return count + } + return 0 + } + + // MARK: - UIPickerViewDelegate + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + if let names = dataSource.items { + return names[row] + } + return nil + } + + func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { + if let names = dataSource.items { + let item = names[row] + return NSAttributedString(string: item, attributes: [NSAttributedString.Key.foregroundColor: itemTextColor, NSAttributedString.Key.font: font]) + } + return nil + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + var itemName: String? + var id: String? + + if let names = dataSource.items { + itemName = names[row] + } + if let ids = dataSource.itemIds { + id = ids[row] + } + self.didSelectCompletion?(itemName, id) + } + + @objc func buttonDoneClicked() { + self.hidePicker(handler: doneBottonCompletion) + } + + @objc func buttonCancelClicked() { + self.hidePicker(handler: cancelBottonCompletion) + } + + func hidePicker(handler: CompletionBlock?) { + var itemName: String? + var id: String? + let row = self.pickerView.selectedRow(inComponent: 0) + if let names = dataSource.items { + itemName = names[row] + } + if let ids = dataSource.itemIds { + id = ids[row] + } + handler?(itemName, id) + + bottomAnchorOfPickerView.constant = self.heightOfPickerView + UIView.animate( + withDuration: 0.7, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 1, + options: .curveEaseOut, + animations: { + self.disablerView.window!.layoutIfNeeded() + self.disablerView.alpha = 0}, + completion: { (_: Bool) in + self.disablerView.removeFromSuperview() + PopupPickerView.shared = nil} + ) + } + +} diff --git a/DailyDozen/DailyDozen/Utility/Views/UtilityLayout.storyboard b/DailyDozen/DailyDozen/Utility/Views/UtilityLayout.storyboard new file mode 100644 index 00000000..2fbff3a6 --- /dev/null +++ b/DailyDozen/DailyDozen/Utility/Views/UtilityLayout.storyboard @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryPagerViewController.swift b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryPagerViewController.swift new file mode 100644 index 00000000..2288d260 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryPagerViewController.swift @@ -0,0 +1,143 @@ +// +// WeightEntryPagerViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import UIKit +import SimpleAnimation + +// MARK: - Builder + +class WeightEntryPagerBuilder { + + // MARK: - Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Returns: The initial view controller in the storyboard. + static func instantiateController() -> UIViewController { + let storyboard = UIStoryboard(name: "WeightEntryPagerLayout", bundle: nil) + guard + let viewController = storyboard.instantiateInitialViewController() + else { fatalError("Did not instantiate `WeightEntryPagerViewController`") } + + return viewController + } +} + +// MARK: - Controller +class WeightEntryPagerViewController: UIViewController { + + // MARK: - Properties + private var currentDate = Date() { + didSet { + if currentDate.isInCurrentDayWith(Date()) { + backButton.superview?.isHidden = true + dateButton.setTitle("Today", for: .normal) + } else { + backButton.superview?.isHidden = false + dateButton.setTitle(datePicker.date.dateString(for: .long), for: .normal) + } + } + } + + // MARK: - Outlets + @IBOutlet private weak var dateButton: UIButton! { + didSet { + dateButton.layer.borderWidth = 1 + dateButton.layer.borderColor = dateButton.titleColor(for: .normal)?.cgColor + dateButton.layer.cornerRadius = 5 + } + } + @IBOutlet private weak var datePicker: UIDatePicker! { + didSet { + datePicker.maximumDate = Date() + } + } + + @IBOutlet private weak var backButton: UIButton! + + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + title = "Weight" + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + } + + // MARK: - Methods + /// Updates UI for the current date. + /// + /// - Parameter date: The current date. + func updateDate(_ date: Date) { + currentDate = date + datePicker.setDate(date, animated: false) + + guard let viewController = children.first as? WeightEntryViewController else { return } + viewController.view.fadeOut().fadeIn() + viewController.setViewModel(viewDate: currentDate) + } + + // MARK: - Actions + @IBAction private func dateButtonPressed(_ sender: UIButton) { + datePicker.isHidden = false + dateButton.isHidden = true + } + + @IBAction private func dateChanged(_ sender: UIDatePicker) { + dateButton.isHidden = false + datePicker.isHidden = true + currentDate = datePicker.date + + guard let viewController = children.first as? WeightEntryViewController else { return } + viewController.view.fadeOut().fadeIn() + viewController.setViewModel(viewDate: datePicker.date) + } + + @IBAction private func viewSwipped(_ sender: UISwipeGestureRecognizer) { + let interval = sender.direction == .left ? -1 : 1 + let currentDate = datePicker.date.adding(.day, value: interval) + + let today = Date() + + guard let date = currentDate, date <= today else { return } + + datePicker.setDate(date, animated: false) + + self.currentDate = datePicker.date + + guard let viewController = children.first as? WeightEntryViewController else { return } + + if sender.direction == .left { + viewController.view.slideOut(x: -view.frame.width).slideIn(x: view.frame.width) + } else { + viewController.view.slideOut(x: view.frame.width).slideIn(x: -view.frame.width) + } + + viewController.setViewModel(viewDate: datePicker.date) + } + + @IBAction private func backButtonPressed(_ sender: UIButton) { + updateDate(Date()) + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryViewController.swift b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryViewController.swift new file mode 100644 index 00000000..3187650d --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Controllers/WeightEntryViewController.swift @@ -0,0 +1,377 @@ +// +// WeightEntryViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// +// swiftlint :ENABLED: disable file_length +// swiftlint :ENABLED: disable function_body_length + +import UIKit +import HealthKit + +class WeightEntryViewController: UIViewController { + + // MARK: - Outlets + // Text Edit + @IBOutlet weak var timeAMInput: UITextField! + @IBOutlet weak var timePMInput: UITextField! + @IBOutlet weak var weightAM: UITextField! + @IBOutlet weak var weightPM: UITextField! + + @IBOutlet weak var weightAMLabel: UILabel! + @IBOutlet weak var weightPMLabel: UILabel! + + // Buttons + @IBOutlet weak var clearWeightAMButton: UIButton! + @IBOutlet weak var clearWeightPMButton: UIButton! + + // MARK: - Properties + private let realm = RealmProvider() + private var currentViewDateWeightEntry = Date() + private var timePickerAM: UIDatePicker? + private var timePickerPM: UIDatePicker? + + var pidAM: String { + return "\(currentViewDateWeightEntry.datestampKey).am" + } + + var pidPM: String { + return "\(currentViewDateWeightEntry.datestampKey).pm" + } + + var pidWeight: String { + return "\(currentViewDateWeightEntry.datestampKey).tweakWeightTwice" + } + + // MARK: - Data Actions + + func clearIBWeight(ampm: DataWeightType) { + if ampm == .am { + timeAMInput.text = "" + weightAM.text = "" + } else { + timePMInput.text = "" + weightPM.text = "" + } + HealthSynchronizer.shared.syncWeightClear(date: currentViewDateWeightEntry, ampm: ampm) + updateWeightDataCount() + } + + /// Save weight values from InterfaceBuilder (IB) fields + func saveIBWeight(ampm: DataWeightType) { + let datestampKey = currentViewDateWeightEntry.datestampKey + + if + let timeText = ampm == .am ? timeAMInput.text : timePMInput.text, + let weightText = ampm == .am ? weightAM.text : weightPM.text, + let date = Date(healthkit: "\(datestampKey) \(timeText)"), + var weight = Double(weightText), + weight > 5.0 { + + // Update local data + if SettingsManager.isImperial() { + weight = weight / 2.2046 // kg = lbs * 2.2046 + } + HealthSynchronizer.shared.syncWeightPut(date: date, ampm: ampm, kg: weight) + } + // Update local counter + updateWeightDataCount() + } + + /// showIBWeight() when "BodyMassDataAvailable" notification occurs + @objc func showIBWeight(notification: Notification) { + LogService.shared.debug("•HK• WeightEntryViewController showIBWeight") + guard let healthRecord = notification.object as? HealthWeightRecord else { + return + } + + let weightToShow = healthRecord.getIBWeightToShow() + if healthRecord.ampm == .am { + timeAMInput.text = weightToShow.time + weightAM.text = weightToShow.weight + } else { + timePMInput.text = weightToShow.time + weightPM.text = weightToShow.weight + } + } + + /// Notification: "NoticeChangedUnitsType" + @objc func changedUnitsType(notification: Notification) { + guard let isImperial = notification.object as? Bool else { + return + } + + // Unit Type + if isImperial { + weightAMLabel.text = "lbs." // :TBD:ToBeLocalized: + weightPMLabel.text = "lbs." // :TBD:ToBeLocalized: + if let txt = weightAM.text { + weightAM.text = SettingsManager.convertKgToLbs(txt) + } + if let txt = weightPM.text { + weightPM.text = SettingsManager.convertKgToLbs(txt) + } + } else { + weightAMLabel.text = "kg" // :TBD:ToBeLocalized: + weightPMLabel.text = "kg" // :TBD:ToBeLocalized: + if let txt = weightAM.text { + weightAM.text = SettingsManager.convertLbsToKg(txt) + } + if let txt = weightPM.text { + weightPM.text = SettingsManager.convertLbsToKg(txt) + } + } + } + + // MARK: - UI Actions + @IBAction func clearWeightAMButtonPressed(_ sender: Any) { + // :NYI: confirm clear & delete + view.endEditing(true) + clearIBWeight(ampm: .am) + } + + @IBAction func clearWeightPMButtonPressed(_ sender: Any) { + // :NYI: confirm clear & delete + view.endEditing(true) + clearIBWeight(ampm: .pm) + } + + /// Update "weight twice daily" tweak tracker + /// for current date view using database values + private func updateWeightDataCount() { + let recordAM = realm.getDBWeight(date: currentViewDateWeightEntry, ampm: .am) + let recordPM = realm.getDBWeight(date: currentViewDateWeightEntry, ampm: .pm) + var count = 0 + if recordAM != nil { + count += 1 + } + if recordPM != nil { + count += 1 + } + + realm.saveCount(count, date: currentViewDateWeightEntry, countType: .tweakWeightTwice) + } + + // Note: call once upon entry from tweaks checklist or history + // MARK: - UIViewController + override func viewDidLoad() { + super.viewDidLoad() + + weightPM.delegate = self + weightAM.delegate = self + timeAMInput.delegate = self + timePMInput.delegate = self + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + appDelegate.realmDelegate = self + + // AM Morning + // :TBD: timePickerAM = UIDatePicker() // may need min-max contraints? + timePickerAM = UIDatePicker() + timePickerAM?.datePickerMode = .time + timePickerAM?.addTarget(self, action: #selector(WeightEntryViewController.timeChangedAM(timePicker:)), for: .valueChanged) + timeAMInput.inputView = timePickerAM // assign initial value + + // PM Evening + timePickerPM = UIDatePicker() + timePickerPM?.datePickerMode = .time + timePickerPM?.addTarget(self, action: #selector(WeightEntryViewController.timeChangedPM(timePicker:)), for: .valueChanged) + timePMInput.inputView = timePickerPM + + setViewModel(viewDate: Date()) // today + + // Unit Type + if SettingsManager.isImperial() { + weightAMLabel.text = "lbs." // :TBD:ToBeLocalized: + weightPMLabel.text = "lbs." // :TBD:ToBeLocalized: + } else { + weightAMLabel.text = "kg" // :TBD:ToBeLocalized: + weightPMLabel.text = "kg" // :TBD:ToBeLocalized: + } + + // + let tapGesture = UITapGestureRecognizer( + target: self, + action: #selector(WeightEntryViewController.viewTapped(gestureRecognizer:))) + view.addGestureRecognizer(tapGesture) + + NotificationCenter.default.addObserver( + self, + selector: #selector(changedUnitsType(notification:)), + name: Notification.Name(rawValue: "NoticeChangedUnitsType"), + object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(showIBWeight(notification:)), + name: Notification.Name(rawValue: "BodyMassDataAvailable"), + object: nil) + } + + override func viewWillAppear(_ animated: Bool) { + LogService.shared.debug("WeightEntryViewController viewWillAppear") + super.viewWillAppear(animated) + setViewModel(viewDate: self.currentViewDateWeightEntry) + } + + /// Return to previous screen. Not invoked by date pager. + override func viewWillDisappear(_ animated: Bool) { + LogService.shared.debug("WeightEntryViewController viewWillDisappear") + super.viewWillDisappear(animated) + // Update stored values + saveIBWeight(ampm: .am) + saveIBWeight(ampm: .pm) + } + + func getTimeNow() -> String { + let dateNow = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm a" + let timeNow = dateFormatter.string(from: dateNow) + return timeNow + } + + // MARK: - Methods + /// Sets a view model for the current date. + /// + @objc func viewTapped(gestureRecognizer: UITapGestureRecognizer) { + view.endEditing(true) + } + /// + @objc func timeChangedAM(timePicker: UIDatePicker) { + + let dateFormatter = DateFormatter() + let min = dateFormatter.date(from: "12:00") //creating min time + let max = dateFormatter.date(from: "11:59") + dateFormatter.dateFormat = "hh:mm a" + timePicker.minimumDate = min + timePicker.maximumDate = max + timeAMInput.text = dateFormatter.string(from: timePicker.date) + //view.endEditing(true) + } + + @objc func timeChangedPM(timePicker: UIDatePicker) { + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm a" + timePMInput.text = dateFormatter.string(from: timePicker.date) + view.endEditing(true) + } + + /// Set the current date. + /// + /// Note: updated by pager. + /// + /// - Parameter item: sets the current date. + func setViewModel(viewDate: Date) { + LogService.shared.debug("•HK• WeightEntryViewController setViewModel \(viewDate.datestampKey)") + // Update or create stored values from the current view + saveIBWeight(ampm: .am) + saveIBWeight(ampm: .pm) + + // Switch to new date + self.currentViewDateWeightEntry = viewDate + + let recordAM = HealthSynchronizer.shared.syncWeightToShow(date: viewDate, ampm: .am) + timeAMInput.text = recordAM.time + weightAM.text = recordAM.weight + timePickerAM?.setDate(viewDate, animated: false) + + let recordPM = HealthSynchronizer.shared.syncWeightToShow(date: viewDate, ampm: .pm) + timePMInput.text = recordPM.time + weightPM.text = recordPM.weight + timePickerPM?.setDate(viewDate, animated: false) + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + // weightAM.endEditing(true) + view.endEditing(true) + } + +} + +// MARK: - UITextFieldDelegate + +extension WeightEntryViewController: UIPickerViewDelegate { + // pickerView +} + +// MARK: - UITextFieldDelegate + +extension WeightEntryViewController: UITextFieldDelegate { + // :1: + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + //LogService.shared.debug("textFieldShouldBeginEditing") + + // :===: should solve initial picker registration + if textField.text == nil || textField.text!.isEmpty { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm a" + if textField == timeAMInput { + timeAMInput.text = dateFormatter.string(from: Date()) + } + if textField == timePMInput { + timePMInput.text = dateFormatter.string(from: Date()) + } + } + return true // return NO to disallow editing. + } + // :2: + func textFieldDidBeginEditing(_ textField: UITextField) { + //LogService.shared.debug("textFieldDidBeginEditing") + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + //LogService.shared.debug("textFieldShouldReturn") + //weightAM.endEditing(true) + view.endEditing(true) + + //textField.resignFirstResponder() + return true + } + + // :3: + func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + //LogService.shared.debug("textFieldShouldEndEditing") + if textField.text != "" { + return true + } else { + return false + } + } + + // :4: + func textFieldDidEndEditing(_ textField: UITextField) { + //this is where you might add other code + //LogService.shared.debug("textFieldDidEndEditing") + if let weight = weightAM.text { + LogService.shared.debug("•HK• WeightEntryViewController textFieldDidEndEditing \(weight)") + } + } +} + +// MARK: - RealmDelegate + +extension WeightEntryViewController: RealmDelegate { + func didUpdateFile() { + navigationController?.popViewController(animated: false) + } +} + +// Helper function inserted by Swift 4.2 migrator. +private func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] { + return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)}) +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryLayout.storyboard b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryLayout.storyboard new file mode 100644 index 00000000..b98ce803 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryLayout.storyboard @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryPagerLayout.storyboard b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryPagerLayout.storyboard new file mode 100644 index 00000000..7fa070fc --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/Base.lproj/WeightEntryPagerLayout.storyboard @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryLayout.strings b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryLayout.strings new file mode 100644 index 00000000..678b9f1c --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryLayout.strings @@ -0,0 +1,30 @@ +/* Class = "UIButton"; normalTitle = "Clear"; ObjectID = "7l0-os-Oqt"; */ +"7l0-os-Oqt.normalTitle" = "Borrar"; + +/* Class = "UILabel"; text = "Evening (right before bed)"; ObjectID = "77g-xa-ali"; */ +"77g-xa-ali.text" = "Tarde (justo antes de acostarse)"; + +/* Class = "UIButton"; normalTitle = "Save"; ObjectID = "Eai-xa-KOK"; */ +"Eai-xa-KOK.normalTitle" = "Guardar"; + +/* Class = "UILabel"; text = "Time"; ObjectID = "FBJ-Im-BZk"; */ +"FBJ-Im-BZk.text" = "Tiempo"; + +/* Class = "UILabel"; text = "Time "; ObjectID = "g3G-kp-d2S"; */ +"g3G-kp-d2S.text" = "Tiempo"; + +/* Class = "UIButton"; normalTitle = "Save"; ObjectID = "IeK-uC-yhe"; */ +"IeK-uC-yhe.normalTitle" = "Guardar"; + +/* Class = "UIButton"; normalTitle = "Clear"; ObjectID = "NRG-eE-YhG"; */ +"NRG-eE-YhG.normalTitle" = "Borrar"; + +/* Class = "UILabel"; text = "lbs."; ObjectID = "Tgz-om-dEk"; */ +"Tgz-om-dEk.text" = "lbs"; + +/* Class = "UILabel"; text = "Morning (upon waking)"; ObjectID = "xCp-N8-5Uc"; */ +"xCp-N8-5Uc.text" = "Mañana (al despertar)"; + +/* Class = "UILabel"; text = "lbs."; ObjectID = "zV0-lA-zHj"; */ +"zV0-lA-zHj.text" = "lbs"; + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryPagerLayout.strings b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryPagerLayout.strings new file mode 100644 index 00000000..9ae55fc3 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightEntry/Views/es.lproj/WeightEntryPagerLayout.strings @@ -0,0 +1,6 @@ +/* Class = "UIButton"; normalTitle = "Today"; ObjectID = "6FY-X2-BdZ"; */ +"6FY-X2-BdZ.normalTitle" = "Hoy"; + +/* Class = "UIButton"; normalTitle = "Back to today"; ObjectID = "OIQ-oh-3QN"; */ +"OIQ-oh-3QN.normalTitle" = "Volver al día de hoy"; + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthManager.swift b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthManager.swift new file mode 100644 index 00000000..8d1c8f76 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthManager.swift @@ -0,0 +1,357 @@ +// +// HealthManager.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import HealthKit + +// If the user denied the authorization request, +// HealthKit only provides information written by the app and nothing else. +// https://developer.apple.com/documentation/HealthKit/HKAuthorizationStatus + +// HKQuantityType > HKSampleType > HKObjectType + +/// HealthManager provides read, save, delete layer for HealthKit. +class HealthManager { + + public static let shared = HealthManager() + + public let hkHealthStore = HKHealthStore() + + public func isAuthorized() -> Bool { + + guard HKHealthStore.isHealthDataAvailable() else { + return false + } + + if let bodymassQType = HKQuantityType.quantityType(forIdentifier: .bodyMass) { + let healthStore = HKHealthStore() + let authorizationStatus = healthStore.authorizationStatus(for: bodymassQType) + + switch authorizationStatus { + case HKAuthorizationStatus.sharingAuthorized: return true + case .sharingDenied: return false + default: return false + } + } + return false + } + + public func requestPermissions() { + let hkTypesToRead: Set = [ + HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, + ] + + let hkTypesToWrite: Set = [ + HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, + ] + + hkHealthStore.requestAuthorization( + toShare: hkTypesToWrite, + read: hkTypesToRead, + completion: { (success, error) in + if success { + LogService.shared.debug("•HK• HealthManager Authorization success") + } else { + LogService.shared.error("•HK• HealthManager Authorization error: \(String(describing: error?.localizedDescription))") + } + }) + } + + public func buildPredicate(date: Date, ampm: DataWeightType) -> NSPredicate { + let baseDate = Calendar.current.startOfDay(for: date) + var dateComponentsStart = DateComponents() + dateComponentsStart.hour = ampm == .am ? 0 : 12 + let startDate = Calendar.current.date(byAdding: dateComponentsStart, to: baseDate)! + var dateComponentsEnd = DateComponents() + dateComponentsEnd.hour = ampm == .am ? 12 : 24 + let endDate = Calendar.current.date(byAdding: dateComponentsEnd, to: baseDate)! + + // Weight sample "start" time .greater.than.or.equal.to. query target start time, and + // weight sample "end" time .less.than. query target end time. + let options: HKQueryOptions = [.strictStartDate] + + let predicate: NSPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: options) + return predicate + } + + public func buildPredicate(fromDate: Date, thruDate: Date) -> NSPredicate { + let dateIn = Calendar.current.startOfDay(for: fromDate) + var dateComponentsStart = DateComponents() + dateComponentsStart.hour = 0 + let startDate = Calendar.current.date(byAdding: dateComponentsStart, to: dateIn)! + + let baseOut = Calendar.current.startOfDay(for: thruDate) + var dateComponentsEnd = DateComponents() + dateComponentsEnd.hour = 24 + let endDate = Calendar.current.date(byAdding: dateComponentsEnd, to: baseOut)! + + // Weight sample "start" time .greater.than.or.equal.to. query target start time, and + // weight sample "end" time .less.than. query target end time. + let options: HKQueryOptions = [.strictStartDate] + + let predicate: NSPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: options) + return predicate + } + + // MARK: - READ + + /// Read HealthKit data and send update notification. + public func readHKWeight(date: Date, ampm: DataWeightType) { + LogService.shared.debug("•HK• WeightEntryViewController readHKWeight date: \(date.datestampyyyyMMddHHmmss) ampm: \(ampm.typeKey)") + let predicate = buildPredicate(date: date, ampm: ampm) + + // AM: ascending order TRUE so earliest AM time lists first. + // PM: ascending order FALSE so latest PM time lists first. + let ascending = ampm == .am + + readHKWeight(predicate: predicate, ascending: ascending, passthru: ampm, handler: readResultsDisplay) + } + + public func readHKWeight(key: String, values: [Any]? = nil) { + let predicate: NSPredicate! + if let values = values { + predicate = HKQuery.predicateForObjects(withMetadataKey: key, allowedValues: values) + } else { + predicate = HKQuery.predicateForObjects(withMetadataKey: key) + } + + readHKWeight(predicate: predicate, ascending: true, handler: readResultsLog) + } + + // Possible additional predication: + // let p0: NSPredicate = HKQuery.predicateForSamples(...) + // let p1 = HKQuery.predicateForObjects(...) + // let p2 = NSCompoundPredicate(notPredicateWithSubpredicate: p1) + // let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [p0, p1]) + + public func readHKWeight( + predicate: NSPredicate?, + ascending: Bool, + passthru: Any? = nil, + handler: @escaping (Any?, HKSampleQuery, [HKSample]?, Error?) -> Void) { + + let bodymassQType = HKQuantityType.quantityType(forIdentifier: .bodyMass)! + let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending) + + let sampleQuery = HKSampleQuery( + sampleType: bodymassQType, // HKSampleType + predicate: predicate, // NSPredicate? + limit: HKObjectQueryNoLimit, // Int + sortDescriptors: [sortDescriptor] // [NSSortDescriptor]? + ) { (query: HKSampleQuery, samples: [HKSample]?, error: Error?) in + handler(passthru, query, samples, error) + } + + self.hkHealthStore.execute(sampleQuery as HKSampleQuery) + } + + private func readResultsDisplay(passthru: Any?, query: HKSampleQuery, samples: [HKSample]?, error: Error?) { + + if let error = error { + LogService.shared.error("readHKResultDisplay \"\(error.localizedDescription)\"") + } + + guard let ampm = passthru as? DataWeightType else { + LogService.shared.error("readHKResultDisplay expected an 'AMPM' value") + return + } + + if let hkQuantitySamples = samples as? [HKQuantitySample] { + let r = HealthWeightRecord(ampm: ampm, hkWeightSamples: hkQuantitySamples) + LogService.shared.debug("•HK• WeightEntryViewController HealthWeightRecord\n\(r.toString())") + + DispatchQueue.main.async(execute: { + NotificationCenter.default.post( + name: Notification.Name(rawValue: "BodyMassDataAvailable"), + object: r, + userInfo: nil) + LogService.shared.debug("•HK• WeightEntryViewController post BodyMassDataAvailable") + }) + } + } + + private func readResultsLog(passthru: Any?, query: HKSampleQuery, samples: [HKSample]?, error: Error?) { + guard let samples = samples as? [HKQuantitySample] else { + LogService.shared.info("readResultsLog no samples found") + return + } + + var str = "\nHealthManager READ Results:\n" + str.append(HealthManager.toStringCSV(samples: samples)) + LogService.shared.info(str) + } + + // MARK: - SAVE + + public func saveHKWeight(date: Date, weight: Double) { + saveHKWeight(date: date, weight: weight, isImperial: SettingsManager.isImperial()) + } + + public func saveHKWeight(date: Date, kg: Double) { + saveHKWeight(date: date, weight: kg, isImperial: false) + } + + /// Update or create HealthKit weight sample + public func saveHKWeight(date: Date, weight: Double, isImperial: Bool, metadata: [String: Any]? = nil) { + LogService.shared.verbose("::: HealthManager saveHKWeight \(String(format: "%.1f", weight)) \(date.datestampyyyyMMddHHmmss)") + let bodymassQType = HKQuantityType.quantityType(forIdentifier: .bodyMass)! + let hkUnit = isImperial ? HKUnit.pound() : HKUnit.gramUnit(with: .kilo) + let hkQuantity = HKQuantity(unit: hkUnit, doubleValue: weight) + + let bodymassSample = HKQuantitySample( + type: bodymassQType, // HKQuantityType + quantity: hkQuantity, // HKQuantity + start: date, // Date + end: date, // Date + device: nil, // HKDevice? + metadata: metadata) // [String: Any]? + + hkHealthStore.save(bodymassSample, withCompletion: saveResultsLog) + } + + private func saveResultsLog(success: Bool, error: Error?) { + if error != nil { + LogService.shared.error("::: HealthManager saveResultsLog error: '\(error.debugDescription)'") + } + if success { + LogService.shared.debug("::: HealthManager saveResultsLog SUCCESS") + } + } + + // MARK: - DELETE + + /// Clear all weight samples for which DailyDozen app is the source. + /// Use: clear button + public func deleteHKWeight(date: Date, ampm: DataWeightType) { + let baseDate = Calendar.current.startOfDay(for: date) + var dateComponentsStart = DateComponents() + dateComponentsStart.hour = ampm == .am ? 0 : 12 + let startDate = Calendar.current.date(byAdding: dateComponentsStart, to: baseDate)! + var dateComponentsEnd = DateComponents() + dateComponentsEnd.hour = ampm == .am ? 12 : 24 + let endDate = Calendar.current.date(byAdding: dateComponentsEnd, to: baseDate)! + + // Weight sample "start" time .greater.than.or.equal.to. query target start time, and + // weight sample "end" time .less.than. query target end time. + let options: HKQueryOptions = [.strictStartDate] + + let predicate: NSPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: options) + + deleteHKWeight(predicate: predicate, handler: deleteResultsLog) + } + + // Use: remove "BIT" values + public func deleteHKWeight(key: String, values: [Any]? = nil) { + let predicate: NSPredicate! + if let values = values { + predicate = HKQuery.predicateForObjects(withMetadataKey: key, allowedValues: values) + } else { + predicate = HKQuery.predicateForObjects(withMetadataKey: key) + } + + deleteHKWeight(predicate: predicate, handler: deleteResultsLog) + } + + public func deleteHKWeight(samples: [HKObject]) { + if samples.isEmpty == false { + hkHealthStore.delete(samples, withCompletion: deleteResultsLog) + } + } + + private func deleteHKWeight(predicate: NSPredicate, handler: @escaping (Bool, Int, Error?) -> Void) { + let bodymassQType = HKQuantityType.quantityType(forIdentifier: .bodyMass)! + hkHealthStore.deleteObjects(of: bodymassQType, predicate: predicate, withCompletion: handler) + } + + private func deleteResultsLog(success: Bool, deletedObjectCount: Int, error: Error?) { + LogService.shared.info("HealthManager deleteResultsLog() success=\(success) count=\(deletedObjectCount) error=\(error.debugDescription)") + } + + private func deleteResultsLog(success: Bool, error: Error?) { + LogService.shared.info("HealthManager deleteResultsLog() success=\(success) error=\(error.debugDescription)") + } + + // Use: clear & reset sync values + public func deleteHKAllWeigths(completion: @escaping () -> Void) { + let bodymassQType = HKQuantityType.quantityType(forIdentifier: .bodyMass)! + let dailydozenHKSource = HKSource.default() + let predicate = HKSampleQuery.predicateForObjects(from: dailydozenHKSource) + + hkHealthStore.deleteObjects(of: bodymassQType, predicate: predicate) { + (success: Bool, deletedObjectCount: Int, error: Error?) in + if success == false { + if let error = error { + LogService.shared.debug("deleteHKAllWeigths failed count=\(deletedObjectCount) error=\(error)") + } else { + LogService.shared.debug("deleteHKAllWeigths failed count=\(deletedObjectCount)") + } + return + } + completion() + } + } + + // MARK: - Export + + public func exportHKWeight(marker: String) { + let filename = "\(Date.datestampNow())_\(marker).csv" + readHKWeight(predicate: nil, ascending: true, passthru: filename, handler: exportResults) + } + + private func exportResults(passthru: Any?, query: HKSampleQuery, samples: [HKSample]?, error: Error?) { + if let error = error { + LogService.shared.error("HealthManager exportResults error:'\(error)'") + return + } + + guard let samples = samples as? [HKQuantitySample] else { + LogService.shared.verbose("HealthManager exportResults no weight samples found.") + return + } + + guard let filename = passthru as? String else { + LogService.shared.error("HealthManager exportResults passthru failed.") + return + } + + let outUrl = URL.inDocuments().appendingPathComponent(filename) + let content = HealthManager.toStringCSV(samples: samples) + do { + try content.write(to: outUrl, atomically: true, encoding: .utf8) + } catch { + LogService.shared.error( + "FAIL HealthManager exportResults \(error) path:'\(outUrl.path)'" + ) + } + } + + public static func toStringCSV(samples: [HKQuantitySample]) -> String { + var str = "HK_PID,time,kg,lbs,source\n" + + for item: HKQuantitySample in samples { + let bodymassKg = item.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo)) + let weightKg = Double(bodymassKg) + let weightKgStr = String(format: "%.1f", weightKg) + let weightLbs = weightKg * 2.2046 // 1 kg = 2.2046 lbs + let weightLbsStr = String(format: "%.1f", weightLbs) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd.a" + let dateStr = dateFormatter.string(from: item.startDate).lowercased() + + let timeFormatter = DateFormatter() + timeFormatter.dateFormat = "HH:mm" + let timeStr = timeFormatter.string(from: item.startDate) + + let source = item.sourceRevision.source.bundleIdentifier + + str.append("\(dateStr),\(timeStr),\(weightKgStr),\(weightLbsStr),\(source)\n") + } + return str + } + +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthSynchronizer.swift b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthSynchronizer.swift new file mode 100644 index 00000000..00649e96 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthSynchronizer.swift @@ -0,0 +1,192 @@ +// +// HealthSynchronizer.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import HealthKit +import RealmSwift + +/// HealthSynchronizer coordinates data between the application database and healthkit. +/// +/// Notes: +/// * HKQuantitySample does not provide access to data added to HealthKit. +/// * +/// +/// Tolerance Notes: +/// * 0.10 kg == 0.220 lbs +/// * 0.10 lbs == 0.045 kg +struct HealthSynchronizer { + + static var shared = HealthSynchronizer() + + // MARK: - Properties + private let realm = RealmProvider() + + /// Note: keep `realm` operations on this thread. Pass value copies of realm objects to the `serialSyncQueue` tasks. + private let serialSyncQueue = DispatchQueue(label: "com.nutritionfacts.queues.serial.sync", qos: DispatchQoS.userInitiated) + + public func resetSyncAll() { + // Note: keep realm object access and processing on the same thread + let dbResults = self.realm.getDBWeightDatetimes() + var dbValues = [DataWeightValues]() + for record: DataWeightRecord in dbResults { + // Copy Realm `Object` values on this thead before completion block and `async` enqueueing. + dbValues.append(DataWeightValues(record: record)) + } + HealthManager.shared.deleteHKAllWeigths { // completion: () -> Void + for values in dbValues { + self.serialSyncQueue.async { + guard let datetime = values.datetime else { return } + let ampm = datetime.ampm + let kg = values.kg + self.syncWeightDate(date: datetime, ampm: ampm, kg: kg) + } + } + } + } + + public func syncWeightDate(date: Date, ampm: DataWeightType, kg: Double) { + let predicate = HealthManager.shared.buildPredicate(date: date, ampm: ampm) + + // AM: ascending order TRUE so earliest AM time lists first. + // PM: ascending order FALSE so latest PM time lists first. + let ascending = ampm == .am + + let values = DataWeightValues(date: date, weightType: ampm, kg: kg) + + serialSyncQueue.async { + HealthManager.shared.readHKWeight( + predicate: predicate, + ascending: ascending, + passthru: values, + handler: self.syncWeightDateResults) + } + } + + private func syncWeightDateResults(passthru: Any?, query: HKSampleQuery, samples: [HKSample]?, error: Error?) { + if let error = error { + LogService.shared.error("syncWeightDateResults \"\(error.localizedDescription)\"") + } + + guard let values = passthru as? DataWeightValues, + let datetime = values.datetime else { + LogService.shared.error("syncWeightDateResults expected an 'VALUES' DataWeightType") + return + } + + // Case: no existing HK samples for this datetime. Save sample + guard let samples = samples as? [HKQuantitySample] else { + serialSyncQueue.async { + HealthManager.shared.saveHKWeight(date: datetime, kg: values.kg) + } + return + } + + // "HKTweak" HealthKit entries created by DailyDozen app + // "HKOther" HealthKit entries created by non-DailyDozen apps. e.g. Apple's Health app + // search for matching samples + let bundleId = Bundle.main.bundleIdentifier! + var samplesHKTweak: [HKQuantitySample] = [] + var matchHKTweak: HKQuantitySample? + var matchHKOther: HKQuantitySample? + for item in samples { + let bodymassKg = item.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo)) + if item.sourceRevision.source.bundleIdentifier == bundleId { + // :TDB: only checks kg value for the given AM/PM time period. + // Perhaps also constrain match to a more narrow datetime range. + if matchHKTweak == nil && abs(bodymassKg - values.kg) < 0.1 { + matchHKTweak = item + } else { + samplesHKTweak.append(item) + } + } else { + if matchHKOther == nil && abs(bodymassKg - values.kg) < 0.1 { + matchHKOther = item + } + } + } + + // Case: matches an HKOther quantity. Remove any HKTweak values. + if matchHKOther != nil { + // Remove any HKTweak samples + if let matchHKTweak = matchHKTweak { + samplesHKTweak.append(matchHKTweak) + } + serialSyncQueue.async { + HealthManager.shared.deleteHKWeight(samples: samplesHKTweak) + } + return + } + + // Case: matches a HKTweak quantity. Remove extra, non-matching HKTweak values. + if matchHKTweak != nil { + // Remove any HKTweak samples other than the matched sample + serialSyncQueue.async { + HealthManager.shared.deleteHKWeight(samples: samplesHKTweak) + } + return + } + + // Case: DB passthru value exists without any HealthKit match + serialSyncQueue.async { + // Remove any extra non-matching HKTweak samples + HealthManager.shared.deleteHKWeight(samples: samplesHKTweak) + } + serialSyncQueue.async { + // Add new HKTweak value + HealthManager.shared.saveHKWeight(date: datetime, kg: values.kg) + } + } + + func syncWeightPut(date: Date, ampm: DataWeightType, kg: Double) { + if let record = realm.getDBWeight(date: date, ampm: ampm) { + if abs(record.kg - kg) < 0.1 { + return // close enough. no additional actions needed. + } + } + // Note: keep `realm` access on this thread. + realm.saveDBWeight(date: date, ampm: ampm, kg: kg) + + NotificationCenter.default.post(name: Notification.Name(rawValue: "NoticeChangedWeight"), object: date, userInfo: nil) + + serialSyncQueue.async { + self.syncWeightDate(date: date, ampm: ampm, kg: kg) + } + } + + /// + func syncWeightToShow(date: Date, ampm: DataWeightType) -> (time: String, weight: String) { + let record: DataWeightRecord? = realm.getDBWeight(date: date, ampm: ampm) + + // Return DB weight record if present + if let record = record { + let weightStr = SettingsManager.isImperial() ? record.lbsStr : record.kgStr + return (time: record.timeAmPm, weight: weightStr) + } + + // Otherwise, request "first" HK weight record if present + serialSyncQueue.async { + HealthManager.shared.readHKWeight(date: date, ampm: ampm) + } + // Return empty values. HK will update later via notification + return (time: "", weight: "") + } + + func syncWeightClear(date: Date, ampm: DataWeightType) { + realm.deleteDBWeight(date: date, ampm: ampm) + NotificationCenter.default.post(name: Notification.Name(rawValue: "NoticeChangedWeight"), object: date, userInfo: nil) + serialSyncQueue.async { + HealthManager.shared.deleteHKWeight(date: date, ampm: ampm) + } + } + + func syncWeightExport(marker: String) { + serialSyncQueue.async { + HealthManager.shared.exportHKWeight(marker: marker) + } + } + +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthWeightRecord.swift b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthWeightRecord.swift new file mode 100644 index 00000000..11310465 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHealthKit/HealthWeightRecord.swift @@ -0,0 +1,89 @@ +// +// HealthWeightRecord.swift +// DailyDozen +// +// Copyright © 2020 Nutritionfacts.org. All rights reserved. +// + +import Foundation +import HealthKit + +/// Notification passthrough structure for HealthKit completion handlers. +struct HealthWeightRecord { + + let ampm: DataWeightType + var hkWeightSamples: [HKQuantitySample] + + init(ampm: DataWeightType, hkWeightSamples: [HKQuantitySample]) { + self.ampm = ampm + self.hkWeightSamples = hkWeightSamples + } + + /// Weight data to show to user "IB" + func getIBWeightToShow() -> (time: String, weight: String) { + // Return "first" HK weight record if present + guard let sample = hkWeightSamples.first else { + return (time: "", weight: "") + } + + let bodymassKg = sample.quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo)) + var weight = Double(bodymassKg) + if SettingsManager.isImperial() { + weight = weight * 2.2046 // 1 kg = 2.2046 lbs + } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "hh:mm a" + let timeStr = dateFormatter.string(from: sample.startDate) + let weightStr = String(format: "%.1f", weight) + return (time: timeStr, weight: weightStr) + } + + func toString() -> String { + var s = "" + + for sample: HKQuantitySample in hkWeightSamples { + s.append( "\(sample.uuid) " ) + s.append( "\(sample.startDate.datestampyyyyMMddHHmmss) " ) + + // -- HKQuantity > Double > kilo|pound -- + let quantity: HKQuantity = sample.quantity + let kiloValue = quantity.doubleValue(for: HKUnit.gramUnit(with: .kilo)) + let kiloStr = String(format: "%.1f", kiloValue) + s.append( "\(kiloStr)kg " ) + + let poundValue = quantity.doubleValue(for: HKUnit.pound()) + let poundStr = String(format: "%.1f", poundValue) + s.append( "\(poundStr)lbs " ) + + // -- HKQuantityType > HKQuantityAggregationStyle -- + // -- Double, discreteArithmetic + //let quantityType: HKQuantityType = sample.quantityType + //let aggregationStyle: HKQuantityAggregationStyle = quantityType.aggregationStyle + //s.append( "style:\(aggregationStyle.rawValue) " ) + //quantityType.isMaximumDurationRestricted // iOS 13+ + //quantityType.isMinimumDurationRestricted // iOS 13+ + + // -- HKSourceRevision > HKSource -- + let sourceRevision = sample.sourceRevision + //sourceRevision.operatingSystemVersion.majorVersion + let source: HKSource = sourceRevision.source + // com.nutritionfacts.dailydozen DailyDozen + // com.apple.Health Health + s.append("\(source.bundleIdentifier) \(source.name) ") + + // --- METADATA --- + // metadata: { HKWasUserEntered = 1; } created by Health app + //s.metadata // [String: Any]? + + //s.append( "\n" ) + //s.append( "*** \(sample.debugDescription) ***\n" ) + //s.append( "### \(sample.description) ###\n" ) + //s.append( "\((sample.device != nil) ? sample.device.debugDescription : "nil-device")\n" ) + + s.append("\n") + } + + return s + } + +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHistory/Controllers/WeightHistoryViewController.swift b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Controllers/WeightHistoryViewController.swift new file mode 100644 index 00000000..ec78d024 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Controllers/WeightHistoryViewController.swift @@ -0,0 +1,361 @@ +// +// WeightHistoryViewController.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// +// swiftlint: disable cyclomatic_complexity +// swiftlint: disable function_body_length +// swiftlint: disable type_body_length + +import UIKit +import Charts + +class WeightHistoryBuilder { + + // MARK: Methods + /// Instantiates and returns the initial view controller for a storyboard. + /// + /// - Returns: The initial view controller in the storyboard. + static func instantiateController() -> WeightHistoryViewController { + let storyboard = UIStoryboard(name: "WeightHistoryLayout", bundle: nil) + guard + let viewController = storyboard + .instantiateInitialViewController() as? WeightHistoryViewController + else { fatalError("Did not instantiate `WeightHistoryViewController`") } + viewController.title = NSLocalizedString("historyRecordWeight.heading", comment: "Weight History") + + return viewController + } +} + +// MARK: - + +/// Historic record of daily checkbox tally. +class WeightHistoryViewController: UIViewController { + + @IBOutlet weak var lineChartView: LineChartView! + @IBOutlet private weak var controlPanel: ControlPanel! // Buttons: << < … > >> + @IBOutlet private weak var scaleControl: UISegmentedControl! // Day|Month|Year + @IBOutlet weak var weightEditDataButton: UIButton! + @IBOutlet weak var weightTitleUnits: UILabel! + + // MARK: - Properties + private var weightViewModel: WeightHistoryViewModel! + private var currentTimeScale = TimeScale.day + private let realm = RealmProvider() + + private var chartSettings: (year: Int, month: Int)! { + didSet { + lineChartView.clear() + + if SettingsManager.isImperial() { + weightTitleUnits.text = NSLocalizedString("historyRecordWeight.titleImperial", comment: "Weight (lbs)") + } else { + weightTitleUnits.text = NSLocalizedString("historyRecordWeight.titleMetric", comment: "Weight (kg)") + } + + if currentTimeScale == .day { + controlPanel.isHidden = false + controlPanel.superview?.isHidden = false + + var canLeft = true + if chartSettings.month == 0, chartSettings.year == 0 { + canLeft = false + } + + var canRight = true + + if chartSettings.year == weightViewModel.lastYearIndex, + chartSettings.month == weightViewModel.lastMonthIndex(for: weightViewModel.lastYearIndex) { + canRight = false + } + + controlPanel.configure(canSwitch: (left: canLeft, right: canRight)) + + let data = weightViewModel.monthData(yearIndex: chartSettings.year, monthIndex: chartSettings.month) + + // :DEBUG:LOG_VERBOSE: + //LogService.shared.verbose("## Weight History: \(data.month) chartSettings(year:\(chartSettings.year), month:\(chartSettings.month)) ##") + //var i = 0 + //for point in data.points { + // let dataStr = """ + // \(point.anyDate) • \ + // \(point.dateAM?.datestampHHmm ?? "nil") \(String(format: "%.2f", point.kgAM ?? -1.0)) • \ + // \(point.datePM?.datestampHHmm ?? "nil") \(String(format: "%.2f", point.kgPM ?? -1.0)) + // """ + // LogService.shared.verbose(dataStr) + // i += 1 + //} + + controlPanel.setLabels(month: data.month, year: weightViewModel.yearName(yearIndex: chartSettings.year)) + + updateChart(points: data.points, scale: .day) + //chartView.configure(with: data.map, for: currentTimeScale) + } else if currentTimeScale == .month { + controlPanel.isHidden = false + controlPanel.superview?.isHidden = false + + let canLeft = chartSettings.year != 0 + let canRight = chartSettings.year != weightViewModel.lastYearIndex + controlPanel.configure(canSwitch: (left: canLeft, right: canRight)) + + let data = weightViewModel.yearlyData(yearIndex: chartSettings.year) + + // :DEBUG:LOG_VERBOSE: + //LogService.shared.verbose("## Weight History: \(data.year) chartSettings(year:\(chartSettings.year), month:\(chartSettings.month)) ##") + //var i = 0 + //for point in data.points { + // let dataStr = """ + // \(point.anyDate) • \ + // \(point.dateAM?.datestampHHmm ?? "nil") \(String(format: "%.2f", point.kgAM ?? -1.0)) • \ + // \(point.datePM?.datestampHHmm ?? "nil") \(String(format: "%.2f", point.kgPM ?? -1.0)) + // """ + // LogService.shared.verbose(dataStr) + // i += 1 + //} + + controlPanel.setLabels(year: data.year) + + updateChart(points: data.points, scale: .month) + //chartView.configure(with: data.map, for: currentTimeScale) + } else { + controlPanel.isHidden = true + controlPanel.superview?.isHidden = true + + // Multiple years + updateChart(points: weightViewModel.fullDataMap(), scale: .year) + //chartView.configure(with: weightViewModel.fullDataMap(), for: currentTimeScale) + } + } + } + + // MARK: - Methods + override func viewDidLoad() { + LogService.shared.debug("•HK• WeightHistoryViewController viewDidLoad") + super.viewDidLoad() + navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white] + navigationController?.navigationBar.barTintColor = UIColor.greenColor + navigationController?.navigationBar.tintColor = UIColor.white + + lineChartView.xAxis.valueFormatter = self + setViewModel() + //updateChart(fromDate: Date(), toDate: Date()) // :!!!: + } + + // ------------------------- + + // IB action, then updateChartWithData + private func updateChartWithData(am: [ChartDataEntry], pm: [ChartDataEntry], range: Double) { + switch currentTimeScale { + case .day: + break + case .month: + break + case .year: + break + } + + let legendMorningText = NSLocalizedString("historyRecordWeight.legendMorning", comment: "Morning") + let lineChartDataSetAM = LineChartDataSet(entries: am, label: legendMorningText) + lineChartDataSetAM.colors = [UIColor.yellowSunglowColor] + lineChartDataSetAM.circleColors = [UIColor.yellowSunglowColor] + lineChartDataSetAM.circleHoleRadius = 0.0 // Default: 4.0 + lineChartDataSetAM.circleRadius = 4.0 // Default: 8.0 + lineChartDataSetAM.drawValuesEnabled = false + lineChartDataSetAM.lineWidth = 2.0 // Default: 1 + lineChartDataSetAM.mode = .linear // .cubicBezier + + let legendEveningText = NSLocalizedString("historyRecordWeight.legendEvening", comment: "Evening") + let lineChartDataSetPM = LineChartDataSet(entries: pm, label: legendEveningText) + lineChartDataSetPM.colors = [UIColor.redFlamePeaColor] + lineChartDataSetPM.circleColors = [UIColor.redFlamePeaColor] + lineChartDataSetPM.circleHoleRadius = 0.0 // Default: 4.0 + lineChartDataSetPM.circleRadius = 4.0 // Default: 8.0 + lineChartDataSetPM.drawValuesEnabled = false + lineChartDataSetAM.lineWidth = 2.0 // Default: 1 + lineChartDataSetPM.mode = .linear // .cubicBezier + + let lineChartData = LineChartData(dataSets: [lineChartDataSetAM, lineChartDataSetPM]) + lineChartView.data = lineChartData + lineChartView.xAxis.drawLabelsEnabled = false + //lineChartView.xAxis.axisRange = range + lineChartView.xAxis.axisMinimum = 0.0 + lineChartView.xAxis.axisMaximum = range + lineChartView.xAxis.avoidFirstLastClippingEnabled = true + } + + func updateChart(points: [DailyWeightReport], scale: TimeScale) { + + guard + let firstDatestampKey = points.first?.anyDate.datestampKey, + let firstDayOfMonth = Date(datestampKey: "\(firstDatestampKey.prefix(6))01"), + let firstDayOfYear = Date(datestampKey: "\(firstDatestampKey.prefix(4))0101") + else { return } + + // day scale = 60 seconds * 60 minutes * 24 hours + var xScaleFactor = 60.0*60.0*24.0 // default: .day (seconds/day) + var xAxisRange = 31.0 // daily for 31 days + var fromTimeInterval = firstDayOfMonth.timeIntervalSince1970 + if scale == .month { + fromTimeInterval = firstDayOfYear.timeIntervalSince1970 + xScaleFactor = 60.0*60.0*24.0 * 30.44 // .month (seconds/average_julian_month) + xAxisRange = 12.0 // monthly for year + } else if currentTimeScale == .year { + fromTimeInterval = firstDayOfYear.timeIntervalSince1970 + xScaleFactor = 60.0*60.0*24.0 * 365.25 // .year (seconds/average_julian_year) + xAxisRange = 3 // yearly for 3 years + } + + //var dataEntries = [ + // ChartDataEntry(x: 1.0, y: 144.3), + // ChartDataEntry(x: 3.0, y: 143.5), + //] + //dataEntriesAM.append(ChartDataEntry(x: 4.0, y: 144.5)) + var dataEntriesAM = [ChartDataEntry]() + var dataEntriesPM = [ChartDataEntry]() + + for dailyWeightRecord in points { + if let dateAM = dailyWeightRecord.dateAM, + let kgAM = dailyWeightRecord.kgAM { + let xTimeInterval: TimeInterval = dateAM.timeIntervalSince1970 + let x = (xTimeInterval - fromTimeInterval) / xScaleFactor + var y = kgAM + if SettingsManager.isImperial() { + y = kgAM * 2.204 + } + let chartDataEntry = ChartDataEntry(x: x, y: y) + dataEntriesAM.append(chartDataEntry) + } + if let datePM = dailyWeightRecord.datePM, + let kgPM = dailyWeightRecord.kgPM { + let xTimeInterval: TimeInterval = datePM.timeIntervalSince1970 + let x = (xTimeInterval - fromTimeInterval) / xScaleFactor + var y = kgPM + if SettingsManager.isImperial() { + y = kgPM * 2.204 + } + let chartDataEntry = ChartDataEntry(x: x, y: y) + dataEntriesPM.append(chartDataEntry) + } + } + + // :DEBUG:LOG_VERBOSE: + //LogService.shared.verbose("\n•••••••••••••••••••••••••••••••••••••••••••••••••") + //LogService.shared.verbose("••• WeightHistoryViewController updateChart() •••") + //LogService.shared.verbose("••• INPUT: points @ scale:\(scale.toString()) •••") + //for dailyWeightReport in points { + // LogService.shared.verbose(dailyWeightReport.toString()) + //} + //LogService.shared.verbose("••• OUTPUT: dataEntries @ xAxisRange:\(xAxisRange) •••") + //LogService.shared.verbose("••• AM (x,y)") + //for chartDataEntry: ChartDataEntry in dataEntriesAM { + // LogService.shared.verbose(chartDataEntry.toStringXY()) + //} + //LogService.shared.verbose("••• PM (x,y)") + //for chartDataEntry in dataEntriesPM { + // LogService.shared.verbose(chartDataEntry.toStringXY()) + //} + + updateChartWithData(am: dataEntriesAM, pm: dataEntriesPM, range: xAxisRange) + } + + // ------------------------- + + private func setViewModel() { + let realm = RealmProvider() + + let weights = realm.getDailyWeights() + guard weights.am.count + weights.pm.count > 0 else { + controlPanel.isHidden = true + scaleControl.isEnabled = false + controlPanel.superview?.isHidden = true + return + } + + weightViewModel = WeightHistoryViewModel(amRecords: weights.am, pmRecords: weights.pm) + let lastYearIndex = weightViewModel.lastYearIndex + chartSettings = (lastYearIndex, weightViewModel.lastMonthIndex(for: lastYearIndex)) + } + + // MARK: - Actions + + @IBAction func editDataButtonPressed(_ sender: UIButton) { + let viewController = WeightEntryPagerBuilder.instantiateController() + navigationController?.pushViewController(viewController, animated: true) + } + + @IBAction private func toFirstButtonPressed(_ sender: UIButton) { + chartSettings = (0, 0) + } + + @IBAction private func toPreviousButtonPressed(_ sender: UIButton) { + if chartSettings.month > 0 && currentTimeScale == .day { + chartSettings.month -= 1 + } else if chartSettings.year > 0 { + let year = chartSettings.year - 1 + let month = weightViewModel.lastMonthIndex(for: year) + chartSettings = (year, month) + } + } + + @IBAction private func toNextButtonPressed(_ sender: UIButton) { + if chartSettings.month < weightViewModel.lastMonthIndex(for: chartSettings.year) && currentTimeScale == .day { + chartSettings.month += 1 + } else if chartSettings.year < weightViewModel.lastYearIndex { + let year = chartSettings.year + 1 + let month = 0 + chartSettings = (year, month) + } + } + + @IBAction private func toLastButtonPressed(_ sender: UIButton) { + let lastYearIndex = weightViewModel.lastYearIndex + chartSettings = (lastYearIndex, weightViewModel.lastMonthIndex(for: lastYearIndex)) + } + + @IBAction private func timeScaleChanged(_ sender: UISegmentedControl) { + guard let timeScale = TimeScale(rawValue: sender.selectedSegmentIndex) else { return } + currentTimeScale = timeScale + + switch currentTimeScale { + case .day: + let lastYearIndex = weightViewModel.lastYearIndex + chartSettings = (lastYearIndex, weightViewModel.lastMonthIndex(for: lastYearIndex)) + case .month: + let lastYearIndex = weightViewModel.lastYearIndex + chartSettings = (lastYearIndex, 0) + case .year: + chartSettings = (0, 0) + } + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} + +// MARK: - IAxisValueFormatter +extension WeightHistoryViewController: IAxisValueFormatter { + + func stringForValue(_ value: Double, axis: AxisBase?) -> String { + let labels: [String] + if currentTimeScale == .day { + labels = weightViewModel.datesLabels(yearIndex: chartSettings.year, monthIndex: chartSettings.month) + } else if currentTimeScale == .month { + labels = weightViewModel.monthsLabels(yearIndex: chartSettings.year) + } else { + labels = weightViewModel.fullDataLabels() + } + let index = Int(value) + guard index < labels.count else { return "" } + return labels[index] + } +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightHistoryViewModel.swift b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightHistoryViewModel.swift new file mode 100644 index 00000000..b78cb2e3 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightHistoryViewModel.swift @@ -0,0 +1,92 @@ +// +// WeightHistoryViewModel.swift +// DailyDozen +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import Foundation + +struct WeightHistoryViewModel { + + // MARK: - Properties + private let report: WeightReport + + var lastYearIndex: Int { + return report.data.count - 1 + } + + // MARK: - Inits + init(amRecords: [DataWeightRecord], pmRecords: [DataWeightRecord]) { + report = WeightReport(amRecords: amRecords, pmRecords: pmRecords) + LogService.shared.verbose( + "WeightHistoryViewModel init report:\n\(report.toString())" + ) + } + + // MARK: - Methods + func lastMonthIndex(for yearIndex: Int) -> Int { + return report.yearlyWeightReport(for: yearIndex).months.count - 1 + } + + func monthData(yearIndex: Int, monthIndex: Int) -> (month: String, points: [DailyWeightReport]) { + let monthReport: MonthWeightReport = report + .yearlyWeightReport(for: yearIndex) + .monthWeightReport(for: monthIndex) + + let month = monthReport.month + + return (month, monthReport.daily) + } + + func yearlyData(yearIndex: Int) -> (year: String, points: [DailyWeightReport]) { + let yearlyReport = report.yearlyWeightReport(for: yearIndex) + let year = String(yearlyReport.year) + + var points = [DailyWeightReport]() + for monthWeightReport in yearlyReport.months { + points.append(contentsOf: monthWeightReport.daily) + } + return (year, points) + } + + func fullDataMap() -> [DailyWeightReport] { + + var allDataPoints = [DailyWeightReport]() + for yearlyWeightReport in report.data { + for monthWeightReport in yearlyWeightReport.months { + allDataPoints.append(contentsOf: monthWeightReport.daily) + } + } + + return allDataPoints + } + + func yearName(yearIndex: Int) -> String { + return String(report.yearlyWeightReport(for: yearIndex).year) + } + + func datesLabels(yearIndex: Int, monthIndex: Int) -> [String] { + var labels = [String]() + for day in report + .yearlyWeightReport(for: yearIndex) + .monthWeightReport(for: monthIndex) + .daily { + labels.append("\(day.anyDate.day)") + } + return labels + } + + func monthsLabels(yearIndex: Int) -> [String] { + return report + .yearlyWeightReport(for: yearIndex) + .months + .map { $0.month } + } + + func fullDataLabels() -> [String] { + return report + .data + .map { String($0.year) } + } +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightReport.swift b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightReport.swift new file mode 100644 index 00000000..18e266d1 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Models/WeightReport.swift @@ -0,0 +1,226 @@ +// +// WeightReport.swift +// DailyDozen +// +// Copyright © 2017 Nutritionfacts.org. All rights reserved. +// +// swiftlint:disable cyclomatic_complexity + +import Foundation +import RealmSwift + +struct DailyWeightReport { + var dateAM: Date? + var datePM: Date? + var kgAM: Double? + var kgPM: Double? + + var anyDate: Date { + if let date = dateAM { + return date + } + return datePM! + } + + init?(am: DataWeightRecord?, pm: DataWeightRecord?) { + if am == nil && pm == nil { return nil } + if let am = am, let date = am.datetime { + dateAM = date + kgAM = am.kg + } + if let pm = pm, let date = pm.datetime { + datePM = date + kgPM = pm.kg + } + } + + func toString() -> String { + let str = """ + \(dateAM?.datestampKey ?? "yyyyHHdd")\t\ + \(dateAM?.datestampHHmm ?? "nil")\t\(String(format: "%.2f", kgAM ?? -0.1))\t\ + \(datePM?.datestampKey ?? "yyyyHHdd")\t\ + \(datePM?.datestampHHmm ?? "nil")\t\(String(format: "%.2f", kgPM ?? -0.1)) + """ + return str + } +} + +struct MonthWeightReport { + var daily = [DailyWeightReport]() + var month: String + // var weightAverageMorningKg: Double? :NYI: + // var weightAverageEveningKg: Double? :NYI: + + init(daily: [DailyWeightReport], month: String) { + self.daily = daily + self.month = month + // weightAverageMorningKg = + // weightAverageEveningKg = + } + + func toString() -> String { + var str = "•• MONTH:\(month) ••\n" + for report in daily { + str.append("\(report.toString())\n") + } + return str + } +} + +struct YearlyWeightReport { + var months = [MonthWeightReport]() + var year: Int // 2019, 2020, etc + // var weightAverageMorningKg: Double? :NYI: + // var weightAverageEveningKg: Double? :NYI: + + init(months: [MonthWeightReport], year: Int) { + self.months = months + self.year = year + // weightAverageMorningKg = + // weightAverageEveningKg = + } + + func monthWeightReport(for index: Int) -> MonthWeightReport { + return months[index] + } + + func toString() -> String { + var str = "•• YEAR:\(year) ••\n" + for report in months { + str.append(report.toString()) + } + return str + } + +} + +struct WeightReport { + var data = [YearlyWeightReport]() + + // amRecords: [DataWeightRecord], pmRecords: [DataWeightRecord] + // amRecords, pmRecords must be presorted + // amRecords, pmRecords must both contain at least one member + private func merge(amRecords: [DataWeightRecord], pmRecords: [DataWeightRecord]) -> [DailyWeightReport] { + var results = [DailyWeightReport]() + + var amIndex = 0 + var pmIndex = 0 + while amIndex < amRecords.count && pmIndex < pmRecords.count { + guard + let datestampAM = amRecords[amIndex].pidParts?.datestamp, + let datestampPM = pmRecords[pmIndex].pidParts?.datestamp + else { return [] } + if datestampAM == datestampPM { + let recordAM = amRecords[amIndex] + let recordPM = pmRecords[pmIndex] + if let report = DailyWeightReport(am: recordAM, pm: recordPM) { + results.append(report) + } + amIndex += 1 + pmIndex += 1 + } else if datestampAM > datestampPM { + let record = amRecords[amIndex] + if let report = DailyWeightReport(am: record, pm: nil) { + results.append(report) + } + amIndex += 1 + } else { + let record = pmRecords[pmIndex] + if let report = DailyWeightReport(am: nil, pm: record) { + results.append(report) + } + pmIndex += 1 + } + } + while amIndex < amRecords.count { + let record = amRecords[amIndex] + if let report = DailyWeightReport(am: record, pm: nil) { + results.append(report) + } + amIndex += 1 + } + while pmIndex < pmRecords.count { + let record = pmRecords[pmIndex] + if let report = DailyWeightReport(am: nil, pm: record) { + results.append(report) + } + pmIndex += 1 + } + + return results + } + + /// weight pecords must be date sorted + init(amRecords: [DataWeightRecord], pmRecords: [DataWeightRecord]) { + var dailyReports = [DailyWeightReport]() + + // Convert am/pm into daily weight records + if amRecords.count > 0 && pmRecords.count > 0 { + let merged = merge(amRecords: amRecords, pmRecords: pmRecords) + dailyReports.append(contentsOf: merged) + } else if amRecords.count > 0 { + for record in amRecords { + if let report = DailyWeightReport(am: record, pm: nil) { + dailyReports.append(report) + } + } + } else if pmRecords.count > 0 { + for record in pmRecords { + if let report = DailyWeightReport(am: nil, pm: record) { + dailyReports.append(report) + } + } + } + + // Segment days into months + var monthReports = [MonthWeightReport]() + guard var month = dailyReports.first?.anyDate.monthName else { return } + + var weightInMonth = [DailyWeightReport]() + dailyReports.forEach { weightReport in + if weightReport.anyDate.monthName == month { + weightInMonth.append(weightReport) + month = weightReport.anyDate.monthName + } else { + let monthWeightReport = MonthWeightReport(daily: weightInMonth, month: month) + monthReports.append(monthWeightReport) + weightInMonth.removeAll() + weightInMonth.append(weightReport) + month = weightReport.anyDate.monthName + } + } + monthReports.append(MonthWeightReport(daily: weightInMonth, month: month)) + + guard var year = monthReports.first?.daily.first?.anyDate.year else { + return + } + + // Segment months into years + var reportsInYear = [MonthWeightReport]() + monthReports.forEach { weightReport in + if weightReport.daily.first!.anyDate.year == year { + reportsInYear.append(weightReport) + year = weightReport.daily.first!.anyDate.year + } else { + let yearlyWeightReport = YearlyWeightReport(months: reportsInYear, year: year) + data.append(yearlyWeightReport) + reportsInYear.removeAll() + reportsInYear.append(weightReport) + year = weightReport.daily.first!.anyDate.year + } + } + data.append(YearlyWeightReport(months: reportsInYear, year: year)) + } + + func yearlyWeightReport(for index: Int) -> YearlyWeightReport { + return data[index] + } + + func toString() -> String { + var str = "••• FULL WEIGHT REPORT •••\n" + for report in data { + str.append(report.toString()) + } + return str + } +} diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/Base.lproj/WeightHistoryLayout.storyboard b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/Base.lproj/WeightHistoryLayout.storyboard new file mode 100644 index 00000000..af720a83 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/Base.lproj/WeightHistoryLayout.storyboard @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/es.lproj/WeightHistoryLayout.strings b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/es.lproj/WeightHistoryLayout.strings new file mode 100644 index 00000000..8fc0cc83 --- /dev/null +++ b/DailyDozen/DailyDozen/WeightSection/WeightHistory/Views/es.lproj/WeightHistoryLayout.strings @@ -0,0 +1,24 @@ +/* Class = "UILabel"; text = "Weight (lbs)"; ObjectID = "8ed-5m-QMc"; */ +"8ed-5m-QMc.text" = "Peso (lbs)"; + +/* Class = "UISegmentedControl"; fcu-ZA-byN.segmentTitles[0] = "Day"; ObjectID = "fcu-ZA-byN"; */ +"fcu-ZA-byN.segmentTitles[0]" = "Día"; + +/* Class = "UISegmentedControl"; fcu-ZA-byN.segmentTitles[1] = "Month"; ObjectID = "fcu-ZA-byN"; */ +"fcu-ZA-byN.segmentTitles[1]" = "Mes"; + +/* Class = "UISegmentedControl"; fcu-ZA-byN.segmentTitles[2] = "Year"; ObjectID = "fcu-ZA-byN"; */ +"fcu-ZA-byN.segmentTitles[2]" = "Año"; + +/* Class = "UILabel"; text = "2017"; ObjectID = "FTS-2y-Iil"; */ +"FTS-2y-Iil.text" = "2017"; + +/* Class = "UILabel"; text = "Month"; ObjectID = "lTR-i5-Tn0"; */ +"lTR-i5-Tn0.text" = "Mes"; + +/* Class = "UILabel"; text = "Time Scale"; ObjectID = "v2E-Ao-OVz"; */ +"v2E-Ao-OVz.text" = "Escala de tiempo"; + +/* Class = "UIButton"; normalTitle = "Edit Data"; ObjectID = "XYD-fX-adn"; */ +"XYD-fX-adn.normalTitle" = "Editar datos"; + diff --git a/DailyDozen/DailyDozenTests/DailyDozenTests.swift b/DailyDozen/DailyDozenTests/DailyDozenTests.swift new file mode 100644 index 00000000..dd900174 --- /dev/null +++ b/DailyDozen/DailyDozenTests/DailyDozenTests.swift @@ -0,0 +1,147 @@ +// +// DailyDozenTests.swift +// DailyDozenTests +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import XCTest +@testable import DailyDozen // module +import RealmSwift // List + +/// `IPHONEOS_DEPLOYMENT_TARGET` 12.4+ +class DailyDozenTests: XCTestCase { + + lazy var testBundle = Bundle(for: type(of: self)) + lazy var testBundleUrl = testBundle.bundleURL + + // World Pasta Day: Oct 25, 1995 + let date1995Pasta = Date(datestampKey: "19951025")! + // International Carrot Day: Apr 4, 2003 + let date2003Carrot = Date(datestampKey: "20030404")! + // World Porridge Day: Oct 10, 2009 (initial celebration date) + let date2009Porridge = Date(datestampKey: "20091010")! + // Nationale Kale Day: Oct 2, 2013 + let date2013Kale = Date(datestampKey: "20131002")! + + override func setUp() { + // Setup code. Called before each test method invocation. + } + + override func tearDown() { + // Teardown code. Called after each test method invocation. + } + + func testA() { + print("\n::: testA :::") + + let fm = FileManager.default + let testWorkingDirPath = fm.currentDirectoryPath + print("TestBundle Directory: \n\(testBundleUrl.path)") + print("TestBundle Working Directory = \n\(testWorkingDirPath)") + + //import Foundation + //import RealmSwift + // + //let exampleTask = ExampleTask() + //exampleTask.doTask01ImportRealmCsv() + + print(":::::::::::::\n") + } + + func testB() { + print("\n::: testB :::") + let date = date2009Porridge + + let urlLegacy = workingUrl.appendingPathComponent("testBLegacy.realm", isDirectory: false) + let realmLegacy = RealmProviderLegacy(fileURL: urlLegacy) + let dozeA = realmLegacy.getDozeLegacy(for: date) + let dozeAItems: List = dozeA.items + realmLegacy.saveStatesLegacy([true, true, true], id: dozeAItems[0].id) + + let urlV01 = workingUrl.appendingPathComponent("testBV01.realm", isDirectory: false) + let realmV01 = RealmProvider(fileURL: urlV01) + _ = realmV01.getDailyTracker(date: date) + realmV01.saveCount(1, date: date, countType: DataCountType.dozeBeans) + + let trackerReadV01 = realmV01.getDailyTracker(date: date) + XCTAssertEqual(trackerReadV01.itemsDict.count, DataCountType.allCases.count) + + print(":::::::::::::\n") + } + + var workingUrl: URL { + URL.inDocuments() + } + + func testC() { + print("\n::: testC :::") + // Oct 2, 2013 Nationale Kale Day + let date00 = date2013Kale + + let urlLegacy = workingUrl.appendingPathComponent("testCLegacy.realm", isDirectory: false) + let realmMngrLegacy = RealmManagerLegacy(fileUrl: urlLegacy) + let realmDBLegacy = realmMngrLegacy.realmDb + realmDBLegacy.deleteDBAllLegacy() + let urlV01 = workingUrl.appendingPathComponent("testCV01.realm", isDirectory: false) + let realmMngrV01 = RealmManager(fileURL: urlV01) + let realmDBV01 = realmMngrV01.realmDb + realmDBV01.deleteDBAll() + + // Add known content to legacy + let dozeA = realmDBLegacy.getDozeLegacy(for: date00) + realmDBLegacy.saveStatesLegacy([true, false, true], id: dozeA.items[0].id) // Beans + + // 01: export legacy file, then import to new database + let filename01 = realmMngrLegacy.csvExport() + realmMngrV01.csvImport(filename: filename01) + + // Check new content length & values + let trackersPass01 = realmDBV01.getDailyTrackers() + XCTAssert(trackersPass01.count == 1, "incorrect number of imported legacy trackers") + XCTAssert( + trackersPass01[0].itemsDict[.dozeBeans]!.count == 2, + "" + ) + + // Change a value + realmDBV01.saveCount(3, date: date00, countType: .dozeBeans) + + // 02: export new database format + let filename02 = realmMngrV01.csvExport() + realmMngrV01.realmDb.deleteDBAll() + realmMngrV01.csvImport(filename: filename02) + + // :!!!: check new content length & values + let trackersPass02 = realmDBV01.getDailyTrackers() + XCTAssert(trackersPass02.count == 1, "incorrect number of imported legacy trackers") + XCTAssert( + trackersPass02[0].itemsDict[.dozeBeans]!.count == 3, + "" + ) + + } + + func testDatestamp() { + print("\n::: testDatestamp :::") + // World Vegan Day: Nov 1, 1994 (initial celebration date) + guard let date = Date(datestampKey: "19941102") else { + XCTFail("testDatestamp() failed to create date.") + return + } + + print("date: '\(date)'") + print("datestampKey: '\(date.datestampKey)'") + print("datestampHHmm: '\(date.datestampHHmm)'") + XCTAssertEqual(date.datestampKey, "19941102") + XCTAssertEqual(date.datestampHHmm, "00:00") + } + + //func testPerformance() { + // // Performance test case. + // measure { + // // Put the code you want to measure the time of here. + // } + //} + +} diff --git a/DailyDozen/DailyDozenTests/Info.plist b/DailyDozen/DailyDozenTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/DailyDozen/DailyDozenTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/DailyDozen/DailyDozenTests/Resources/ImportData01ab.csv b/DailyDozen/DailyDozenTests/Resources/ImportData01ab.csv new file mode 100644 index 00000000..7300d516 --- /dev/null +++ b/DailyDozen/DailyDozenTests/Resources/ImportData01ab.csv @@ -0,0 +1,5 @@ +Date,Beans,Berries,Greens +20190901,0,0,2 +20190901,0,1,2 +20190901,3,1,2 +20190901,3,1,2 diff --git a/DailyDozen/DailyDozenUITests/DailyDozenUITests.swift b/DailyDozen/DailyDozenUITests/DailyDozenUITests.swift new file mode 100644 index 00000000..14321ef5 --- /dev/null +++ b/DailyDozen/DailyDozenUITests/DailyDozenUITests.swift @@ -0,0 +1,42 @@ +// +// DailyDozenUITests.swift +// DailyDozenUITests +// +// Copyright © 2019 Nutritionfacts.org. All rights reserved. +// + +import XCTest + +class DailyDozenUITests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testAppLaunch() { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { + XCUIApplication().launch() + } + } + } +} diff --git a/DailyDozen/DailyDozenUITests/Info.plist b/DailyDozen/DailyDozenUITests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/DailyDozen/DailyDozenUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/DailyDozen/Podfile b/DailyDozen/Podfile index 7d7fc241..4f5b9d89 100644 --- a/DailyDozen/Podfile +++ b/DailyDozen/Podfile @@ -1,17 +1,53 @@ # Uncomment the next line to define a global platform for your project - platform :ios, '10.0' +platform :ios, '12.0' +# Comment the next line if you're not using Swift and don't want to use dynamic frameworks +use_frameworks! +inhibit_all_warnings! -target 'DailyDozen' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - inhibit_all_warnings! +def common_pods + ########################### + ### Pods for DailyDozen ### + ########################### + + ## https://cocoapods.org/pods/ActiveLabel + ## https://github.com/optonaut/ActiveLabel.swift + ## Swift: 5.0 + ## Swift Package Manager: 4.2 :APPEARS_OK: + pod 'ActiveLabel', '~> 1.1.0' + + ## https://cocoapods.org/pods/Charts + ## https://github.com/danielgindi/Charts + ## Swift: v5 ... 84% Swift, 15% ObjC, Swift demo code, ObjC demo code + ## Swift Package Manager: 5.1 + pod 'Charts', '~> 3.5.0' + + ## https://cocoapods.org/pods/FSCalendar + ## https://github.com/WenchaoD/FSCalendar + ## Swift: ... 88% ObjC + ## ... issues piling up + ## Swift Package Manager: not supported + pod 'FSCalendar', '~> 2.8.1' + + ## https://cocoapods.org/pods/RealmSwift + ## https://github.com/realm/realm-cocoa + ## Swift: looks like 5 + ## C++: cxx14 + ## Swift Package Manager: 5.0 + ## pod 'RealmSwift', '~> 3.18.0' ## Used in Daily Dozen release + ## pod 'RealmSwift', '~> 3.21.0' ## Skipped + pod 'RealmSwift', '~> 4.4.1' ## + + ## https://cocoapods.org/pods/SimpleAnimation + ## https://github.com/keithito/SimpleAnimation + ## Swift: 4.0 ... maybe ok at 4.2 + ## Swift Package Manager: unspecified + pod 'SimpleAnimation', '~> 0.4.2' +end - # Pods for DailyDozen - pod 'UICheckbox.Swift', '~> 1.0' - pod 'RealmSwift', '~> 3.0' - pod 'SimpleAnimation', '~> 0.3' - pod 'FSCalendar', '~> 2.7' - pod 'Charts', '~> 3.0' - pod 'ActiveLabel', :git => 'https://github.com/optonaut/ActiveLabel.swift.git', :tag => '0.8.0' +target 'DailyDozen' do + common_pods +end +target 'DailyDozenTests' do + common_pods end diff --git a/DailyDozen/Podfile.lock b/DailyDozen/Podfile.lock index 02915f46..534b7c70 100644 --- a/DailyDozen/Podfile.lock +++ b/DailyDozen/Podfile.lock @@ -1,44 +1,40 @@ PODS: - - ActiveLabel (0.7.1) - - Charts (3.0.5): - - Charts/Core (= 3.0.5) - - Charts/Core (3.0.5) - - FSCalendar (2.7.9) - - Realm (3.1.0): - - Realm/Headers (= 3.1.0) - - Realm/Headers (3.1.0) - - RealmSwift (3.1.0): - - Realm (= 3.1.0) - - SimpleAnimation (0.4.0) - - UICheckbox.Swift (1.0.0) + - ActiveLabel (1.1.0) + - Charts (3.5.0): + - Charts/Core (= 3.5.0) + - Charts/Core (3.5.0) + - FSCalendar (2.8.1) + - Realm (4.4.1): + - Realm/Headers (= 4.4.1) + - Realm/Headers (4.4.1) + - RealmSwift (4.4.1): + - Realm (= 4.4.1) + - SimpleAnimation (0.4.2) DEPENDENCIES: - - ActiveLabel (from `https://github.com/optonaut/ActiveLabel.swift.git`, tag `0.8.0`) - - Charts (~> 3.0) - - FSCalendar (~> 2.7) - - RealmSwift (~> 3.0) - - SimpleAnimation (~> 0.3) - - UICheckbox.Swift (~> 1.0) + - ActiveLabel (~> 1.1.0) + - Charts (~> 3.5.0) + - FSCalendar (~> 2.8.1) + - RealmSwift (~> 4.4.1) + - SimpleAnimation (~> 0.4.2) -EXTERNAL SOURCES: - ActiveLabel: - :git: https://github.com/optonaut/ActiveLabel.swift.git - :tag: 0.8.0 - -CHECKOUT OPTIONS: - ActiveLabel: - :git: https://github.com/optonaut/ActiveLabel.swift.git - :tag: 0.8.0 +SPEC REPOS: + trunk: + - ActiveLabel + - Charts + - FSCalendar + - Realm + - RealmSwift + - SimpleAnimation SPEC CHECKSUMS: - ActiveLabel: faa96b5f50507770536a3e48a4cf291ee88fb7db - Charts: 45cdc985b764c838b35ee47eb1365d15735f6d1a - FSCalendar: a04b09f16f811bc92e82f3cf852a15225233b9d5 - Realm: 09513d7c054678d65cd02ce09871f805b0d758ac - RealmSwift: e868fee9b10e5490ad94b6b6ecd9027944da1824 - SimpleAnimation: a3473da5421c65100d68f98a44fc853b81091249 - UICheckbox.Swift: 85715689206fac5bae38b308f19c2a3e00442c3b + ActiveLabel: 5e3f4de79a1952d4604b845a0610d4776e4b82b3 + Charts: 40a08591df1f8ad5c223ddedfb1a06da92f24f7c + FSCalendar: 5245140456fcbcac5824c203b98bd8068bdfee1a + Realm: 4eb04d7487bd43c0581256f40b424eafb711deff + RealmSwift: 3eb8924ff7100df5928c7602f71d0fec51e7c9c5 + SimpleAnimation: 5430010b9548f392e7459290acce01c9ceba7171 -PODFILE CHECKSUM: 5caabf47232433366e463a15b673f71f1ab71b7b +PODFILE CHECKSUM: 9117ef146b1e169eb6f2d748fd5e19ba0c73c80b -COCOAPODS: 1.3.1 +COCOAPODS: 1.9.1 diff --git a/DailyDozen/SVG/Checkmark/ic_checkmark_white.svg b/DailyDozen/SVG/Checkmark/ic_checkmark_white.svg new file mode 100644 index 00000000..67be65dd --- /dev/null +++ b/DailyDozen/SVG/Checkmark/ic_checkmark_white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/DailyDozen/SVG/Checkmark/ic_checkmark_white_green.svg b/DailyDozen/SVG/Checkmark/ic_checkmark_white_green.svg new file mode 100644 index 00000000..4c603175 --- /dev/null +++ b/DailyDozen/SVG/Checkmark/ic_checkmark_white_green.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/DailyDozen/SVG/Checkmark/ic_checkmark_white_red.svg b/DailyDozen/SVG/Checkmark/ic_checkmark_white_red.svg new file mode 100644 index 00000000..00a8a442 --- /dev/null +++ b/DailyDozen/SVG/Checkmark/ic_checkmark_white_red.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/DailyDozen/SVG/Servings/01_Beans_device.svg b/DailyDozen/SVG/Servings/01_Beans_device.svg new file mode 100644 index 00000000..52575614 --- /dev/null +++ b/DailyDozen/SVG/Servings/01_Beans_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/02_Cruciferous_device.svg b/DailyDozen/SVG/Servings/02_Cruciferous_device.svg new file mode 100644 index 00000000..fcf0432b --- /dev/null +++ b/DailyDozen/SVG/Servings/02_Cruciferous_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/03_OtherFruit_device.svg b/DailyDozen/SVG/Servings/03_OtherFruit_device.svg new file mode 100644 index 00000000..b2df08ae --- /dev/null +++ b/DailyDozen/SVG/Servings/03_OtherFruit_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/04_Berries_device.svg b/DailyDozen/SVG/Servings/04_Berries_device.svg new file mode 100644 index 00000000..433ec7e6 --- /dev/null +++ b/DailyDozen/SVG/Servings/04_Berries_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/05_OtherVegetables_device.svg b/DailyDozen/SVG/Servings/05_OtherVegetables_device.svg new file mode 100644 index 00000000..32d2a844 --- /dev/null +++ b/DailyDozen/SVG/Servings/05_OtherVegetables_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/06_WholeGrains_device.svg b/DailyDozen/SVG/Servings/06_WholeGrains_device.svg new file mode 100644 index 00000000..76ad053d --- /dev/null +++ b/DailyDozen/SVG/Servings/06_WholeGrains_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/07_Beverages_device.svg b/DailyDozen/SVG/Servings/07_Beverages_device.svg new file mode 100644 index 00000000..2377e75c --- /dev/null +++ b/DailyDozen/SVG/Servings/07_Beverages_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/08_Greens_device.svg b/DailyDozen/SVG/Servings/08_Greens_device.svg new file mode 100644 index 00000000..b300ef43 --- /dev/null +++ b/DailyDozen/SVG/Servings/08_Greens_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/09_Exercise_device.svg b/DailyDozen/SVG/Servings/09_Exercise_device.svg new file mode 100644 index 00000000..4811fe01 --- /dev/null +++ b/DailyDozen/SVG/Servings/09_Exercise_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/10_Flaxseed_device.svg b/DailyDozen/SVG/Servings/10_Flaxseed_device.svg new file mode 100644 index 00000000..b4edbb2a --- /dev/null +++ b/DailyDozen/SVG/Servings/10_Flaxseed_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/11_Nuts_device.svg b/DailyDozen/SVG/Servings/11_Nuts_device.svg new file mode 100644 index 00000000..b46dd852 --- /dev/null +++ b/DailyDozen/SVG/Servings/11_Nuts_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/12_Spices_device.svg b/DailyDozen/SVG/Servings/12_Spices_device.svg new file mode 100644 index 00000000..0778be1f --- /dev/null +++ b/DailyDozen/SVG/Servings/12_Spices_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/Extra_B12_device.svg b/DailyDozen/SVG/Servings/Extra_B12_device.svg new file mode 100644 index 00000000..754c1533 --- /dev/null +++ b/DailyDozen/SVG/Servings/Extra_B12_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/Extra_Omega_device.svg b/DailyDozen/SVG/Servings/Extra_Omega_device.svg new file mode 100644 index 00000000..e22c1aa4 --- /dev/null +++ b/DailyDozen/SVG/Servings/Extra_Omega_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/DailyDozen/SVG/Servings/Extra_VitaminD_device.svg b/DailyDozen/SVG/Servings/Extra_VitaminD_device.svg new file mode 100644 index 00000000..5c914d88 --- /dev/null +++ b/DailyDozen/SVG/Servings/Extra_VitaminD_device.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/README.md b/README.md index 8308ff8a..4a35c9b2 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,34 @@ -Daily Dozen iOS App -=================== +# Daily Dozen iOS App -

+

-About ------------ +## About In the years of research required to create the more than a thousand evidence-based videos on [NutritionFacts.org][nutritionfacts.org], Michael Greger, MD, FACLM, has arrived at a list of what he considers the most important foods to include in a healthy daily diet. Yes, greens are good for you, but how much should we try to eat each day? Dr. Greger’s Daily Dozen details the healthiest foods and how many servings of each we should try to check off every day. He explains his rationale in his book [How Not to Die][book]. All his proceeds from his books, DVDs, and speaking engagements is all donated to charity. -Daily Dozen on the App Store ----------------------------- +## Daily Dozen on the App Store - + -Contribute ----------- +## Contribute We would love for you to contribute to our source code and to help make the Daily Dozen for Android even better! Check out our [Contribution Guidelines][contribute] for details on how to get started and our suggested best practices. -Donate ------- +## Donate To help support [NutritionFacts.org][nutritionfacts.org], click [here][donate] -License -------- +## License The Daily Dozen iOS App is licensed under the GPLv3 -Contributors ------------- +## Contributors + * [Konstantin Khokhlov][justaninja] * [Will Webb][innerfish] * [Christi Richards][christirichards] @@ -42,32 +36,52 @@ Contributors **Special thanks to the volunteer efforts of the original creators of the app:** -- **Application Development:** Chan Kruse -- **Application Design:** Allan Portera -- **Photography:** Sangeeta Kumar +* **Application Development:** Chan Kruse +* **Application Design:** Allan Portera +* **Photography:** Sangeeta Kumar + +## Updates + +**3.2.x (Work In Progress)** + +* Restructured to support international localization in general +* Adds Spanish + +**3.1 (App Store)** + +* Adds 21 Tweaks +* Adds Weight Tracking with Health Kit Integration +* Add Application Tab Controller bar + +Improvements: -Updates -------- +* Adds settings control to choice between using "Daily Dozen only" or "Daily Dozen + 21 Tweaks". -###2.0.0 (App Store) -- Brand new design -- Daily Dozen tracking now persists over multiple days -- Visualize your progress over time with our new Charts integration -- Enable a daily reminder with a custom time setting in the Daily Reminder Settings -- Backup your data to your files -- Added additional links in the main menu including The Daily Dozen Cookbook -- Updated About information in the main menu +* Adds settings control to select whether to use Imperial or Metric type units throughout the app. -###1.0.1 - 1.0.2 (App Store) -- Misspelling fix +**2.0.0 (App Store)** -###1.0.0 (App Store) -- Initial Release +* Brand new design +* Daily Dozen tracking now persists over multiple days +* Visualize your progress over time with our new Charts integration +* Enable a daily reminder with a custom time setting in the Daily Reminder Settings +* Backup your data to your files +* Added additional links in the main menu including The Daily Dozen Cookbook +* Updated About information in the main menu + +**1.0.1 - 1.0.2 (App Store)** + +* Misspelling fix + +**1.0.0 (App Store)** + +* Initial Release [nutritionfacts.org]: http://nutritionfacts.org "NutritionFacts.org - The Latest in Nutrition Research" [contribute]: https://github.com/nutritionfactsorg/daily-dozen-ios/blob/master/CONTRIBUTING.md "Contribute to the Daily Dozen Android App" [donate]: https://nutritionfacts.org/donate "Donate to NutritionFacts.org" [book]: http://nutritionfacts.org/book "How Not to Die" [christirichards]: http://github.com/christirichards "Christi Richards on GitHub" -[laurenhacker]: http://github.com/lahacker "Lauren Hacker on Github" +[innerfish]: https://github.com/innerfish "Will Webb on Github" [justaninja]: https://github.com/justaninja "Konstantin Khokhlov on Github" +[laurenhacker]: http://github.com/lahacker "Lauren Hacker on Github" diff --git a/README_files/app-store.png b/README_files/app-store.png new file mode 100644 index 00000000..206ba192 Binary files /dev/null and b/README_files/app-store.png differ diff --git a/README_files/github-dailydozen.jpg b/README_files/github-dailydozen.jpg new file mode 100644 index 00000000..40006978 Binary files /dev/null and b/README_files/github-dailydozen.jpg differ