From 4719ef562d46924eca26f82961610d8ed6646a83 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 15 Jan 2024 19:55:37 -0300 Subject: [PATCH 01/15] Add Stats Traffic date picker --- .../SiteStatsDashboardViewController.swift | 3 +- .../Traffic/StatsTrafficDatePickerView.swift | 82 +++++++++++++ .../StatsTrafficDatePickerViewModel.swift | 108 ++++++++++++++++++ .../Stats/Traffic/TrafficView.swift | 16 +++ WordPress/WordPress.xcodeproj/project.pbxproj | 30 +++++ .../WordPressTest/StatsTrafficTests.swift | 87 ++++++++++++++ 6 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift create mode 100644 WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift create mode 100644 WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift create mode 100644 WordPress/WordPressTest/StatsTrafficTests.swift diff --git a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift index 43c3739aba9d..edbe0851c01f 100644 --- a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift @@ -1,4 +1,5 @@ import UIKit +import SwiftUI enum StatsPeriodType: Int, FilterTabBarItem, CaseIterable { case insights = 0 @@ -267,7 +268,7 @@ private extension SiteStatsDashboardViewController { insightsTableViewController.refreshInsights() case .traffic: if previousSelectedPeriodWasInsights || pageViewControllerIsEmpty { - let viewController = UIViewController() // TODO + let viewController = UIHostingController(rootView: TrafficView()) pageViewController?.setViewControllers([viewController], direction: .forward, animated: false) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift new file mode 100644 index 000000000000..5bc2c9a63c10 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -0,0 +1,82 @@ +import SwiftUI +import DesignSystem + +struct StatsTrafficDatePickerView: View { + typealias ViewModel = StatsTrafficDatePickerViewModel + typealias Period = ViewModel.Period + + @ObservedObject var viewModel = ViewModel() + + var body: some View { + HStack { + Menu { + ForEach(Period.allCases, id: \.self) { period in + Button(period.label, action: { + viewModel.selectedPeriod = period + }) + } + } label: { + Text(viewModel.selectedPeriod.label) + .style(TextStyle.bodyMedium(.emphasized)) + .foregroundColor(Color.DS.Foreground.primary) + Image(systemName: "chevron.down") + .imageScale(.small) + .foregroundColor(Color.DS.Foreground.secondary) + } + .menuStyle(.borderlessButton) + .padding(.vertical, Length.Padding.single) + .padding(.horizontal, Length.Padding.double) + .background(Color.DS.Background.secondary) + .clipShape(RoundedRectangle(cornerRadius: Length.Radius.max)) + .overlay( + RoundedRectangle(cornerRadius: Length.Radius.max) + .strokeBorder(.clear, lineWidth: 0) + ) + + Spacer() + + Text(viewModel.formattedCurrentInterval()) + .style(TextStyle.bodyMedium(.emphasized)) + .foregroundColor(Color.DS.Foreground.primary) + .lineLimit(1) + + Button(action: { + viewModel.goToPreviousDateInterval() + }) { + Image(systemName: "chevron.left") + .imageScale(.medium) + .foregroundColor(Color.DS.Foreground.secondary) + .flipsForRightToLeftLayoutDirection(true) + } + .padding(.trailing, Length.Padding.single) + + let isNextDisabled = !viewModel.isNextDateIntervalAvailable + let enabledColor = Color.DS.Foreground.secondary + let disabledColor = enabledColor.opacity(0.5) + + Button(action: { + viewModel.goToNextDateInterval() + }) { + Image(systemName: "chevron.right") + .imageScale(.medium) + .foregroundColor(isNextDisabled ? disabledColor : enabledColor) + .flipsForRightToLeftLayoutDirection(true) + }.disabled(isNextDisabled) + } + } +} + +private extension StatsTrafficDatePickerViewModel.Period { + var label: String { + switch self { + case .day: + return NSLocalizedString("stats.traffic.day", value: "By day", comment: "The label for the option to show Stats Traffic by day.") + case .week: + return NSLocalizedString("stats.traffic.week", value: "By week", comment: "The label for the option to show Stats Traffic by week.") + case .month: + return NSLocalizedString("stats.traffic.month", value: "By month", comment: "The label for the option to show Stats Traffic by month.") + case .year: + return NSLocalizedString("stats.traffic.year", value: "By year", comment: "The label for the option to show Stats Traffic by year.") + } + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift new file mode 100644 index 000000000000..e804ec28f537 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -0,0 +1,108 @@ +import Foundation + +class StatsTrafficDatePickerViewModel: ObservableObject { + + enum Period: String, CaseIterable { + case day + case week + case month + case year + + var calendarComponent: Calendar.Component { + switch self { + case .day: + return .day + case .week: + return .weekOfYear + case .month: + return .month + case .year: + return .year + } + } + } + + @Published var selectedPeriod: Period { + didSet { + currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: selectedPeriod, oldDateInterval: currentDateInterval) + } + } + + @Published var currentDateInterval: DateInterval + private let now: Date + private static let calendar: Calendar = .current + + private let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "MMM d, yyyy" + return formatter + }() + + var isNextDateIntervalAvailable: Bool { + let nextDateInterval = nextDateInterval() + return nextDateInterval.start <= now + } + + init(now: Date = Date()) { + self.now = now + let defaultPeriod: Period = .day + selectedPeriod = defaultPeriod + let defaultDateInterval = DateInterval(start: now, end: now) // Use today by default + currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: defaultPeriod, oldDateInterval: defaultDateInterval) + } + + func goToPreviousDateInterval() { + guard let newStartDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: currentDateInterval.start), + let newEndDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: currentDateInterval.end) else { + return + } + currentDateInterval = DateInterval(start: newStartDate, end: newEndDate) + } + + func goToNextDateInterval() { + currentDateInterval = nextDateInterval() + } + + func nextDateInterval() -> DateInterval { + guard let newStartDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: currentDateInterval.start), + let newEndDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: currentDateInterval.end) else { + return currentDateInterval + } + return DateInterval(start: newStartDate, end: newEndDate) + } + + private static func calculateDateInterval(for period: Period, oldDateInterval: DateInterval) -> DateInterval { + let anchorDate = oldDateInterval.start // The date in the date interval which stays fixed when the period changes + + switch period { + case .day: + return DateInterval(start: anchorDate, end: anchorDate) + case .week: + guard let weekInterval = calendar.dateInterval(of: .weekOfYear, for: anchorDate), // Spans 8 days since end date is exclusive + let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: weekInterval.end) else { + return oldDateInterval + } + return DateInterval(start: weekInterval.start, end: inclusiveEndDate) + case .month: + guard let monthInterval = calendar.dateInterval(of: .month, for: anchorDate), + let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: monthInterval.end) else { + return oldDateInterval + } + return DateInterval(start: monthInterval.start, end: inclusiveEndDate) + case .year: + guard let yearInterval = calendar.dateInterval(of: .year, for: anchorDate), + let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: yearInterval.end) else { + return oldDateInterval + } + return DateInterval(start: yearInterval.start, end: inclusiveEndDate) + } + } + + func formattedCurrentInterval() -> String { + if currentDateInterval.duration < 24 * 60 * 60 { // different format when showing a single day vs. a period such as a week + return formatter.string(from: currentDateInterval.start) + } else { + return "\(formatter.string(from: currentDateInterval.start)) - \(formatter.string(from: currentDateInterval.end))" + } + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift new file mode 100644 index 000000000000..2fc8b0769d01 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +struct TrafficView: View { + var body: some View { + VStack { + StatsTrafficDatePickerView() + .padding() + + Spacer() + } + } +} + +#Preview { + TrafficView() +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 1af13d9fa9aa..26380669a888 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2698,6 +2698,13 @@ B084E62027E3B7A4007BF7A8 /* SiteIntentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B089140C27E1255D00CF468B /* SiteIntentViewController.swift */; }; B089140D27E1255D00CF468B /* SiteIntentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B089140C27E1255D00CF468B /* SiteIntentViewController.swift */; }; B0960C8727D14BD400BC9717 /* SiteIntentStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0960C8627D14BD400BC9717 /* SiteIntentStep.swift */; }; + B098796F2B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */; }; + B09879702B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */; }; + B09879732B55ED180048256D /* TrafficView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879722B55ED180048256D /* TrafficView.swift */; }; + B09879742B55ED180048256D /* TrafficView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879722B55ED180048256D /* TrafficView.swift */; }; + B09879762B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; + B09879772B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; + B09879792B5730BC0048256D /* StatsTrafficTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879782B5730BC0048256D /* StatsTrafficTests.swift */; }; B0A6DEBF2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A6DEBE2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift */; }; B0AC50DD251E96270039E022 /* ReaderCommentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AC50DC251E96270039E022 /* ReaderCommentsViewController.swift */; }; B0B68A9C252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B68A9A252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift */; }; @@ -7991,6 +7998,10 @@ B07F133D2A16C69800AF7FCF /* PlanSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanSelectionViewController.swift; sourceTree = ""; }; B089140C27E1255D00CF468B /* SiteIntentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentViewController.swift; sourceTree = ""; }; B0960C8627D14BD400BC9717 /* SiteIntentStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentStep.swift; sourceTree = ""; }; + B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerView.swift; sourceTree = ""; }; + B09879722B55ED180048256D /* TrafficView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficView.swift; sourceTree = ""; }; + B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerViewModel.swift; sourceTree = ""; }; + B09879782B5730BC0048256D /* StatsTrafficTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficTests.swift; sourceTree = ""; }; B0A6DEBE2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "AztecPostViewController+MenuTests.swift"; path = "Aztec/AztecPostViewController+MenuTests.swift"; sourceTree = ""; }; B0AC50DC251E96270039E022 /* ReaderCommentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsViewController.swift; sourceTree = ""; }; B0B68A9A252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+CoreDataClass.swift"; sourceTree = ""; }; @@ -11162,6 +11173,7 @@ 984B138C21F65F680004B6A2 /* Period Stats */, 931215E2267F4F92008C3B69 /* Referrer Details */, 98F89D47219E008600190EE6 /* Shared Views */, + B09879712B55ECFF0048256D /* Traffic */, 981C348F2183871100FC2683 /* SiteStatsDashboard.storyboard */, 981C3493218388CA00FC2683 /* SiteStatsDashboardViewController.swift */, 9874766E219630240080967F /* SiteStatsTableViewCells.swift */, @@ -11968,6 +11980,7 @@ 931215E0267DE1C0008C3B69 /* StatsTotalRowDataTests.swift */, 01E78D1C296EA54F00FB6863 /* StatsPeriodHelperTests.swift */, 015BA4EA29A788A300920F4B /* StatsTotalInsightsCellTests.swift */, + B09879782B5730BC0048256D /* StatsTrafficTests.swift */, ); name = Stats; sourceTree = ""; @@ -14963,6 +14976,16 @@ path = "Site Intent"; sourceTree = ""; }; + B09879712B55ECFF0048256D /* Traffic */ = { + isa = PBXGroup; + children = ( + B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */, + B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */, + B09879722B55ED180048256D /* TrafficView.swift */, + ); + path = Traffic; + sourceTree = ""; + }; B50C0C441EF429D500372C65 /* Aztec */ = { isa = PBXGroup; children = ( @@ -21509,6 +21532,7 @@ 435D101A2130C2AB00BB2AA8 /* BlogDetailsViewController+FancyAlerts.swift in Sources */, 8000361D292468D4007D2D26 /* JetpackFullscreenOverlaySiteCreationViewModel.swift in Sources */, 0C23F33E2AC4AEF600EE6117 /* SiteMediaPickerViewController.swift in Sources */, + B09879732B55ED180048256D /* TrafficView.swift in Sources */, F1E72EBA267790110066FF91 /* UIViewController+Dismissal.swift in Sources */, F532AD61253B81320013B42E /* StoriesIntroDataSource.swift in Sources */, E1ECE34F1FA88DA2007FA37A /* StoreContainer.swift in Sources */, @@ -22044,6 +22068,7 @@ E6DAABDD1CF632EC0069D933 /* ReaderSearchSuggestionsViewController.swift in Sources */, 8B3DECAB2388506400A459C2 /* SentryStartupEvent.swift in Sources */, 3FBF21B7267AA17A0098335F /* BloggingRemindersAnimator.swift in Sources */, + B098796F2B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */, C700FAB2258020DB0090938E /* JetpackScanThreatCell.swift in Sources */, D8C31CC62188490000A33B35 /* SiteSegmentsCell.swift in Sources */, 83796699299C048E004A92B9 /* DashboardJetpackInstallCardCell.swift in Sources */, @@ -22810,6 +22835,7 @@ 24ADA24C24F9A4CB001B5DAE /* RemoteFeatureFlagStore.swift in Sources */, 836498CE281735CC00A2C170 /* BloggingPromptsHeaderView.swift in Sources */, 3F43703F2893201400475B6E /* JetpackOverlayViewController.swift in Sources */, + B09879762B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */, 319D6E8519E44F7F0013871C /* SuggestionsTableViewCell.m in Sources */, 01B7590B2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */, 0CB424F12ADEE52A0080B807 /* PostSearchToken.swift in Sources */, @@ -23707,6 +23733,7 @@ 4A76A4BB29D4381100AABF4B /* CommentService+LikesTests.swift in Sources */, 8BB185CE24B62CE100A4CCE8 /* ReaderCardServiceTests.swift in Sources */, 019D699E2A5EA963003B676D /* RootViewCoordinatorTests.swift in Sources */, + B09879792B5730BC0048256D /* StatsTrafficTests.swift in Sources */, 57DF04C1231489A200CC93D6 /* PostCardStatusViewModelTests.swift in Sources */, D821C81B21003AE9002ED995 /* FormattableContentGroupTests.swift in Sources */, 93D86B981C691E71003D8E3E /* LocalCoreDataServiceTests.m in Sources */, @@ -24607,6 +24634,7 @@ FABB231A2602FC2C00C8785C /* GutenbergImgUploadProcessor.swift in Sources */, FABB231B2602FC2C00C8785C /* PluginListViewModel.swift in Sources */, FABB231C2602FC2C00C8785C /* WPStyleGuide+People.swift in Sources */, + B09879772B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */, FA98B61729A3B76A0071AAE8 /* DashboardBlazeCardCell.swift in Sources */, FA3A281C2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift in Sources */, FABB231D2602FC2C00C8785C /* PlanGroup.swift in Sources */, @@ -25271,6 +25299,7 @@ C31852A329673BFC00A78BE9 /* MenusViewController+JetpackBannerViewController.swift in Sources */, FABB25002602FC2C00C8785C /* PlansLoadingIndicatorView.swift in Sources */, FABB25012602FC2C00C8785C /* JetpackScanThreatDetailsViewController.swift in Sources */, + B09879742B55ED180048256D /* TrafficView.swift in Sources */, FABB25022602FC2C00C8785C /* NotificationTextContent.swift in Sources */, FABB25032602FC2C00C8785C /* PostEditor+Publish.swift in Sources */, FABB25042602FC2C00C8785C /* MediaCoordinator.swift in Sources */, @@ -25559,6 +25588,7 @@ FABB25D02602FC2C00C8785C /* SiteCreationWizardLauncher.swift in Sources */, FABB25D12602FC2C00C8785C /* ReaderSitesCardCell.swift in Sources */, 46F583B02624CE790010A723 /* BlockEditorSettingElement+CoreDataProperties.swift in Sources */, + B09879702B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */, 833441C82B1AA9DF00B1FD44 /* SOTWCardView.swift in Sources */, FABB25D22602FC2C00C8785C /* SiteSegmentsWizardContent.swift in Sources */, FABB25D32602FC2C00C8785C /* ReaderTopicToReaderDefaultTopic37to38.swift in Sources */, diff --git a/WordPress/WordPressTest/StatsTrafficTests.swift b/WordPress/WordPressTest/StatsTrafficTests.swift new file mode 100644 index 000000000000..54686950d413 --- /dev/null +++ b/WordPress/WordPressTest/StatsTrafficTests.swift @@ -0,0 +1,87 @@ +import XCTest +@testable import WordPress + +final class StatsTrafficTests: XCTestCase { + private var viewModel: StatsTrafficDatePickerViewModel! + let today = Date("2023-01-18") + let startOfWeek = Date("2023-01-15") + let endOfWeek = Date("2023-01-21") + let startOfMonth = Date("2023-01-01") + let endOfMonth = Date("2023-01-31") + let startOfYear = Date("2023-01-01") + let endOfYear = Date("2023-12-31") + + let yesterday = Date("2023-01-17") + + override func setUpWithError() throws { + viewModel = StatsTrafficDatePickerViewModel(now: today) + } + + override func tearDownWithError() throws { + viewModel = nil + } + + func testDefaultPeriodIsDay() throws { + XCTAssertTrue(viewModel.selectedPeriod == .day) + } + + func testDefaultDateInterval() throws { + let dateInterval = viewModel.currentDateInterval + XCTAssertEqual(today, dateInterval.start) + XCTAssertEqual(today, dateInterval.end) + } + + func testWeekInterval() throws { + viewModel.selectedPeriod = .week + let dateInterval = viewModel.currentDateInterval + XCTAssertTrue(dateInterval.start.isSameDay(as: startOfWeek)) + XCTAssertTrue(dateInterval.end.isSameDay(as: endOfWeek)) + } + + func testMonthInterval() throws { + viewModel.selectedPeriod = .month + let dateInterval = viewModel.currentDateInterval + XCTAssertTrue(dateInterval.start.isSameDay(as: startOfMonth)) + XCTAssertTrue(dateInterval.end.isSameDay(as: endOfMonth)) + } + + func testYearInterval() throws { + viewModel.selectedPeriod = .year + let dateInterval = viewModel.currentDateInterval + XCTAssertTrue(dateInterval.start.isSameDay(as: startOfYear)) + XCTAssertTrue(dateInterval.end.isSameDay(as: endOfYear)) + } + + func testYesterdayInterval() throws { + viewModel.selectedPeriod = .day + viewModel.goToPreviousDateInterval() + XCTAssertEqual(yesterday, viewModel.currentDateInterval.start) + XCTAssertEqual(yesterday, viewModel.currentDateInterval.end) + } + + func testTomorrowIntervalNotAvailable() throws { + viewModel.selectedPeriod = .day + XCTAssertFalse(viewModel.isNextDateIntervalAvailable) + } + + func testStartOfWeekIsusedWhenSwitchingFromWeekToDay() throws { + viewModel.selectedPeriod = .week + viewModel.selectedPeriod = .day + XCTAssertEqual(startOfWeek, viewModel.currentDateInterval.start) + XCTAssertEqual(startOfWeek, viewModel.currentDateInterval.end) + } + +} + +private extension Date { + + init(_ dateString: String) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd" + self = dateFormatter.date(from: dateString)! + } + + func isSameDay(as date: Date) -> Bool { + return Calendar.current.isDate(self, inSameDayAs: date) + } +} From 764294a52f4ae089ea2b9a8595aa347177a2d571 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 22 Feb 2024 19:39:06 -0300 Subject: [PATCH 02/15] Replace Period with StatsPeriodUnit --- .../StatsTrafficDatePickerViewModel.swift | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index e804ec28f537..5615e6e3c538 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -2,27 +2,7 @@ import Foundation class StatsTrafficDatePickerViewModel: ObservableObject { - enum Period: String, CaseIterable { - case day - case week - case month - case year - - var calendarComponent: Calendar.Component { - switch self { - case .day: - return .day - case .week: - return .weekOfYear - case .month: - return .month - case .year: - return .year - } - } - } - - @Published var selectedPeriod: Period { + @Published var selectedPeriod: StatsPeriodUnit { didSet { currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: selectedPeriod, oldDateInterval: currentDateInterval) } @@ -45,7 +25,7 @@ class StatsTrafficDatePickerViewModel: ObservableObject { init(now: Date = Date()) { self.now = now - let defaultPeriod: Period = .day + let defaultPeriod: StatsPeriodUnit = .day selectedPeriod = defaultPeriod let defaultDateInterval = DateInterval(start: now, end: now) // Use today by default currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: defaultPeriod, oldDateInterval: defaultDateInterval) @@ -71,7 +51,7 @@ class StatsTrafficDatePickerViewModel: ObservableObject { return DateInterval(start: newStartDate, end: newEndDate) } - private static func calculateDateInterval(for period: Period, oldDateInterval: DateInterval) -> DateInterval { + private static func calculateDateInterval(for period: StatsPeriodUnit, oldDateInterval: DateInterval) -> DateInterval { let anchorDate = oldDateInterval.start // The date in the date interval which stays fixed when the period changes switch period { From ae91a77ae1da96f434c20cd9754329aafe2aa255 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 22 Feb 2024 20:14:10 -0300 Subject: [PATCH 03/15] Fix date period formatting --- .../Traffic/StatsTrafficDatePickerView.swift | 9 ++---- .../StatsTrafficDatePickerViewModel.swift | 30 +++++++++++++------ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index 5bc2c9a63c10..47d861658674 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -2,15 +2,12 @@ import SwiftUI import DesignSystem struct StatsTrafficDatePickerView: View { - typealias ViewModel = StatsTrafficDatePickerViewModel - typealias Period = ViewModel.Period - - @ObservedObject var viewModel = ViewModel() + @ObservedObject var viewModel = StatsTrafficDatePickerViewModel() var body: some View { HStack { Menu { - ForEach(Period.allCases, id: \.self) { period in + ForEach([StatsPeriodUnit.day, .week, .month, .year], id: \.self) { period in Button(period.label, action: { viewModel.selectedPeriod = period }) @@ -66,7 +63,7 @@ struct StatsTrafficDatePickerView: View { } } -private extension StatsTrafficDatePickerViewModel.Period { +private extension StatsPeriodUnit { var label: String { switch self { case .day: diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 5615e6e3c538..2ae7c2428cbe 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -12,12 +12,6 @@ class StatsTrafficDatePickerViewModel: ObservableObject { private let now: Date private static let calendar: Calendar = .current - private let formatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "MMM d, yyyy" - return formatter - }() - var isNextDateIntervalAvailable: Bool { let nextDateInterval = nextDateInterval() return nextDateInterval.start <= now @@ -79,10 +73,28 @@ class StatsTrafficDatePickerViewModel: ObservableObject { } func formattedCurrentInterval() -> String { - if currentDateInterval.duration < 24 * 60 * 60 { // different format when showing a single day vs. a period such as a week - return formatter.string(from: currentDateInterval.start) + let dateFormatter = selectedPeriod.dateFormatter + if selectedPeriod == .week { + return "\(dateFormatter.string(from: currentDateInterval.start)) - \(dateFormatter.string(from: currentDateInterval.end))" } else { - return "\(formatter.string(from: currentDateInterval.start)) - \(formatter.string(from: currentDateInterval.end))" + return dateFormatter.string(from: currentDateInterval.start) } } } + +private extension StatsPeriodUnit { + var dateFormatter: DateFormatter { + let formatter = DateFormatter() + switch self { + case .day: + formatter.dateFormat = "MMMM d" + case .week: + formatter.dateFormat = "MMM d" + case .month: + formatter.dateFormat = "MMMM" + case .year: + formatter.dateFormat = "yyyy" + } + return formatter + } +} From d6ab761ddf5673dc074e018355cf7389c7bc1476 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Thu, 22 Feb 2024 20:22:06 -0300 Subject: [PATCH 04/15] Integrate date picker --- .../SiteStatsPeriodTableViewController.swift | 45 +++++++++---------- .../Traffic/StatsTrafficDatePickerView.swift | 4 +- .../StatsTrafficDatePickerViewModel.swift | 2 +- .../Stats/Traffic/TrafficView.swift | 16 ------- WordPress/WordPress.xcodeproj/project.pbxproj | 6 --- .../WordPressTest/StatsTrafficTests.swift | 2 +- 6 files changed, 26 insertions(+), 49 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index 74b3e121aec2..5485c6b41c08 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -1,5 +1,6 @@ import UIKit import WordPressFlux +import Combine @objc protocol SiteStatsPeriodDelegate { @objc optional func displayWebViewWithURL(_ url: URL) @@ -51,7 +52,9 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController private var changeReceipt: Receipt? private var viewModel: SiteStatsPeriodViewModel? - private weak var tableHeaderView: SiteStatsTableHeaderView? + private let datePickerViewModel = StatsTrafficDatePickerViewModel() + private var datePickerView: StatsTrafficDatePickerView! + private var cancellables: Set = [] private let analyticsTracker = BottomScrollAnalyticsTracker() @@ -81,6 +84,17 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController tableView.estimatedRowHeight = 500 tableView.estimatedSectionHeaderHeight = SiteStatsTableHeaderView.estimatedHeight sendScrollEventsToBanner() + + datePickerViewModel.$currentDateInterval + .sink(receiveValue: { [weak self] dateInterval in + DispatchQueue.main.async { + self?.selectedDate = dateInterval.start + self?.selectedPeriod = self?.datePickerViewModel.selectedPeriod + self?.refreshData() + } + }) + .store(in: &cancellables) + datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) } override func viewWillAppear(_ animated: Bool) { @@ -98,14 +112,12 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { guard section == 0 else { return nil } - guard let cell = Bundle.main.loadNibNamed("SiteStatsTableHeaderView", owner: nil, options: nil)?.first as? SiteStatsTableHeaderView else { - return nil - } - cell.configure(date: selectedDate, period: selectedPeriod, delegate: self) - cell.animateGhostLayers(viewModel?.isFetchingChart() == true) - tableHeaderView = cell - return cell + let picker = UIView.embedSwiftUIView(datePickerView) + picker.backgroundColor = .red + picker.translatesAutoresizingMaskIntoConstraints = true + picker.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 150) + return picker } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { @@ -179,7 +191,7 @@ private extension SiteStatsPeriodTableViewController { tableHandler.diffableDataSource.apply(viewModel.tableViewSnapshot(), animatingDifferences: false) refreshControl.endRefreshing() - tableHeaderView?.animateGhostLayers(viewModel.isFetchingChart() == true) +// tableHeaderView?.animateGhostLayers(viewModel.isFetchingChart() == true) if viewModel.fetchingFailed() { displayFailureViewIfNecessary() @@ -332,21 +344,6 @@ extension SiteStatsPeriodTableViewController: SiteStatsReferrerDelegate { } } -// MARK: - SiteStatsTableHeaderDelegate Methods - -extension SiteStatsPeriodTableViewController: SiteStatsTableHeaderDateButtonDelegate { - func dateChangedTo(_ newDate: Date?) { - selectedDate = newDate - refreshData() - } - - func didTouchHeaderButton(forward: Bool) { - if let intervalDate = viewModel?.updateDate(forward: forward) { - tableHeaderView?.updateDate(with: intervalDate) - } - } -} - // MARK: Jetpack powered banner private extension SiteStatsPeriodTableViewController { diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index 47d861658674..c5505211c30c 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -2,7 +2,7 @@ import SwiftUI import DesignSystem struct StatsTrafficDatePickerView: View { - @ObservedObject var viewModel = StatsTrafficDatePickerViewModel() + @ObservedObject var viewModel: StatsTrafficDatePickerViewModel var body: some View { HStack { @@ -37,6 +37,8 @@ struct StatsTrafficDatePickerView: View { .foregroundColor(Color.DS.Foreground.primary) .lineLimit(1) + Spacer().frame(width: Length.Padding.single) + Button(action: { viewModel.goToPreviousDateInterval() }) { diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 2ae7c2428cbe..098f0b47ffa4 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -56,7 +56,7 @@ class StatsTrafficDatePickerViewModel: ObservableObject { let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: weekInterval.end) else { return oldDateInterval } - return DateInterval(start: weekInterval.start, end: inclusiveEndDate) + return DateInterval(start: anchorDate, end: inclusiveEndDate) case .month: guard let monthInterval = calendar.dateInterval(of: .month, for: anchorDate), let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: monthInterval.end) else { diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift deleted file mode 100644 index 2fc8b0769d01..000000000000 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/TrafficView.swift +++ /dev/null @@ -1,16 +0,0 @@ -import SwiftUI - -struct TrafficView: View { - var body: some View { - VStack { - StatsTrafficDatePickerView() - .padding() - - Spacer() - } - } -} - -#Preview { - TrafficView() -} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 12c92e39b0aa..28497851737b 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2745,8 +2745,6 @@ B0960C8727D14BD400BC9717 /* SiteIntentStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0960C8627D14BD400BC9717 /* SiteIntentStep.swift */; }; B098796F2B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */; }; B09879702B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */; }; - B09879732B55ED180048256D /* TrafficView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879722B55ED180048256D /* TrafficView.swift */; }; - B09879742B55ED180048256D /* TrafficView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879722B55ED180048256D /* TrafficView.swift */; }; B09879762B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; B09879772B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; B09879792B5730BC0048256D /* StatsTrafficTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879782B5730BC0048256D /* StatsTrafficTests.swift */; }; @@ -8073,7 +8071,6 @@ B089140C27E1255D00CF468B /* SiteIntentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentViewController.swift; sourceTree = ""; }; B0960C8627D14BD400BC9717 /* SiteIntentStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentStep.swift; sourceTree = ""; }; B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerView.swift; sourceTree = ""; }; - B09879722B55ED180048256D /* TrafficView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficView.swift; sourceTree = ""; }; B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerViewModel.swift; sourceTree = ""; }; B09879782B5730BC0048256D /* StatsTrafficTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficTests.swift; sourceTree = ""; }; B0A6DEBE2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "AztecPostViewController+MenuTests.swift"; path = "Aztec/AztecPostViewController+MenuTests.swift"; sourceTree = ""; }; @@ -15135,7 +15132,6 @@ children = ( B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */, B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */, - B09879722B55ED180048256D /* TrafficView.swift */, ); path = Traffic; sourceTree = ""; @@ -21718,7 +21714,6 @@ 435D101A2130C2AB00BB2AA8 /* BlogDetailsViewController+FancyAlerts.swift in Sources */, 8000361D292468D4007D2D26 /* JetpackFullscreenOverlaySiteCreationViewModel.swift in Sources */, 0C23F33E2AC4AEF600EE6117 /* SiteMediaPickerViewController.swift in Sources */, - B09879732B55ED180048256D /* TrafficView.swift in Sources */, F1E72EBA267790110066FF91 /* UIViewController+Dismissal.swift in Sources */, F532AD61253B81320013B42E /* StoriesIntroDataSource.swift in Sources */, E1ECE34F1FA88DA2007FA37A /* StoreContainer.swift in Sources */, @@ -25524,7 +25519,6 @@ C31852A329673BFC00A78BE9 /* MenusViewController+JetpackBannerViewController.swift in Sources */, FABB25002602FC2C00C8785C /* PlansLoadingIndicatorView.swift in Sources */, FABB25012602FC2C00C8785C /* JetpackScanThreatDetailsViewController.swift in Sources */, - B09879742B55ED180048256D /* TrafficView.swift in Sources */, FABB25022602FC2C00C8785C /* NotificationTextContent.swift in Sources */, FABB25032602FC2C00C8785C /* PostEditor+Publish.swift in Sources */, FABB25042602FC2C00C8785C /* MediaCoordinator.swift in Sources */, diff --git a/WordPress/WordPressTest/StatsTrafficTests.swift b/WordPress/WordPressTest/StatsTrafficTests.swift index 54686950d413..a3d1182af90f 100644 --- a/WordPress/WordPressTest/StatsTrafficTests.swift +++ b/WordPress/WordPressTest/StatsTrafficTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import WordPress -final class StatsTrafficTests: XCTestCase { +final class StatsTrafficDatePickerViewModelTests: XCTestCase { private var viewModel: StatsTrafficDatePickerViewModel! let today = Date("2023-01-18") let startOfWeek = Date("2023-01-15") From 981f7aed41a7321c9e04133a31db374fb888cf3e Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Fri, 23 Feb 2024 22:30:50 -0300 Subject: [PATCH 05/15] Adjust to designs --- .../SiteStatsPeriodTableViewController.swift | 31 ++++++++++++------- .../Traffic/StatsTrafficDatePickerView.swift | 22 +++++++++---- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index 5485c6b41c08..626a9ea4a7ed 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -75,6 +75,8 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController // MARK: - View override func viewDidLoad() { + datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) + super.viewDidLoad() clearExpandedRows() @@ -94,7 +96,6 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController } }) .store(in: &cancellables) - datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) } override func viewWillAppear(_ animated: Bool) { @@ -108,16 +109,24 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController } } - // TODO: Replace with a new Date Picker - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard section == 0 else { return nil } - - - let picker = UIView.embedSwiftUIView(datePickerView) - picker.backgroundColor = .red - picker.translatesAutoresizingMaskIntoConstraints = true - picker.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 150) - return picker + override func initTableView() { + let embeddedDatePickerView = UIView.embedSwiftUIView(datePickerView) + view.addSubview(embeddedDatePickerView) + tableView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(tableView) + + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: embeddedDatePickerView.topAnchor, constant: 0), + view.leadingAnchor.constraint(equalTo: embeddedDatePickerView.leadingAnchor, constant: 0), + // embeddedDatePickerView.heightAnchor.constraint(equalToConstant: 100), + view.trailingAnchor.constraint(equalTo: embeddedDatePickerView.trailingAnchor, constant: 0), + embeddedDatePickerView.bottomAnchor.constraint(equalTo: tableView.topAnchor, constant: 0), + view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor, constant: 0), + view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: 0), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0), + ]) + + tableView.refreshControl = refreshControl } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index c5505211c30c..e4ffdbcb4867 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -14,11 +14,12 @@ struct StatsTrafficDatePickerView: View { } } label: { Text(viewModel.selectedPeriod.label) - .style(TextStyle.bodyMedium(.emphasized)) + .style(TextStyle.bodySmall(.emphasized)) .foregroundColor(Color.DS.Foreground.primary) Image(systemName: "chevron.down") - .imageScale(.small) + .font(.system(size: 8)) .foregroundColor(Color.DS.Foreground.secondary) + } .menuStyle(.borderlessButton) .padding(.vertical, Length.Padding.single) @@ -33,7 +34,7 @@ struct StatsTrafficDatePickerView: View { Spacer() Text(viewModel.formattedCurrentInterval()) - .style(TextStyle.bodyMedium(.emphasized)) + .style(TextStyle.bodySmall(.emphasized)) .foregroundColor(Color.DS.Foreground.primary) .lineLimit(1) @@ -43,7 +44,7 @@ struct StatsTrafficDatePickerView: View { viewModel.goToPreviousDateInterval() }) { Image(systemName: "chevron.left") - .imageScale(.medium) + .imageScale(.small) .foregroundColor(Color.DS.Foreground.secondary) .flipsForRightToLeftLayoutDirection(true) } @@ -57,11 +58,20 @@ struct StatsTrafficDatePickerView: View { viewModel.goToNextDateInterval() }) { Image(systemName: "chevron.right") - .imageScale(.medium) + .imageScale(.small) .foregroundColor(isNextDisabled ? disabledColor : enabledColor) .flipsForRightToLeftLayoutDirection(true) }.disabled(isNextDisabled) - } + }.padding(.vertical, Length.Padding.single) + .padding(.horizontal, Length.Padding.double) + .background(Color.DS.Background.primary) + .overlay( + Rectangle() + .frame(height: Length.Border.thin) + .foregroundColor(Color.DS.Foreground.tertiary), + alignment: .bottom + ) + .background(Color.DS.Background.secondary) } } From 75c15bd19b0fb476b8d85d71d18b45df9e730431 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 14:15:43 -0300 Subject: [PATCH 06/15] Clean up code formatting --- .../Period Stats/SiteStatsPeriodTableViewController.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index 626a9ea4a7ed..f37985631c7e 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -114,18 +114,17 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController view.addSubview(embeddedDatePickerView) tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) - + NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: embeddedDatePickerView.topAnchor, constant: 0), view.leadingAnchor.constraint(equalTo: embeddedDatePickerView.leadingAnchor, constant: 0), - // embeddedDatePickerView.heightAnchor.constraint(equalToConstant: 100), view.trailingAnchor.constraint(equalTo: embeddedDatePickerView.trailingAnchor, constant: 0), embeddedDatePickerView.bottomAnchor.constraint(equalTo: tableView.topAnchor, constant: 0), view.leadingAnchor.constraint(equalTo: tableView.leadingAnchor, constant: 0), view.trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: 0), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0), ]) - + tableView.refreshControl = refreshControl } From 182300df351f5a41ed9614610f5b4632fdff9ae5 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 15:20:28 -0300 Subject: [PATCH 07/15] Add Traffic date picker tracking --- .../Period Stats/SiteStatsPeriodTableViewController.swift | 2 +- .../Stats/Traffic/StatsTrafficDatePickerViewModel.swift | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index f37985631c7e..11d5b2f31a18 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -380,7 +380,7 @@ private extension SiteStatsPeriodTableViewController { } }() - WPAppAnalytics.track(event) + WPAppAnalytics.track(event, withBlogID: SiteStatsInformation.sharedInstance.siteID) } func trackBarChartTabSelectionEvent(tab: StatsTrafficBarChartTabs, period: StatsPeriodUnit) { diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 098f0b47ffa4..506e14fa2047 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -31,10 +31,18 @@ class StatsTrafficDatePickerViewModel: ObservableObject { return } currentDateInterval = DateInterval(start: newStartDate, end: newEndDate) + WPAppAnalytics.track( + .statsDateTappedBackward, + withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], + withBlogID: SiteStatsInformation.sharedInstance.siteID) } func goToNextDateInterval() { currentDateInterval = nextDateInterval() + WPAppAnalytics.track( + .statsDateTappedForward, + withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], + withBlogID: SiteStatsInformation.sharedInstance.siteID) } func nextDateInterval() -> DateInterval { From a5ded9e77a839a54663297053435d2ce27b09539 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 18:09:04 -0300 Subject: [PATCH 08/15] Use site date & existing helpers --- .../SiteStatsPeriodTableViewController.swift | 109 ++++-------------- .../SiteStatsDashboardViewController.swift | 21 ++-- .../StatsTrafficDatePickerViewModel.swift | 95 +++++++-------- 3 files changed, 76 insertions(+), 149 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index 11d5b2f31a18..53e4e6176dc0 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -26,34 +26,12 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController return ContextManager.sharedInstance().mainContext }() - var selectedDate: Date? - var selectedPeriod: StatsPeriodUnit? { - didSet { - - guard let selectedPeriod else { - return - } - - trackPeriodAccessEvent(selectedPeriod) - - clearExpandedRows() - - // If this is the first time setting the Period, need to initialize the view model. - // Otherwise, just refresh the data. - if oldValue == nil { - initViewModel() - } else { - refreshData() - } - } - } - private let store = StoreContainer.shared.statsPeriod private var changeReceipt: Receipt? - private var viewModel: SiteStatsPeriodViewModel? - private let datePickerViewModel = StatsTrafficDatePickerViewModel() - private var datePickerView: StatsTrafficDatePickerView! + private var viewModel: SiteStatsPeriodViewModel! + private let datePickerViewModel: StatsTrafficDatePickerViewModel + private let datePickerView: StatsTrafficDatePickerView private var cancellables: Set = [] private let analyticsTracker = BottomScrollAnalyticsTracker() @@ -62,9 +40,10 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController return ImmuTableDiffableViewHandler(takeOver: self, with: analyticsTracker) }() - init() { + init(selectedDate: Date, selectedPeriod: StatsPeriodUnit) { + datePickerViewModel = StatsTrafficDatePickerViewModel(selectedPeriod: selectedPeriod, selectedDate: selectedDate) + datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) super.init(nibName: nil, bundle: nil) - tableStyle = .insetGrouped } @@ -75,8 +54,6 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController // MARK: - View override func viewDidLoad() { - datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) - super.viewDidLoad() clearExpandedRows() @@ -87,11 +64,17 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController tableView.estimatedSectionHeaderHeight = SiteStatsTableHeaderView.estimatedHeight sendScrollEventsToBanner() - datePickerViewModel.$currentDateInterval - .sink(receiveValue: { [weak self] dateInterval in + viewModel = SiteStatsPeriodViewModel(store: store, + selectedDate: datePickerViewModel.selectedDate, + selectedPeriod: datePickerViewModel.selectedPeriod, + periodDelegate: self, + referrerDelegate: self) + addViewModelListeners() + viewModel.startFetchingOverview() + + Publishers.CombineLatest(datePickerViewModel.$selectedDate, datePickerViewModel.$selectedPeriod) + .sink(receiveValue: { [weak self] _, _ in DispatchQueue.main.async { - self?.selectedDate = dateInterval.start - self?.selectedPeriod = self?.datePickerViewModel.selectedPeriod self?.refreshData() } }) @@ -101,11 +84,8 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if !isMovingToParent { - guard let date = selectedDate, let period = selectedPeriod else { - return - } addViewModelListeners() - viewModel?.refreshTrafficOverviewData(withDate: date, forPeriod: period) + viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.selectedDate, forPeriod: datePickerViewModel.selectedPeriod) } } @@ -144,28 +124,12 @@ private extension SiteStatsPeriodTableViewController { // MARK: - View Model - func initViewModel() { - - guard let selectedDate = selectedDate, - let selectedPeriod = selectedPeriod else { - return - } - - viewModel = SiteStatsPeriodViewModel(store: store, - selectedDate: selectedDate, - selectedPeriod: selectedPeriod, - periodDelegate: self, - referrerDelegate: self) - addViewModelListeners() - viewModel?.startFetchingOverview() - } - func addViewModelListeners() { if changeReceipt != nil { return } - changeReceipt = viewModel?.onChange { [weak self] in + changeReceipt = viewModel.onChange { [weak self] in self?.refreshTableView() } } @@ -192,10 +156,6 @@ private extension SiteStatsPeriodTableViewController { // MARK: - Table Refreshing func refreshTableView() { - guard let viewModel = viewModel else { - return - } - tableHandler.diffableDataSource.apply(viewModel.tableViewSnapshot(), animatingDifferences: false) refreshControl.endRefreshing() @@ -213,14 +173,12 @@ private extension SiteStatsPeriodTableViewController { } func refreshData() { - guard let selectedDate = selectedDate, - let selectedPeriod = selectedPeriod, - viewIsVisible() else { + guard viewIsVisible() else { refreshControl.endRefreshing() - return + return } addViewModelListeners() - viewModel?.refreshTrafficOverviewData(withDate: selectedDate, forPeriod: selectedPeriod) + viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.selectedDate, forPeriod: datePickerViewModel.selectedPeriod) } func applyTableUpdates() { @@ -323,8 +281,8 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate { let detailTableViewController = SiteStatsDetailTableViewController.loadFromStoryboard() detailTableViewController.configure(statSection: statSection, - selectedDate: selectedDate, - selectedPeriod: selectedPeriod) + selectedDate: datePickerViewModel.selectedDate, + selectedPeriod: datePickerViewModel.selectedPeriod) navigationController?.pushViewController(detailTableViewController, animated: true) } @@ -338,8 +296,8 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate { } func barChartTabSelected(_ tabIndex: StatsTrafficBarChartTabIndex) { - if let tab = StatsTrafficBarChartTabs(rawValue: tabIndex), let period = selectedPeriod { - trackBarChartTabSelectionEvent(tab: tab, period: period) + if let tab = StatsTrafficBarChartTabs(rawValue: tabIndex) { + trackBarChartTabSelectionEvent(tab: tab, period: datePickerViewModel.selectedPeriod) } } } @@ -366,23 +324,6 @@ private extension SiteStatsPeriodTableViewController { // MARK: - Tracking private extension SiteStatsPeriodTableViewController { - func trackPeriodAccessEvent(_ period: StatsPeriodUnit) { - let event: WPAnalyticsStat = { - switch period { - case .day: - return .statsPeriodDaysAccessed - case .week: - return .statsPeriodWeeksAccessed - case .month: - return .statsPeriodMonthsAccessed - case .year: - return .statsPeriodYearsAccessed - } - }() - - WPAppAnalytics.track(event, withBlogID: SiteStatsInformation.sharedInstance.siteID) - } - func trackBarChartTabSelectionEvent(tab: StatsTrafficBarChartTabs, period: StatsPeriodUnit) { let properties: [AnyHashable: Any] = [StatsPeriodUnit.analyticsPeriodKey: period.description as Any] WPAppAnalytics.track(tab.analyticsEvent, withProperties: properties) diff --git a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift index 6936bf84ba3d..b38569364dd3 100644 --- a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift @@ -79,7 +79,18 @@ class SiteStatsDashboardViewController: UIViewController { private var insightsTableViewController = SiteStatsInsightsTableViewController.loadFromStoryboard() private lazy var periodTableViewControllerDeprecated = SiteStatsPeriodTableViewControllerDeprecated.loadFromStoryboard() - private lazy var trafficTableViewController = SiteStatsPeriodTableViewController() + private lazy var trafficTableViewController = { + let selectedDate: Date + if let date = getLastSelectedDateFromUserDefaults() { + selectedDate = date + } else { + selectedDate = StatsDataHelper.currentDateForSite() + } + + let selectedPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day + + return SiteStatsPeriodTableViewController(selectedDate: selectedDate, selectedPeriod: selectedPeriod) + }() private var pageViewController: UIPageViewController? private lazy var displayedPeriods: [StatsPeriodType] = StatsPeriodType.displayedPeriods @@ -251,7 +262,6 @@ private extension SiteStatsDashboardViewController { func restoreSelectedDateFromUserDefaults() { periodTableViewControllerDeprecated.selectedDate = getLastSelectedDateFromUserDefaults() - trafficTableViewController.selectedDate = getLastSelectedDateFromUserDefaults() removeLastSelectedDateFromUserDefaults() } @@ -279,13 +289,6 @@ private extension SiteStatsDashboardViewController { direction: .forward, animated: false) } - - if trafficTableViewController.selectedDate == nil { - trafficTableViewController.selectedDate = StatsDataHelper.currentDateForSite() - } - - let selectedPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day - trafficTableViewController.selectedPeriod = selectedPeriod case .days, .weeks, .months, .years: if previousSelectedPeriodWasInsights || pageViewControllerIsEmpty { pageViewController?.setViewControllers([periodTableViewControllerDeprecated], diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 506e14fa2047..8a583513c913 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -4,33 +4,28 @@ class StatsTrafficDatePickerViewModel: ObservableObject { @Published var selectedPeriod: StatsPeriodUnit { didSet { - currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: selectedPeriod, oldDateInterval: currentDateInterval) + selectedPeriod.track() } } + @Published var selectedDate: Date - @Published var currentDateInterval: DateInterval - private let now: Date - private static let calendar: Calendar = .current - - var isNextDateIntervalAvailable: Bool { - let nextDateInterval = nextDateInterval() - return nextDateInterval.start <= now + init(selectedPeriod: StatsPeriodUnit, selectedDate: Date) { + self.selectedPeriod = selectedPeriod + self.selectedDate = selectedDate + selectedPeriod.track() } - init(now: Date = Date()) { - self.now = now - let defaultPeriod: StatsPeriodUnit = .day - selectedPeriod = defaultPeriod - let defaultDateInterval = DateInterval(start: now, end: now) // Use today by default - currentDateInterval = StatsTrafficDatePickerViewModel.calculateDateInterval(for: defaultPeriod, oldDateInterval: defaultDateInterval) + var isNextDateIntervalAvailable: Bool { + return StatsPeriodHelper().dateAvailableAfterDate(selectedDate, period: selectedPeriod) } func goToPreviousDateInterval() { - guard let newStartDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: currentDateInterval.start), - let newEndDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: currentDateInterval.end) else { + guard let newStartDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: -1, unit: selectedPeriod) else { return } - currentDateInterval = DateInterval(start: newStartDate, end: newEndDate) + + selectedDate = newStartDate + WPAppAnalytics.track( .statsDateTappedBackward, withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], @@ -38,54 +33,24 @@ class StatsTrafficDatePickerViewModel: ObservableObject { } func goToNextDateInterval() { - currentDateInterval = nextDateInterval() + guard let newStartDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: 1, unit: selectedPeriod) else { + return + } + + selectedDate = newStartDate + WPAppAnalytics.track( .statsDateTappedForward, withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], withBlogID: SiteStatsInformation.sharedInstance.siteID) } - func nextDateInterval() -> DateInterval { - guard let newStartDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: currentDateInterval.start), - let newEndDate = StatsTrafficDatePickerViewModel.calendar.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: currentDateInterval.end) else { - return currentDateInterval - } - return DateInterval(start: newStartDate, end: newEndDate) - } - - private static func calculateDateInterval(for period: StatsPeriodUnit, oldDateInterval: DateInterval) -> DateInterval { - let anchorDate = oldDateInterval.start // The date in the date interval which stays fixed when the period changes - - switch period { - case .day: - return DateInterval(start: anchorDate, end: anchorDate) - case .week: - guard let weekInterval = calendar.dateInterval(of: .weekOfYear, for: anchorDate), // Spans 8 days since end date is exclusive - let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: weekInterval.end) else { - return oldDateInterval - } - return DateInterval(start: anchorDate, end: inclusiveEndDate) - case .month: - guard let monthInterval = calendar.dateInterval(of: .month, for: anchorDate), - let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: monthInterval.end) else { - return oldDateInterval - } - return DateInterval(start: monthInterval.start, end: inclusiveEndDate) - case .year: - guard let yearInterval = calendar.dateInterval(of: .year, for: anchorDate), - let inclusiveEndDate = calendar.date(byAdding: .second, value: -1, to: yearInterval.end) else { - return oldDateInterval - } - return DateInterval(start: yearInterval.start, end: inclusiveEndDate) - } - } - func formattedCurrentInterval() -> String { let dateFormatter = selectedPeriod.dateFormatter - if selectedPeriod == .week { - return "\(dateFormatter.string(from: currentDateInterval.start)) - \(dateFormatter.string(from: currentDateInterval.end))" + if selectedPeriod == .week, let endDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: 1, unit: .week) { + return "\(dateFormatter.string(from: selectedDate)) - \(dateFormatter.string(from: endDate))" } else { - return dateFormatter.string(from: currentDateInterval.start) + return dateFormatter.string(from: selectedDate) } } } @@ -105,4 +70,22 @@ private extension StatsPeriodUnit { } return formatter } + + var event: WPAnalyticsStat { + switch self { + case .day: + return .statsPeriodDaysAccessed + case .week: + return .statsPeriodWeeksAccessed + case .month: + return .statsPeriodMonthsAccessed + case .year: + return .statsPeriodYearsAccessed + } + } + + func track() { + WPAppAnalytics.track(event, withBlogID: SiteStatsInformation.sharedInstance.siteID) + } + } From b9a13218770875e326497430cced4eeea24ac284 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 19:11:15 -0300 Subject: [PATCH 09/15] Fixed date formatting --- .../StatsTrafficDatePickerViewModel.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 8a583513c913..89f564b8298c 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -47,8 +47,8 @@ class StatsTrafficDatePickerViewModel: ObservableObject { func formattedCurrentInterval() -> String { let dateFormatter = selectedPeriod.dateFormatter - if selectedPeriod == .week, let endDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: 1, unit: .week) { - return "\(dateFormatter.string(from: selectedDate)) - \(dateFormatter.string(from: endDate))" + if selectedPeriod == .week, let week = StatsPeriodHelper().weekIncludingDate(selectedDate) { + return "\(dateFormatter.string(from: week.weekStart)) - \(dateFormatter.string(from: week.weekEnd))" } else { return dateFormatter.string(from: selectedDate) } @@ -57,17 +57,19 @@ class StatsTrafficDatePickerViewModel: ObservableObject { private extension StatsPeriodUnit { var dateFormatter: DateFormatter { - let formatter = DateFormatter() + let format: String switch self { case .day: - formatter.dateFormat = "MMMM d" + format = "MMMM d" case .week: - formatter.dateFormat = "MMM d" + format = "MMM d" case .month: - formatter.dateFormat = "MMMM" + format = "MMMM" case .year: - formatter.dateFormat = "yyyy" + format = "yyyy" } + let formatter = DateFormatter() + formatter.setLocalizedDateFormatFromTemplate(format) return formatter } From 0ddf520a95ae75e2f168661aa940d2a757bce3ce Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 21:29:28 -0300 Subject: [PATCH 10/15] Fix date navigation --- .../Traffic/StatsTrafficDatePickerViewModel.swift | 12 ++---------- ...ft => StatsTrafficDatePickerViewModelTests.swift} | 0 2 files changed, 2 insertions(+), 10 deletions(-) rename WordPress/WordPressTest/{StatsTrafficTests.swift => StatsTrafficDatePickerViewModelTests.swift} (100%) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 89f564b8298c..4370942ac582 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -20,11 +20,7 @@ class StatsTrafficDatePickerViewModel: ObservableObject { } func goToPreviousDateInterval() { - guard let newStartDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: -1, unit: selectedPeriod) else { - return - } - - selectedDate = newStartDate + selectedDate = Calendar.current.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: selectedDate) ?? selectedDate WPAppAnalytics.track( .statsDateTappedBackward, @@ -33,11 +29,7 @@ class StatsTrafficDatePickerViewModel: ObservableObject { } func goToNextDateInterval() { - guard let newStartDate = StatsPeriodHelper().calculateEndDate(from: selectedDate, offsetBy: 1, unit: selectedPeriod) else { - return - } - - selectedDate = newStartDate + selectedDate = Calendar.current.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: selectedDate) ?? selectedDate WPAppAnalytics.track( .statsDateTappedForward, diff --git a/WordPress/WordPressTest/StatsTrafficTests.swift b/WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift similarity index 100% rename from WordPress/WordPressTest/StatsTrafficTests.swift rename to WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift From 596d9870c71664a320aa249490a85418cb4a422b Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 21:43:38 -0300 Subject: [PATCH 11/15] Refactor to simplify naming --- .../SiteStatsPeriodTableViewController.swift | 18 +++---- .../Traffic/StatsTrafficDatePickerView.swift | 12 ++--- .../StatsTrafficDatePickerViewModel.swift | 54 +++++++++---------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index 53e4e6176dc0..c9caed62ef2d 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -41,7 +41,7 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController }() init(selectedDate: Date, selectedPeriod: StatsPeriodUnit) { - datePickerViewModel = StatsTrafficDatePickerViewModel(selectedPeriod: selectedPeriod, selectedDate: selectedDate) + datePickerViewModel = StatsTrafficDatePickerViewModel(period: selectedPeriod, date: selectedDate) datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) super.init(nibName: nil, bundle: nil) tableStyle = .insetGrouped @@ -65,14 +65,14 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController sendScrollEventsToBanner() viewModel = SiteStatsPeriodViewModel(store: store, - selectedDate: datePickerViewModel.selectedDate, - selectedPeriod: datePickerViewModel.selectedPeriod, + selectedDate: datePickerViewModel.date, + selectedPeriod: datePickerViewModel.period, periodDelegate: self, referrerDelegate: self) addViewModelListeners() viewModel.startFetchingOverview() - Publishers.CombineLatest(datePickerViewModel.$selectedDate, datePickerViewModel.$selectedPeriod) + Publishers.CombineLatest(datePickerViewModel.$date, datePickerViewModel.$period) .sink(receiveValue: { [weak self] _, _ in DispatchQueue.main.async { self?.refreshData() @@ -85,7 +85,7 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController super.viewWillAppear(animated) if !isMovingToParent { addViewModelListeners() - viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.selectedDate, forPeriod: datePickerViewModel.selectedPeriod) + viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.date, forPeriod: datePickerViewModel.period) } } @@ -178,7 +178,7 @@ private extension SiteStatsPeriodTableViewController { return } addViewModelListeners() - viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.selectedDate, forPeriod: datePickerViewModel.selectedPeriod) + viewModel.refreshTrafficOverviewData(withDate: datePickerViewModel.date, forPeriod: datePickerViewModel.period) } func applyTableUpdates() { @@ -281,8 +281,8 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate { let detailTableViewController = SiteStatsDetailTableViewController.loadFromStoryboard() detailTableViewController.configure(statSection: statSection, - selectedDate: datePickerViewModel.selectedDate, - selectedPeriod: datePickerViewModel.selectedPeriod) + selectedDate: datePickerViewModel.date, + selectedPeriod: datePickerViewModel.period) navigationController?.pushViewController(detailTableViewController, animated: true) } @@ -297,7 +297,7 @@ extension SiteStatsPeriodTableViewController: SiteStatsPeriodDelegate { func barChartTabSelected(_ tabIndex: StatsTrafficBarChartTabIndex) { if let tab = StatsTrafficBarChartTabs(rawValue: tabIndex) { - trackBarChartTabSelectionEvent(tab: tab, period: datePickerViewModel.selectedPeriod) + trackBarChartTabSelectionEvent(tab: tab, period: datePickerViewModel.period) } } } diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index e4ffdbcb4867..20ba6e53d0dd 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -9,11 +9,11 @@ struct StatsTrafficDatePickerView: View { Menu { ForEach([StatsPeriodUnit.day, .week, .month, .year], id: \.self) { period in Button(period.label, action: { - viewModel.selectedPeriod = period + viewModel.period = period }) } } label: { - Text(viewModel.selectedPeriod.label) + Text(viewModel.period.label) .style(TextStyle.bodySmall(.emphasized)) .foregroundColor(Color.DS.Foreground.primary) Image(systemName: "chevron.down") @@ -33,7 +33,7 @@ struct StatsTrafficDatePickerView: View { Spacer() - Text(viewModel.formattedCurrentInterval()) + Text(viewModel.formattedCurrentPeriod()) .style(TextStyle.bodySmall(.emphasized)) .foregroundColor(Color.DS.Foreground.primary) .lineLimit(1) @@ -41,7 +41,7 @@ struct StatsTrafficDatePickerView: View { Spacer().frame(width: Length.Padding.single) Button(action: { - viewModel.goToPreviousDateInterval() + viewModel.goToPreviousPeriod() }) { Image(systemName: "chevron.left") .imageScale(.small) @@ -50,12 +50,12 @@ struct StatsTrafficDatePickerView: View { } .padding(.trailing, Length.Padding.single) - let isNextDisabled = !viewModel.isNextDateIntervalAvailable + let isNextDisabled = !viewModel.isNextPeriodAvailable let enabledColor = Color.DS.Foreground.secondary let disabledColor = enabledColor.opacity(0.5) Button(action: { - viewModel.goToNextDateInterval() + viewModel.goToNextPeriod() }) { Image(systemName: "chevron.right") .imageScale(.small) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index 4370942ac582..dc217a8bbdeb 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -2,49 +2,48 @@ import Foundation class StatsTrafficDatePickerViewModel: ObservableObject { - @Published var selectedPeriod: StatsPeriodUnit { + @Published var period: StatsPeriodUnit { didSet { - selectedPeriod.track() + period.track() } } - @Published var selectedDate: Date + @Published var date: Date - init(selectedPeriod: StatsPeriodUnit, selectedDate: Date) { - self.selectedPeriod = selectedPeriod - self.selectedDate = selectedDate - selectedPeriod.track() + init(period: StatsPeriodUnit, date: Date) { + self.period = period + self.date = date + period.track() } - var isNextDateIntervalAvailable: Bool { - return StatsPeriodHelper().dateAvailableAfterDate(selectedDate, period: selectedPeriod) + var isNextPeriodAvailable: Bool { + return StatsPeriodHelper().dateAvailableAfterDate(date, period: period) } - func goToPreviousDateInterval() { - selectedDate = Calendar.current.date(byAdding: selectedPeriod.calendarComponent, value: -1, to: selectedDate) ?? selectedDate - - WPAppAnalytics.track( - .statsDateTappedBackward, - withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], - withBlogID: SiteStatsInformation.sharedInstance.siteID) + func goToPreviousPeriod() { + date = Calendar.current.date(byAdding: period.calendarComponent, value: -1, to: date) ?? date + track(isNext: false) } - func goToNextDateInterval() { - selectedDate = Calendar.current.date(byAdding: selectedPeriod.calendarComponent, value: 1, to: selectedDate) ?? selectedDate - - WPAppAnalytics.track( - .statsDateTappedForward, - withProperties: [StatsPeriodUnit.analyticsPeriodKey: selectedPeriod.description as Any], - withBlogID: SiteStatsInformation.sharedInstance.siteID) + func goToNextPeriod() { + date = Calendar.current.date(byAdding: period.calendarComponent, value: 1, to: date) ?? date + track(isNext: true) } - func formattedCurrentInterval() -> String { - let dateFormatter = selectedPeriod.dateFormatter - if selectedPeriod == .week, let week = StatsPeriodHelper().weekIncludingDate(selectedDate) { + func formattedCurrentPeriod() -> String { + let dateFormatter = period.dateFormatter + if period == .week, let week = StatsPeriodHelper().weekIncludingDate(date) { return "\(dateFormatter.string(from: week.weekStart)) - \(dateFormatter.string(from: week.weekEnd))" } else { - return dateFormatter.string(from: selectedDate) + return dateFormatter.string(from: date) } } + + func track(isNext: Bool) { + WPAppAnalytics.track( + isNext ? .statsDateTappedForward : .statsDateTappedBackward, + withProperties: [StatsPeriodUnit.analyticsPeriodKey: period.description as Any], + withBlogID: SiteStatsInformation.sharedInstance.siteID) + } } private extension StatsPeriodUnit { @@ -81,5 +80,4 @@ private extension StatsPeriodUnit { func track() { WPAppAnalytics.track(event, withBlogID: SiteStatsInformation.sharedInstance.siteID) } - } From 6f641074177b752ef1a0e9de81ba91655db91a92 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Mon, 26 Feb 2024 22:05:51 -0300 Subject: [PATCH 12/15] Updated tests --- WordPress/WordPress.xcodeproj/project.pbxproj | 8 +-- ...StatsTrafficDatePickerViewModelTests.swift | 69 ++++--------------- 2 files changed, 18 insertions(+), 59 deletions(-) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 28497851737b..f8655a90fd95 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2747,7 +2747,7 @@ B09879702B55EC010048256D /* StatsTrafficDatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */; }; B09879762B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; B09879772B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */; }; - B09879792B5730BC0048256D /* StatsTrafficTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879782B5730BC0048256D /* StatsTrafficTests.swift */; }; + B09879792B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09879782B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift */; }; B0A6DEBF2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A6DEBE2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift */; }; B0AC50DD251E96270039E022 /* ReaderCommentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0AC50DC251E96270039E022 /* ReaderCommentsViewController.swift */; }; B0B68A9C252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B68A9A252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift */; }; @@ -8072,7 +8072,7 @@ B0960C8627D14BD400BC9717 /* SiteIntentStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIntentStep.swift; sourceTree = ""; }; B098796E2B55EC010048256D /* StatsTrafficDatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerView.swift; sourceTree = ""; }; B09879752B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerViewModel.swift; sourceTree = ""; }; - B09879782B5730BC0048256D /* StatsTrafficTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficTests.swift; sourceTree = ""; }; + B09879782B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTrafficDatePickerViewModelTests.swift; sourceTree = ""; }; B0A6DEBE2626335F00B5B8EF /* AztecPostViewController+MenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "AztecPostViewController+MenuTests.swift"; path = "Aztec/AztecPostViewController+MenuTests.swift"; sourceTree = ""; }; B0AC50DC251E96270039E022 /* ReaderCommentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsViewController.swift; sourceTree = ""; }; B0B68A9A252FA91E0001B28C /* UserSuggestion+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+CoreDataClass.swift"; sourceTree = ""; }; @@ -12123,7 +12123,7 @@ 931215E0267DE1C0008C3B69 /* StatsTotalRowDataTests.swift */, 01E78D1C296EA54F00FB6863 /* StatsPeriodHelperTests.swift */, 015BA4EA29A788A300920F4B /* StatsTotalInsightsCellTests.swift */, - B09879782B5730BC0048256D /* StatsTrafficTests.swift */, + B09879782B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift */, ); name = Stats; sourceTree = ""; @@ -23934,7 +23934,7 @@ 4A76A4BB29D4381100AABF4B /* CommentService+LikesTests.swift in Sources */, 8BB185CE24B62CE100A4CCE8 /* ReaderCardServiceTests.swift in Sources */, 019D699E2A5EA963003B676D /* RootViewCoordinatorTests.swift in Sources */, - B09879792B5730BC0048256D /* StatsTrafficTests.swift in Sources */, + B09879792B5730BC0048256D /* StatsTrafficDatePickerViewModelTests.swift in Sources */, 57DF04C1231489A200CC93D6 /* PostCardStatusViewModelTests.swift in Sources */, D821C81B21003AE9002ED995 /* FormattableContentGroupTests.swift in Sources */, 93D86B981C691E71003D8E3E /* LocalCoreDataServiceTests.m in Sources */, diff --git a/WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift b/WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift index a3d1182af90f..6340505bfaad 100644 --- a/WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift +++ b/WordPress/WordPressTest/StatsTrafficDatePickerViewModelTests.swift @@ -3,74 +3,33 @@ import XCTest final class StatsTrafficDatePickerViewModelTests: XCTestCase { private var viewModel: StatsTrafficDatePickerViewModel! - let today = Date("2023-01-18") - let startOfWeek = Date("2023-01-15") - let endOfWeek = Date("2023-01-21") - let startOfMonth = Date("2023-01-01") - let endOfMonth = Date("2023-01-31") - let startOfYear = Date("2023-01-01") - let endOfYear = Date("2023-12-31") - - let yesterday = Date("2023-01-17") + let today = Date("2024-01-18") override func setUpWithError() throws { - viewModel = StatsTrafficDatePickerViewModel(now: today) + viewModel = StatsTrafficDatePickerViewModel(period: .day, date: today) } override func tearDownWithError() throws { viewModel = nil } - func testDefaultPeriodIsDay() throws { - XCTAssertTrue(viewModel.selectedPeriod == .day) - } - - func testDefaultDateInterval() throws { - let dateInterval = viewModel.currentDateInterval - XCTAssertEqual(today, dateInterval.start) - XCTAssertEqual(today, dateInterval.end) - } - - func testWeekInterval() throws { - viewModel.selectedPeriod = .week - let dateInterval = viewModel.currentDateInterval - XCTAssertTrue(dateInterval.start.isSameDay(as: startOfWeek)) - XCTAssertTrue(dateInterval.end.isSameDay(as: endOfWeek)) - } - - func testMonthInterval() throws { - viewModel.selectedPeriod = .month - let dateInterval = viewModel.currentDateInterval - XCTAssertTrue(dateInterval.start.isSameDay(as: startOfMonth)) - XCTAssertTrue(dateInterval.end.isSameDay(as: endOfMonth)) - } + func testNavigation() throws { - func testYearInterval() throws { - viewModel.selectedPeriod = .year - let dateInterval = viewModel.currentDateInterval - XCTAssertTrue(dateInterval.start.isSameDay(as: startOfYear)) - XCTAssertTrue(dateInterval.end.isSameDay(as: endOfYear)) - } + let yesterday = Date("2024-01-17") + let aWeekEarlier = Date("2024-01-10") + let aMonthEarlier = Date("2023-12-10") - func testYesterdayInterval() throws { - viewModel.selectedPeriod = .day - viewModel.goToPreviousDateInterval() - XCTAssertEqual(yesterday, viewModel.currentDateInterval.start) - XCTAssertEqual(yesterday, viewModel.currentDateInterval.end) - } + viewModel.goToPreviousPeriod() + XCTAssertEqual(yesterday, viewModel.date) - func testTomorrowIntervalNotAvailable() throws { - viewModel.selectedPeriod = .day - XCTAssertFalse(viewModel.isNextDateIntervalAvailable) - } + viewModel.period = .week + viewModel.goToPreviousPeriod() + XCTAssertEqual(aWeekEarlier, viewModel.date) - func testStartOfWeekIsusedWhenSwitchingFromWeekToDay() throws { - viewModel.selectedPeriod = .week - viewModel.selectedPeriod = .day - XCTAssertEqual(startOfWeek, viewModel.currentDateInterval.start) - XCTAssertEqual(startOfWeek, viewModel.currentDateInterval.end) + viewModel.period = .month + viewModel.goToPreviousPeriod() + XCTAssertEqual(aMonthEarlier, viewModel.date) } - } private extension Date { From aa34722d5de5a8b188fee96faafe29efb023dac0 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 27 Feb 2024 10:12:21 -0300 Subject: [PATCH 13/15] Fix code alignment Co-authored-by: Povilas Staskus --- .../ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index 20ba6e53d0dd..6320c23f9021 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -61,7 +61,8 @@ struct StatsTrafficDatePickerView: View { .imageScale(.small) .foregroundColor(isNextDisabled ? disabledColor : enabledColor) .flipsForRightToLeftLayoutDirection(true) - }.disabled(isNextDisabled) + } + .disabled(isNextDisabled) }.padding(.vertical, Length.Padding.single) .padding(.horizontal, Length.Padding.double) .background(Color.DS.Background.primary) From 16eabfbc45b7e69a3b701459a1973eb065033717 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 27 Feb 2024 10:13:42 -0300 Subject: [PATCH 14/15] Adjusted date picker navigation button spacing Co-authored-by: Povilas Staskus --- .../ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift index 6320c23f9021..f0d307637fa0 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerView.swift @@ -38,7 +38,7 @@ struct StatsTrafficDatePickerView: View { .foregroundColor(Color.DS.Foreground.primary) .lineLimit(1) - Spacer().frame(width: Length.Padding.single) + Spacer().frame(width: Length.Padding.split) Button(action: { viewModel.goToPreviousPeriod() From 18b7d4b5b993ba175eef2168360a1679d8be9f44 Mon Sep 17 00:00:00 2001 From: Paul Von Schrottky Date: Tue, 27 Feb 2024 10:38:36 -0300 Subject: [PATCH 15/15] Clean up code --- .../SiteStatsPeriodTableViewController.swift | 5 ++--- .../Stats/SiteStatsDashboardViewController.swift | 13 ++++++------- .../Traffic/StatsTrafficDatePickerViewModel.swift | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift index c9caed62ef2d..210d13a67c87 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/SiteStatsPeriodTableViewController.swift @@ -40,8 +40,8 @@ final class SiteStatsPeriodTableViewController: SiteStatsBaseTableViewController return ImmuTableDiffableViewHandler(takeOver: self, with: analyticsTracker) }() - init(selectedDate: Date, selectedPeriod: StatsPeriodUnit) { - datePickerViewModel = StatsTrafficDatePickerViewModel(period: selectedPeriod, date: selectedDate) + init(date: Date, period: StatsPeriodUnit) { + datePickerViewModel = StatsTrafficDatePickerViewModel(period: period, date: date) datePickerView = StatsTrafficDatePickerView(viewModel: datePickerViewModel) super.init(nibName: nil, bundle: nil) tableStyle = .insetGrouped @@ -159,7 +159,6 @@ private extension SiteStatsPeriodTableViewController { tableHandler.diffableDataSource.apply(viewModel.tableViewSnapshot(), animatingDifferences: false) refreshControl.endRefreshing() -// tableHeaderView?.animateGhostLayers(viewModel.isFetchingChart() == true) if viewModel.fetchingFailed() { displayFailureViewIfNecessary() diff --git a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift index b38569364dd3..6c3bad1377bf 100644 --- a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift @@ -1,5 +1,4 @@ import UIKit -import SwiftUI enum StatsPeriodType: Int, FilterTabBarItem, CaseIterable { case insights = 0 @@ -80,16 +79,16 @@ class SiteStatsDashboardViewController: UIViewController { private var insightsTableViewController = SiteStatsInsightsTableViewController.loadFromStoryboard() private lazy var periodTableViewControllerDeprecated = SiteStatsPeriodTableViewControllerDeprecated.loadFromStoryboard() private lazy var trafficTableViewController = { - let selectedDate: Date - if let date = getLastSelectedDateFromUserDefaults() { - selectedDate = date + let date: Date + if let selectedDate = getLastSelectedDateFromUserDefaults() { + date = selectedDate } else { - selectedDate = StatsDataHelper.currentDateForSite() + date = StatsDataHelper.currentDateForSite() } - let selectedPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day + let currentPeriod = StatsPeriodUnit(rawValue: currentSelectedPeriod.rawValue - 1) ?? .day - return SiteStatsPeriodTableViewController(selectedDate: selectedDate, selectedPeriod: selectedPeriod) + return SiteStatsPeriodTableViewController(date: date, period: currentPeriod) }() private var pageViewController: UIPageViewController? private lazy var displayedPeriods: [StatsPeriodType] = StatsPeriodType.displayedPeriods diff --git a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift index dc217a8bbdeb..fe237d363dce 100644 --- a/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Traffic/StatsTrafficDatePickerViewModel.swift @@ -20,12 +20,12 @@ class StatsTrafficDatePickerViewModel: ObservableObject { } func goToPreviousPeriod() { - date = Calendar.current.date(byAdding: period.calendarComponent, value: -1, to: date) ?? date + date = StatsDataHelper.calendar.date(byAdding: period.calendarComponent, value: -1, to: date) ?? date track(isNext: false) } func goToNextPeriod() { - date = Calendar.current.date(byAdding: period.calendarComponent, value: 1, to: date) ?? date + date = StatsDataHelper.calendar.date(byAdding: period.calendarComponent, value: 1, to: date) ?? date track(isNext: true) }