Files
Reflect/Shared/Views/MonthView/MonthDetailView.swift
Trey T e7648ddd8a Add missing accessibility identifiers to all interactive UI elements
Audit found ~50+ interactive elements (buttons, toggles, pickers, alerts,
links) missing accessibility identifiers across 13 view files. Added
centralized ID definitions and applied them to every entry detail button,
guided reflection control, settings toggle, paywall unlock button,
subscription/IAP button, lock screen control, and photo action dialog.
2026-03-26 07:59:52 -05:00

233 lines
8.5 KiB
Swift

//
// MonthDetailView.swift
// Reflect (iOS)
//
// Created by Trey Tartt on 2/18/22.
//
import SwiftUI
struct MonthDetailView: View {
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@AppStorage(UserDefaultsStore.Keys.deleteEnable.rawValue, store: GroupUserDefaults.groupDefaults) private var deleteEnabled = true
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
private var textColor: Color { theme.currentTheme.labelColor }
@StateObject private var shareImage = ShareImageStateViewModel()
@State private var showingSheet = false
@State private var selectedEntry: MoodEntryModel?
@State private var showingUpdateEntryAlert = false
@State private var showUpdateEntryAlert = false
let monthInt: Int
let yearInt: Int
@State var entries: [MoodEntryModel]
var parentViewModel: DayViewViewModel
let columns = [
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500)),
GridItem(.flexible(minimum: 5, maximum: 500))
]
var image: UIImage {
let image = shareView.asImage(size: CGSize(width: 666, height: 1190))
return image
}
var body: some View {
VStack {
HStack {
Text("\(Random.monthName(fromMonthInt: monthInt)) \(String(yearInt))")
.font(.title)
.foregroundColor(textColor)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Image(systemName: "square.and.arrow.up")
.foregroundColor(textColor)
.padding(.trailing)
.accessibilityIdentifier(AccessibilityID.MonthDetail.shareButton)
.onTapGesture {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
let _image = self.image
self.shareImage.showSheet = true
self.shareImage.selectedShareImage = _image
}
}
.background(
theme.currentTheme.secondaryBGColor
)
createListView()
.padding([.leading, .trailing])
monthDetails
.frame(maxWidth: .infinity, alignment: .leading)
.background(
theme.currentTheme.secondaryBGColor
)
}
.onAppear(perform: {
AnalyticsManager.shared.trackScreen(.monthDetail)
})
.background(
theme.currentTheme.bg
)
.sheet(isPresented: self.$shareImage.showSheet) {
if let uiImage = self.shareImage.selectedShareImage {
ShareSheet(photo: uiImage)
}
}
.alert(DayViewViewModel.updateTitleHeader(forEntry: selectedEntry), isPresented: $showUpdateEntryAlert) {
ForEach(Mood.allValues) { mood in
Button(mood.strValue, action: {
if let selectedEntry = selectedEntry {
parentViewModel.update(entry: selectedEntry, toMood: mood)
}
updateEntries()
showUpdateEntryAlert = false
selectedEntry = nil
})
.accessibilityIdentifier(AccessibilityID.MonthDetail.moodButton(mood.strValue))
}
if let selectedEntry = selectedEntry,
deleteEnabled,
selectedEntry.mood != .missing {
Button(String(localized: "content_view_delete_entry"), action: {
parentViewModel.update(entry: selectedEntry, toMood: .missing)
updateEntries()
showUpdateEntryAlert = false
})
.accessibilityIdentifier(AccessibilityID.MonthDetail.deleteButton)
}
Button(String(localized: "content_view_fill_in_missing_entry_cancel"), role: .cancel, action: {
updateEntries()
selectedEntry = nil
showUpdateEntryAlert = false
})
}
}
private var shareView: some View {
VStack {
HStack {
Text("\(Random.monthName(fromMonthInt: monthInt)) \(String(yearInt))")
.font(.title)
.foregroundColor(textColor)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
.background(
theme.currentTheme.secondaryBGColor
)
createListView()
.padding([.leading, .trailing])
.frame(minWidth: 0, maxWidth: .infinity)
monthDetails
.frame(maxWidth: .infinity, alignment: .leading)
.background(
theme.currentTheme.secondaryBGColor
)
}
.background(
theme.currentTheme.bg
)
}
private func createListView() -> some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 25) {
ForEach(entries, id: \.self) { entry in
listViewEntry(forEntry: entry)
.onTapGesture(perform: {
if entry.canEdit {
selectedEntry = entry
showUpdateEntryAlert = true
}
})
.frame(minWidth: 0, maxWidth: .infinity)
}
}
}
}
private func listViewEntry(forEntry entry: MoodEntryModel) -> some View {
VStack {
if entry.mood == .placeholder {
Text(" ")
.font(.title3)
.foregroundColor(Mood.placeholder.color)
Circle()
.frame(minWidth: 5,
maxWidth: 50,
minHeight: 5,
maxHeight: 50,
alignment: .center)
.foregroundColor(moodTint.color(forMood: entry.mood))
} else {
Text(entry.forDate,
format: Date.FormatStyle().day())
.font(.title3)
.foregroundColor(textColor)
entry.mood.icon
.resizable()
.scaledToFit()
.frame(minWidth: 5,
maxWidth: 50,
minHeight: 5,
maxHeight: 50,
alignment: .center)
.foregroundColor(moodTint.color(forMood: entry.mood))
}
}
}
private func updateEntries() {
parentViewModel.updateData()
let (startDate, endDate) = Date.dateRange(monthInt: monthInt, yearInt: yearInt)
let updatedEntries = DataController.shared.getData(startDate: startDate, endDate: endDate, includedDays: [1,2,3,4,5,6,7])
let padded = MoodEntryFunctions.padMoodEntriesMonth(monthEntries: updatedEntries)
entries = padded
}
private var monthDetails: some View {
VStack {
SmallRollUpHeaderView(entries: entries,
viewType: .constant(.total))
.frame(minHeight: 0, maxHeight: 100)
SmallRollUpHeaderView(entries: entries,
viewType: .constant(.percentageShape))
.frame(minHeight: 0, maxHeight: 100)
.padding(.top, -20)
}
.frame(minHeight: 0, maxHeight: 200)
.padding()
}
}
struct MonthDetailView_Previews: PreviewProvider {
static var previews: some View {
MonthDetailView(monthInt: 5, yearInt: 2022, entries:
DataController.shared.randomEntries(count: 30).sorted(by: {
$0.forDate < $1.forDate
}), parentViewModel: DayViewViewModel(addMonthStartWeekdayPadding: true))
}
}