Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// ProfileFeature+Dependencies.swift
// DevLogPresentation
//
// Created by opfic on 6/15/26.
//

import ComposableArchitecture
import DevLogDomain

extension DependencyValues {
var profileFetchUserDataUseCase: FetchUserDataUseCase {
get { self[ProfileFetchUserDataKey.self] }
set { self[ProfileFetchUserDataKey.self] = newValue }
}

var profileFetchImageDataUseCase: FetchProfileImageDataUseCase {
get { self[ProfileFetchImageDataKey.self] }
set { self[ProfileFetchImageDataKey.self] = newValue }
}

var profileFetchTodosUseCase: FetchTodosUseCase {
get { self[ProfileFetchTodosKey.self] }
set { self[ProfileFetchTodosKey.self] = newValue }
}

var profileUpsertStatusMessageUseCase: UpsertStatusMessageUseCase {
get { self[ProfileUpsertStatusMessageKey.self] }
set { self[ProfileUpsertStatusMessageKey.self] = newValue }
}

var profileFetchHeatmapActivityTypesUseCase: FetchHeatmapActivityTypesUseCase {
get { self[ProfileFetchHeatmapTypesKey.self] }
set { self[ProfileFetchHeatmapTypesKey.self] = newValue }
}

var profileUpdateHeatmapActivityTypesUseCase: UpdateHeatmapActivityTypesUseCase {
get { self[ProfileUpdateHeatmapTypesKey.self] }
set { self[ProfileUpdateHeatmapTypesKey.self] = newValue }
}
}

private enum ProfileFetchUserDataKey: DependencyKey {
static var liveValue: FetchUserDataUseCase {
preconditionFailure("FetchUserDataUseCase must be provided.")
}

static var testValue: FetchUserDataUseCase {
liveValue
}
}

private enum ProfileFetchImageDataKey: DependencyKey {
static var liveValue: FetchProfileImageDataUseCase {
preconditionFailure("FetchProfileImageDataUseCase must be provided.")
}

static var testValue: FetchProfileImageDataUseCase {
liveValue
}
}

private enum ProfileFetchTodosKey: DependencyKey {
static var liveValue: FetchTodosUseCase {
preconditionFailure("FetchTodosUseCase must be provided.")
}

static var testValue: FetchTodosUseCase {
liveValue
}
}

private enum ProfileUpsertStatusMessageKey: DependencyKey {
static var liveValue: UpsertStatusMessageUseCase {
preconditionFailure("UpsertStatusMessageUseCase must be provided.")
}

static var testValue: UpsertStatusMessageUseCase {
liveValue
}
}

private enum ProfileFetchHeatmapTypesKey: DependencyKey {
static var liveValue: FetchHeatmapActivityTypesUseCase {
preconditionFailure("FetchHeatmapActivityTypesUseCase must be provided.")
}

static var testValue: FetchHeatmapActivityTypesUseCase {
liveValue
}
}

private enum ProfileUpdateHeatmapTypesKey: DependencyKey {
static var liveValue: UpdateHeatmapActivityTypesUseCase {
preconditionFailure("UpdateHeatmapActivityTypesUseCase must be provided.")
}

static var testValue: UpdateHeatmapActivityTypesUseCase {
liveValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// ProfileFeature+State.swift
// DevLogPresentation
//
// Created by opfic on 6/15/26.
//

import DevLogCore
import Foundation

extension ProfileFeature.State {
var isLoading: Bool {
loading.isLoading
}

var quarterTitle: String {
guard let start = selectedQuarterStart else { return "" }
let year = Calendar.current.component(.year, from: start)
let month = Calendar.current.component(.month, from: start)
let quarter = ((month - 1) / 3) + 1
return String.localizedStringWithFormat(
String(localized: "profile_year_quarter_format"),
String(year),
String(quarter)
)
}

var selectedDayActivities: [HeatmapActivityItem] {
guard let selectedDay else { return [] }
let dayStart = Calendar.current.startOfDay(for: selectedDay.date)
let activities = dayActivitiesByDate[dayStart] ?? []

return activities.filter { activity in
!Set(activity.activityKinds).isDisjoint(with: selectedActivityKinds)
}
}

var isCreatedActivitySelected: Bool {
get { selectedActivityKinds.contains(.created) }
set { setActivityKind(.created, isSelected: newValue) }
}

var isCompletedActivitySelected: Bool {
get { selectedActivityKinds.contains(.completed) }
set { setActivityKind(.completed, isSelected: newValue) }
}

var isDeletedActivitySelected: Bool {
get { selectedActivityKinds.contains(.deleted) }
set { setActivityKind(.deleted, isSelected: newValue) }
}

var isCreatedActivityToggleDisabled: Bool {
selectedActivityKinds == [.created]
}

var isCompletedActivityToggleDisabled: Bool {
selectedActivityKinds == [.completed]
}

var isDeletedActivityToggleDisabled: Bool {
selectedActivityKinds == [.deleted]
}

var canMoveToPreviousQuarter: Bool {
ProfileHeatmapBuilder.canMoveToQuarter(offsetMonths: -3, state: self)
}

var canMoveToNextQuarter: Bool {
ProfileHeatmapBuilder.canMoveToQuarter(offsetMonths: 3, state: self)
}

var isViewingCurrentQuarter: Bool {
guard let selectedQuarterStart,
let currentQuarterStart = ProfileHeatmapBuilder.quarterStart(for: Date()) else {
return false
}
return selectedQuarterStart == currentQuarterStart
}

var availableQuarterYears: [Int] {
guard let earliestQuarterStart,
let currentQuarterStart = ProfileHeatmapBuilder.quarterStart(for: Date()) else {
return [selectedQuarterPickerYear]
}
let earliestYear = Calendar.current.component(.year, from: earliestQuarterStart)
let currentYear = Calendar.current.component(.year, from: currentQuarterStart)
return Array(stride(from: currentYear, through: earliestYear, by: -1))
}

func quarterStartForPicker(quarter: Int) -> Date? {
ProfileHeatmapBuilder.quarterStart(year: selectedQuarterPickerYear, quarter: quarter)
}

func isQuarterSelectableForPicker(_ quarter: Int) -> Bool {
guard let quarterStart = quarterStartForPicker(quarter: quarter) else { return false }
return ProfileHeatmapBuilder.canSelectQuarter(quarterStart, state: self)
}

func isQuarterSelectedForPicker(_ quarter: Int) -> Bool {
quarterStartForPicker(quarter: quarter) == selectedQuarterStart
}

private mutating func setActivityKind(_ activityKind: ActivityKind, isSelected: Bool) {
if isSelected {
selectedActivityKinds.insert(activityKind)
} else if 1 < selectedActivityKinds.count {
selectedActivityKinds.remove(activityKind)
}
}
}
Loading
Loading