// // HomeView.swift // Shared // // Created by Trey Tartt on 1/5/22. // import SwiftUI import CoreData import Charts struct DayViewConstants { static let maxHeaderHeight = 200.0 static let minHeaderHeight = 120.0 } struct DayView: View { @Environment(\.managedObjectContext) private var viewContext @AppStorage(UserDefaultsStore.Keys.deleteEnable.rawValue, store: GroupUserDefaults.groupDefaults) private var deleteEnabled = true @AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system @AppStorage(UserDefaultsStore.Keys.moodImages.rawValue, store: GroupUserDefaults.groupDefaults) private var imagePack: MoodImages = .FontAwesome @AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor // store a value that gets changed when user updates custom colors to update the view since the moodTint doesn't change @AppStorage(UserDefaultsStore.Keys.customMoodTintUpdateNumber.rawValue, store: GroupUserDefaults.groupDefaults) private var customMoodTintUpdateNumber: Int = 0 // MARK: top header storage @AppStorage(UserDefaultsStore.Keys.contentViewCurrentSelectedHeaderViewBackDays.rawValue, store: GroupUserDefaults.groupDefaults) private var currentSelectedHeaderViewBackDays: Int = 30 @AppStorage(UserDefaultsStore.Keys.contentViewHeaderTagViewOneViewType.rawValue, store: GroupUserDefaults.groupDefaults) private var firstSwichableHeaderViewType: MainSwitchableViewType = .total @AppStorage(UserDefaultsStore.Keys.contentViewHeaderTagViewTwoViewType.rawValue, store: GroupUserDefaults.groupDefaults) private var secondSwichableHeaderViewType: MainSwitchableViewType = .total @AppStorage(UserDefaultsStore.Keys.contentViewHeaderTag.rawValue, store: GroupUserDefaults.groupDefaults) private var switchableViewSelectedIndex = 1 @AppStorage(UserDefaultsStore.Keys.currentSelectedHeaderViewViewType.rawValue, store: GroupUserDefaults.groupDefaults) private var currentSelectedHeaderViewViewType: MainSwitchableViewType = .total // // MARK: edit row properties @State private var showingSheet = false @State private var selectedEntry: MoodEntry? // // MARK: ?? properties @State private var showTodayInput = true @State private var showUpdateEntryAlert = false @StateObject private var onboardingData = OnboardingDataDataManager.shared @StateObject private var filteredDays = DaysFilterClass.shared // MARK: header properties @State private var headerHeight: CGFloat = DayViewConstants.maxHeaderHeight @State private var headerViewType: MainSwitchableViewType = .total @State private var headerOpacity: Double = 1.0 // @ObservedObject var viewModel: DayViewViewModel var body: some View { mainView .onAppear(perform: { EventLogger.log(event: "show_home_view") }) .sheet(isPresented: $showingSheet) { SettingsView() } .alert(DayViewViewModel.updateTitleHeader(forEntry: selectedEntry), isPresented: $showUpdateEntryAlert) { ForEach(Mood.allValues) { mood in Button(mood.strValue, action: { if let selectedEntry = selectedEntry { viewModel.update(entry: selectedEntry, toMood: mood) } showUpdateEntryAlert = false selectedEntry = nil }) } if let selectedEntry = selectedEntry, deleteEnabled, selectedEntry.mood != .missing { Button(String(localized: "content_view_delete_entry"), action: { viewModel.update(entry: selectedEntry, toMood: Mood.missing) showUpdateEntryAlert = false }) } Button(String(localized: "content_view_fill_in_missing_entry_cancel"), role: .cancel, action: { selectedEntry = nil showUpdateEntryAlert = false }) } } // MARK: functions that do view type work func calcuateViewAlpha() { let perc = (((Double(headerHeight) - DayViewConstants.minHeaderHeight) * 100) / (DayViewConstants.maxHeaderHeight - DayViewConstants.minHeaderHeight)) / 100 headerOpacity = perc } func calculateHeight(minHeight: CGFloat, maxHeight: CGFloat, yOffset: CGFloat) { let newValue = maxHeight + yOffset calcuateViewAlpha() // If scrolling up, yOffset will be a negative number if newValue < minHeight { // SCROLLING UP // Never go smaller than our minimum height headerHeight = minHeight return } if newValue > maxHeight { // SCROLLING UP // Never go smaller than our minimum height headerHeight = maxHeight return } // SCROLLING DOWN headerHeight = newValue } // MARK: Views public var mainView: some View { VStack { settingsButtonView if viewModel.hasNoData { Spacer() EmptyHomeView(viewModel: viewModel) Spacer() } else { ZStack { Text(String(customMoodTintUpdateNumber)) .hidden() VStack { headerView Spacer() } .opacity(headerOpacity) VStack { SmallRollUpHeaderView(entries: getBackEntries(), viewType: $currentSelectedHeaderViewViewType) .frame(height: DayViewConstants.minHeaderHeight) .padding([.trailing, .leading]) .background( theme.currentTheme.secondaryBGColor ) .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) Spacer() } .opacity(1 - headerOpacity) } .frame(height: headerHeight + 20) listView .padding(.top, -25) } } .padding() .padding(.bottom, 5) .background( theme.currentTheme.bg .edgesIgnoringSafeArea(.all) ) .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in PersistenceController.shared.fillInMissingDates() viewModel.updateData() } } private var settingsButtonView: some View { HStack { Spacer() Button(action: { showingSheet.toggle() }, label: { Image(systemName: "gear") .foregroundColor(Color(UIColor.darkGray)) .font(.system(size: 20)) }).padding(.trailing) } } private var headerView: some View { VStack { if ShowBasedOnVoteLogics.isMissingCurrentVote(onboardingData: UserDefaultsStore.getOnboarding()) { AddMoodHeaderView(addItemHeaderClosure: { (mood, date) in viewModel.add(mood: mood, forDate: date, entryType: .header) }) .frame(height: headerHeight) .frame(minWidth: 0, maxWidth: .infinity) } else { // selection hre doesn't work ... TabView(selection: $switchableViewSelectedIndex) { SwitchableView(daysBack: 30, viewType: $firstSwichableHeaderViewType, headerTypeChanged: { viewType in firstSwichableHeaderViewType = viewType currentSelectedHeaderViewViewType = firstSwichableHeaderViewType }) .tag(1) .frame(height: headerHeight) .frame(minWidth: 0, maxWidth: .infinity) .contentShape(Rectangle()) SwitchableView(daysBack: 7, viewType: $secondSwichableHeaderViewType, headerTypeChanged: { viewType in secondSwichableHeaderViewType = viewType currentSelectedHeaderViewViewType = secondSwichableHeaderViewType }) .tag(2) .frame(height: headerHeight) .frame(minWidth: 0, maxWidth: .infinity) .contentShape(Rectangle()) } .tabViewStyle(.page) .onChange(of: switchableViewSelectedIndex) { value in if value == 1 { currentSelectedHeaderViewBackDays = 30 currentSelectedHeaderViewViewType = firstSwichableHeaderViewType } if value == 2 { currentSelectedHeaderViewBackDays = 7 currentSelectedHeaderViewViewType = secondSwichableHeaderViewType } } } } } private var listView: some View { ScrollView { LazyVStack(spacing: 5, pinnedViews: [.sectionHeaders]) { 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: SectionHeaderView(month: month, year: year)) { monthListView(month: month, year: year, entries: entries) } } } } .background( GeometryReader { proxy in let offset = proxy.frame(in: .named("scroll")).minY Color.clear.preference(key: ViewOffsetKey.self, value: offset) } ) } .background( theme.currentTheme.secondaryBGColor ) .coordinateSpace(name: "scroll") .onPreferenceChange(ViewOffsetKey.self) { value in if viewModel.numberOfItems > 10 { calculateHeight(minHeight: DayViewConstants.minHeaderHeight, maxHeight: DayViewConstants.maxHeaderHeight, yOffset: value) } } .cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight]) } func getBackEntries() -> [MoodEntry] { var daysAgo = Calendar.current.date(byAdding: .day, value: -self.currentSelectedHeaderViewBackDays, to: Date())! daysAgo = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: daysAgo)! return PersistenceController.shared.getData(startDate: daysAgo, endDate: Date(), includedDays: [1,2,3,4,5,6,7]) } } // view that make up the list body extension DayView { private func SectionHeaderView(month: Int, year: Int) -> some View { Text("\(Random.monthName(fromMonthInt: month)) \(String(year))") .font(.title) .foregroundColor(textColor) .frame(maxWidth: .infinity, alignment: .leading) .padding() .background( theme.currentTheme.secondaryBGColor ) } private func monthListView(month: Int, year: Int, entries: [MoodEntry]) -> some View { VStack { // for reach all entries ForEach(entries.sorted(by: { return $0.forDate! > $1.forDate! }), id: \.self) { entry in if filteredDays.currentFilters.contains(Int(entry.weekDay)) { // let _ = print(entry.forDate, entry.weekDay, filteredDays.currentFilters) EntryListView(entry: entry) .contentShape(Rectangle()) .onTapGesture(perform: { selectedEntry = entry showUpdateEntryAlert = true }) } } } } } struct ViewOffsetKey: PreferenceKey { typealias Value = CGFloat static var defaultValue = CGFloat.zero static func reduce(value: inout Value, nextValue: () -> Value) { value += nextValue() } } struct DayView_Previews: PreviewProvider { static var previews: some View { DayView(viewModel: DayViewViewModel(addMonthStartWeekdayPadding: false)).environment(\.managedObjectContext, PersistenceController.shared.viewContext) .onAppear(perform: { PersistenceController.shared.populateMemory() }) DayView(viewModel: DayViewViewModel(addMonthStartWeekdayPadding: false)) .preferredColorScheme(.dark) .environment(\.managedObjectContext, PersistenceController.shared.viewContext) } }