Skip to content

Commit

Permalink
Create HomeView and HomeViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
banghuazhao committed Nov 8, 2024
1 parent 857f41c commit d5fa3bd
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 115 deletions.
15 changes: 11 additions & 4 deletions AdRevenueWatch/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ enum Dependency {
adMobReportRepository: AdMobReportRepository.newRepo
)
}

static var accessTokenUseCase: some AccessTokenUseCaseProtocol {
AccessTokenUseCase(
accessTokenProvider: KeychainAccessTokenProvider()
Expand All @@ -44,11 +44,18 @@ enum Dependency {
googleAuthUseCase: googleAuthUseCase
)
}

@MainActor static var adMobViewModel: AdMobReportViewModel {
AdMobReportViewModel(
@MainActor static var homeViewModel: HomeViewModel {
HomeViewModel(
googleAuthUseCase: googleAuthUseCase,
adMobAccountUseCase: adMobAccountUseCase,
sessionManager: sessionManager
)
}

@MainActor static func createAdMobViewModel(adMobPublisherID: String) -> AdMobReportViewModel {
AdMobReportViewModel(
adMobPublisherID: adMobPublisherID,
adMobReportUseCase: adMobReportUseCase
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public struct AdMobReportUseCase: AdMobReportUseCaseProtocol {
accountID: String,
reportRequest: AdMobReportRequestEntity
) async throws -> AdMobReportEntity {
return try await adMobReportRepository.fetchReport(accountID: accountID, reportRequest: reportRequest)
try await adMobReportRepository.fetchReport(accountID: accountID, reportRequest: reportRequest)
}
}
86 changes: 26 additions & 60 deletions AdRevenueWatch/Presentation/View/AdMobReport/AdMobReportView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,74 +15,40 @@ struct AdMobReportView: View {
var body: some View {
ZStack {
switch viewModel.state {
case .fetchingAccounts:
case .loading:
ProgressView()
case .fetchingReport, .reports:
reportView
}
}
.task {
await viewModel.onLoad()
}
}

var reportView: some View {
NavigationStack {
ZStack {
if let totalEarningsData = viewModel.totalEarningsData,
let adsMetricDatas = viewModel.adsMetricDatas {
ScrollView {
TotalEarningsView(totalEarningsData: totalEarningsData)

HStack {
Image(systemName: "calendar")
Picker("Select a date range", selection: $viewModel.selectedDateRangeOption) {
ForEach(AdMobReportViewModel.DateRangeOption.allCases) { option in
Text(option.rawValue)
.tag(option)
.font(.callout)
}
case let .reports(totalEarningsData, adsMetricDatas):
ScrollView {
TotalEarningsView(totalEarningsData: totalEarningsData)

HStack {
Image(systemName: "calendar")
Picker("Select a date range", selection: $viewModel.selectedDateRangeOption) {
ForEach(AdMobReportViewModel.DateRangeOption.allCases) { option in
Text(option.rawValue)
.tag(option)
.font(.callout)
}
.pickerStyle(.menu)
Spacer()
}
.padding(.horizontal)

AdsActivityPerformanceView(metrics: adsMetricDatas)
.pickerStyle(.menu)
Spacer()
}
} else {
ProgressView()
}
}
.navigationTitle("AdMob Report")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Select Account", systemImage: "person.crop.circle") {
Picker("", selection: $viewModel.selectedPublisherID) {
ForEach(viewModel.adMobPublisherIDs, id: \.self) { adMobPublisherID in
Text(adMobPublisherID)
.tag(adMobPublisherID)
}
}
.padding(.horizontal)

Button("Logout") {
Task {
await viewModel.onTapLogout()
}
}
}
AdsActivityPerformanceView(metrics: adsMetricDatas)
}
}
.onChange(of: viewModel.selectedPublisherID) { selectedPublisherID in
if !selectedPublisherID.isEmpty {
Task {
await viewModel.fetchAdMobReport(accountID: selectedPublisherID)
}
.onChange(of: viewModel.selectedDateRangeOption) { _ in
viewModel.onChangeOfSelectedDateRangeOption()
}
case .error:
Text("error")
}
.onChange(of: viewModel.selectedDateRangeOption) { _ in
viewModel.onChangeOfSelectedDateRangeOption()
}
}
.task {
await viewModel.fetchAdMobReport()
}
.refreshable {
await viewModel.fetchAdMobReport()
}
}
}
6 changes: 3 additions & 3 deletions AdRevenueWatch/Presentation/View/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ struct AppView: View {
OnboardingView(
viewModel: Dependency.onboardingViewModel
)
case .adMobReport:
AdMobReportView(
viewModel: Dependency.adMobViewModel
case .home:
HomeView(
viewModel: Dependency.homeViewModel
)
}
}
Expand Down
58 changes: 58 additions & 0 deletions AdRevenueWatch/Presentation/View/HomeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Created by Banghua Zhao on 14/09/2024
// Copyright Apps Bay Limited. All rights reserved.
//

import SwiftUI

struct HomeView: View {
@StateObject var viewModel: HomeViewModel

init(viewModel: @autoclosure @escaping () -> HomeViewModel) {
_viewModel = StateObject(wrappedValue: viewModel())
}

var body: some View {
ZStack {
switch viewModel.state {
case .loading:
ProgressView()
case let .report(adMobPublisherID):
NavigationStack {
AdMobReportView(
viewModel: Dependency.createAdMobViewModel(adMobPublisherID: adMobPublisherID)
)
.navigationTitle("AdMob Report")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Select Account", systemImage: "person.crop.circle") {
Picker("", selection: $viewModel.selectedPublisherID) {
ForEach(viewModel.adMobPublisherIDs, id: \.self) { adMobPublisherID in
Text(adMobPublisherID)
.tag(adMobPublisherID)
}
}

Button("Logout") {
Task {
await viewModel.onTapLogout()
}
}
}
}
}
.onChange(of: viewModel.selectedPublisherID) { selectedPublisherID in
if !selectedPublisherID.isEmpty {
viewModel.fetchReport(for: selectedPublisherID)
}
}
}
case .error:
Text("error")
}
}
.task {
await viewModel.onLoad()
}
}
}
70 changes: 25 additions & 45 deletions AdRevenueWatch/Presentation/ViewModel/AdMobReportViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import Foundation
@MainActor
class AdMobReportViewModel: ObservableObject {
enum State {
case fetchingAccounts
case fetchingReport
case reports
case loading
case reports(totalEarningsData: TotalEarningData, adsMetricDatas: [AdMetricData])
case error
}

enum DateRangeOption: String, CaseIterable, Identifiable {
Expand All @@ -22,47 +22,25 @@ class AdMobReportViewModel: ObservableObject {

var id: String { rawValue }
}

private let googleAuthUseCase: any GoogleAuthUseCaseProtocol
private let adMobAccountUseCase: any AdMobAccountUseCaseProtocol

private let adMobPublisherID: String
private let adMobReportUseCase: any AdMobReportUseCaseProtocol
private let sessionManager: any SessionManagerProtocol

@Published private(set) var state: State = .fetchingAccounts
@Published private(set) var adMobPublisherIDs: [String] = []
@Published var selectedPublisherID: String = ""
@Published private(set) var state: State = .loading

@Published private(set) var adMobReportEntity: AdMobReportEntity?
@Published private(set) var totalEarningsData: TotalEarningData?
@Published var selectedDateRangeOption: DateRangeOption = .last7DaysVsPrevious7Days
@Published private(set) var adsMetricDatas: [AdMetricData]?

init(
googleAuthUseCase: some GoogleAuthUseCaseProtocol = Dependency.googleAuthUseCase,
adMobAccountUseCase: some AdMobAccountUseCaseProtocol = Dependency.adMobAccountUseCase,
adMobReportUseCase: some AdMobReportUseCaseProtocol = Dependency.adMobReportUseCase,
sessionManager: some SessionManagerProtocol = Dependency.sessionManager
adMobPublisherID: String,
adMobReportUseCase: some AdMobReportUseCaseProtocol = Dependency.adMobReportUseCase
) {
self.googleAuthUseCase = googleAuthUseCase
self.adMobAccountUseCase = adMobAccountUseCase
self.adMobPublisherID = adMobPublisherID
self.adMobReportUseCase = adMobReportUseCase
self.sessionManager = sessionManager
}

func onLoad() async {
state = .fetchingAccounts

do {
let adMobAccounts = try await adMobAccountUseCase.fetchAccounts()
adMobPublisherIDs = adMobAccounts.map(\.publisherID)
selectedPublisherID = adMobPublisherIDs.first ?? ""
await fetchAdMobReport(accountID: selectedPublisherID)
} catch {
state = .reports
}
}

func fetchAdMobReport(accountID: String) async {
state = .fetchingReport
func fetchAdMobReport() async {
state = .loading
let today = Date()
let calendar = Calendar.current
guard let twoMonthsAgo = calendar.date(byAdding: .month, value: -2, to: today) else { return }
Expand All @@ -74,32 +52,34 @@ class AdMobReportViewModel: ObservableObject {

do {
adMobReportEntity = try await adMobReportUseCase.fetchReport(
accountID: accountID,
accountID: adMobPublisherID,
reportRequest: reportRequest
)
totalEarningsData = adMobReportEntity?.toTotalEarningData()
adsMetricDatas = adMobReportEntity?.toAdsMetricDatas(dateRangeOption: selectedDateRangeOption)
guard let adMobReportEntity else {
state = .error
return
}
let totalEarningsData = adMobReportEntity.toTotalEarningData()
let adsMetricDatas = adMobReportEntity.toAdsMetricDatas(dateRangeOption: selectedDateRangeOption)
state = .reports(totalEarningsData: totalEarningsData, adsMetricDatas: adsMetricDatas)
} catch {
print("Failed to fetch report: \(error.localizedDescription)")
state = .error
}
state = .reports
}

func onTapLogout() async {
await googleAuthUseCase.signOut()
sessionManager.refreshAuthenticationStatus()
}

func onChangeOfSelectedDateRangeOption() {
guard let adMobReportEntity else { return }
adsMetricDatas = adMobReportEntity.toAdsMetricDatas(dateRangeOption: selectedDateRangeOption)
let totalEarningsData = adMobReportEntity.toTotalEarningData()
let adsMetricDatas = adMobReportEntity.toAdsMetricDatas(dateRangeOption: selectedDateRangeOption)
state = .reports(totalEarningsData: totalEarningsData, adsMetricDatas: adsMetricDatas)
}
}

extension AdMobReportEntity {
func toTotalEarningData() -> TotalEarningData {
let sortedDailyData = dailyPerformances.sorted { $0.date < $1.date }

let todayEarnings: Decimal = sortedDailyData.last?.estimatedEarnings ?? 0
let yesterdayEarnings: Decimal = sortedDailyData.dropLast().last?.estimatedEarnings ?? 0
var thisMonthEarnings: Decimal = 0
Expand Down
4 changes: 2 additions & 2 deletions AdRevenueWatch/Presentation/ViewModel/AppViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Foundation
class AppViewModel: ObservableObject {
enum State {
case onboarding
case adMobReport
case home
}

@Published var state: State = .onboarding
Expand All @@ -21,7 +21,7 @@ class AppViewModel: ObservableObject {
@MainActor
func monitorLoginStatus() async {
for await isLoggedIn in sessionManager.isLoggedInStream {
state = isLoggedIn ? .adMobReport : .onboarding
state = isLoggedIn ? .home : .onboarding
}
}
}
Loading

0 comments on commit d5fa3bd

Please sign in to comment.