on widgets if its before the voting time show yesterdays vote, if after either show no vote or current vote user shared user defaults
275 lines
9.9 KiB
Swift
275 lines
9.9 KiB
Swift
//
|
|
// ContentView.swift
|
|
// Shared
|
|
//
|
|
// Created by Trey Tartt on 1/5/22.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CoreData
|
|
import Charts
|
|
|
|
struct ContentView: View {
|
|
@Environment(\.managedObjectContext) private var viewContext
|
|
@AppStorage(UserDefaultsStore.Keys.needsOnboarding.rawValue, store: GroupUserDefaults.groupDefaults) private var needsOnboarding = true
|
|
|
|
@State private var showingSheet = false
|
|
@State private var showTodayInput = true
|
|
@State private var selectedEntry: MoodEntry?
|
|
@State private var showUpdateEntryAlert = false
|
|
|
|
@ObservedObject var viewModel = ContentModeViewModel()
|
|
|
|
init(){
|
|
UITabBar.appearance().backgroundColor = UIColor.systemBackground
|
|
}
|
|
|
|
var body: some View {
|
|
TabView {
|
|
mainView
|
|
.tabItem {
|
|
Label(String(localized: "content_view_tab_main"), systemImage: "list.dash")
|
|
}
|
|
|
|
FilterView()
|
|
.tabItem {
|
|
Label(String(localized: "content_view_tab_filter"), systemImage: "calendar.circle")
|
|
}
|
|
|
|
GraphView()
|
|
.tabItem {
|
|
Label(String(localized: "content_view_tab_stats"), systemImage: "chart.line.uptrend.xyaxis")
|
|
}
|
|
}.sheet(isPresented: $needsOnboarding, onDismiss: {
|
|
|
|
}, content: {
|
|
OnboardingMain(onboardingData: viewModel.savedOnboardingData,
|
|
updateBoardingDataClosure: { onboardingData in
|
|
needsOnboarding = false
|
|
viewModel.updateOnboardingData(onboardingData: onboardingData)
|
|
})
|
|
}).alert(String(localized: "content_view_fill_in_missing_entry"), isPresented: $showUpdateEntryAlert) {
|
|
ForEach(Mood.allValues) { mood in
|
|
Button(mood.strValue, action: {
|
|
if let selectedMissingEntry = selectedEntry {
|
|
viewModel.update(entry: selectedMissingEntry, toMood: mood)
|
|
}
|
|
showUpdateEntryAlert = false
|
|
})
|
|
}
|
|
Button(String(localized: "content_view_fill_in_missing_entry_cancel"), role: .cancel, action: {
|
|
selectedEntry = nil
|
|
showUpdateEntryAlert = false
|
|
})
|
|
}
|
|
}
|
|
|
|
private var settingsButtonView: some View {
|
|
HStack {
|
|
Spacer()
|
|
Button(action: {
|
|
showingSheet.toggle()
|
|
}, label: {
|
|
Image(systemName: "gear")
|
|
.foregroundColor(Color(UIColor.darkGray))
|
|
.font(.system(size: 20))
|
|
}).sheet(isPresented: $showingSheet) {
|
|
SettingsView(editedDataClosure: {
|
|
withAnimation{
|
|
viewModel.updateData()
|
|
}
|
|
}, updateBoardingDataClosure: { onboardingData in
|
|
viewModel.updateOnboardingData(onboardingData: onboardingData)
|
|
})
|
|
}.padding(.trailing)
|
|
}
|
|
}
|
|
|
|
private func weekdayName(fromDate date: Date) -> String {
|
|
let weekday = Calendar.current.component(.weekday, from: date)
|
|
let calendar = Calendar.current
|
|
let dayIndex = ((weekday - 1) + (calendar.firstWeekday - 1)) % 7
|
|
return calendar.weekdaySymbols[dayIndex]
|
|
}
|
|
|
|
private func monthName(fromMonthInt: Int) -> String {
|
|
let monthName = DateFormatter().monthSymbols[fromMonthInt-1]
|
|
return monthName
|
|
}
|
|
|
|
private func dayFormat(fromDate date: Date) -> String {
|
|
let components = Calendar.current.dateComponents([.day], from: date)
|
|
let day = components.day!
|
|
|
|
let formatter = NumberFormatter()
|
|
formatter.numberStyle = .ordinal
|
|
return formatter.string(from: NSNumber(integerLiteral: day)) ?? ""
|
|
}
|
|
|
|
private var listView: some View {
|
|
VStack {
|
|
List {
|
|
// for reach year
|
|
ForEach(viewModel.grouped.sorted(by: {
|
|
$0.key > $1.key
|
|
}), id: \.key) { year, months in
|
|
|
|
// for reach month
|
|
ForEach(months.sorted(by: {
|
|
$0.key > $1.key
|
|
}), id: \.key) { month, entries in
|
|
Section(header:
|
|
HStack{
|
|
Text(monthName(fromMonthInt: month))
|
|
.font(.title2)
|
|
.foregroundColor(Color(UIColor.label))
|
|
Text(String(year))
|
|
.font(.title2)
|
|
.foregroundColor(Color(UIColor.label))
|
|
}) {
|
|
// for reach all entries
|
|
ForEach(entries.sorted(by: {
|
|
return $0.forDate! > $1.forDate!
|
|
}), id: \.self) { entry in
|
|
entryListView(entry: entry)
|
|
.onTapGesture(perform: {
|
|
selectedEntry = entry
|
|
showUpdateEntryAlert = true
|
|
})
|
|
}.onDelete(perform: { offsets in
|
|
withAnimation {
|
|
viewModel.delete(offsets: offsets, inMonth: month, inYear: year)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.background(Color.clear.ignoresSafeArea())
|
|
.onAppear {
|
|
// Set the default to clear
|
|
UITableView.appearance().backgroundColor = .clear
|
|
}
|
|
}
|
|
}
|
|
|
|
private func entryListView(entry: MoodEntry) -> some View {
|
|
HStack {
|
|
entry.mood.icon
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 40, height: 40, alignment: .center)
|
|
.foregroundColor(entry.mood.color)
|
|
|
|
VStack {
|
|
HStack {
|
|
Text(weekdayName(fromDate:entry.forDate!))
|
|
.font(.title3)
|
|
.foregroundColor(Color(UIColor.label))
|
|
Text(" - ")
|
|
.padding([.leading, .trailing], -10)
|
|
Text(dayFormat(fromDate:entry.forDate!))
|
|
.font(.title3)
|
|
.foregroundColor(Color(UIColor.label))
|
|
Spacer()
|
|
}
|
|
.multilineTextAlignment(.leading)
|
|
|
|
Text("\(entry.moodString)")
|
|
.font(.body)
|
|
.foregroundColor(Color(UIColor.systemGray))
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var headerView: some View {
|
|
VStack {
|
|
if viewModel.shouldShowVotingHeader() {
|
|
AddMoodHeaderView(addItemHeaderClosure: { (mood, date) in
|
|
withAnimation {
|
|
viewModel.add(mood: mood, forDate: date)
|
|
}
|
|
})
|
|
.frame(height: 180)
|
|
.frame(minWidth: 0, maxWidth: .infinity)
|
|
} else {
|
|
HeaderStatsView(fakeData: false, backDays: 30)
|
|
.frame(height: 180)
|
|
// should match backDays above
|
|
Text(String(format: String(localized: "content_view_header_title"), 30))
|
|
.font(.body)
|
|
.foregroundColor(Color(UIColor.systemGray))
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var emptyView: some View {
|
|
ZStack {
|
|
Color(UIColor.systemBackground)
|
|
|
|
VStack {
|
|
Text(String(localized: "content_view_empty_title"))
|
|
.font(.title)
|
|
.foregroundColor(Color(UIColor.label))
|
|
.padding()
|
|
|
|
Text(String(localized: "content_view_empty_title"))
|
|
.font(.body)
|
|
.foregroundColor(Color(UIColor.label))
|
|
.padding()
|
|
AddMoodHeaderView(addItemHeaderClosure: { (mood, date) in
|
|
withAnimation {
|
|
viewModel.add(mood: mood, forDate: date)
|
|
}
|
|
}, overrideDay: viewModel.shouldShowVotingHeader() ? .Today : .Previous)
|
|
}
|
|
}
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
|
|
.padding()
|
|
}
|
|
|
|
private var mainView: some View {
|
|
ZStack {
|
|
|
|
BGView().equatable()
|
|
|
|
VStack{
|
|
settingsButtonView
|
|
.padding(.top, 50)
|
|
if viewModel.hasNoData {
|
|
Spacer()
|
|
emptyView
|
|
Spacer()
|
|
} else {
|
|
headerView
|
|
listView
|
|
}
|
|
}
|
|
.padding(.bottom)
|
|
}
|
|
}
|
|
}
|
|
|
|
private let itemFormatter: DateFormatter = {
|
|
let formatter = DateFormatter()
|
|
formatter.dateStyle = .short
|
|
formatter.timeStyle = .medium
|
|
return formatter
|
|
}()
|
|
|
|
struct ContentView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
ContentView().environment(\.managedObjectContext, PersistenceController.shared.viewContext)
|
|
.onAppear(perform: {
|
|
PersistenceController.shared.populateMemory()
|
|
})
|
|
|
|
ContentView()
|
|
.preferredColorScheme(.dark)
|
|
.environment(\.managedObjectContext, PersistenceController.shared.viewContext)
|
|
}
|
|
}
|