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.
233 lines
8.5 KiB
Swift
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))
|
|
}
|
|
}
|