// // EntryListView.swift // Feels (iOS) // // Created by Trey Tartt on 3/6/22. // import SwiftUI struct EntryListView: View { @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 @AppStorage(UserDefaultsStore.Keys.dayViewStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var dayViewStyle: DayViewStyle = .classic @Environment(\.colorScheme) private var colorScheme public let entry: MoodEntryModel private var moodColor: Color { moodTint.color(forMood: entry.mood) } private var isMissing: Bool { entry.moodValue == Mood.missing.rawValue } var body: some View { switch dayViewStyle { case .classic: classicStyle case .minimal: minimalStyle case .compact: compactStyle case .bubble: bubbleStyle case .grid: gridStyle } } // MARK: - Classic Style (Original) private var classicStyle: some View { HStack(spacing: 16) { // Large mood icon with gradient background ZStack { Circle() .fill( LinearGradient( colors: isMissing ? [Color.gray.opacity(0.3), Color.gray.opacity(0.1)] : [moodColor.opacity(0.8), moodColor.opacity(0.4)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 56, height: 56) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 32, height: 32) .foregroundColor(isMissing ? .gray : .white) } .shadow(color: isMissing ? .clear : moodColor.opacity(0.4), radius: 8, x: 0, y: 4) VStack(alignment: .leading, spacing: 6) { HStack(spacing: 6) { Text(Random.weekdayName(fromDate: entry.forDate)) .font(.system(size: 17, weight: .semibold)) .foregroundColor(textColor) Text("•") .foregroundColor(textColor.opacity(0.4)) Text(Random.dayFormat(fromDate: entry.forDate)) .font(.system(size: 17, weight: .medium)) .foregroundColor(textColor.opacity(0.8)) } if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 14, weight: .medium)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 14, weight: .semibold)) .foregroundColor(moodColor) .padding(.horizontal, 10) .padding(.vertical, 4) .background( Capsule() .fill(moodColor.opacity(0.15)) ) } } Spacer() Image(systemName: "chevron.right") .font(.system(size: 14, weight: .semibold)) .foregroundColor(textColor.opacity(0.3)) } .padding(.horizontal, 18) .padding(.vertical, 16) .background( RoundedRectangle(cornerRadius: 18) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) .shadow( color: isMissing ? .clear : moodColor.opacity(colorScheme == .dark ? 0.2 : 0.12), radius: 12, x: 0, y: 4 ) ) .overlay( RoundedRectangle(cornerRadius: 18) .stroke( isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.2), lineWidth: 1 ) ) } // MARK: - Minimal Style private var minimalStyle: some View { HStack(spacing: 14) { // Simple flat circle with icon Circle() .fill(isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(0.15)) .frame(width: 44, height: 44) .overlay( imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 22, height: 22) .foregroundColor(isMissing ? .gray : moodColor) ) VStack(alignment: .leading, spacing: 3) { Text(entry.forDate, format: .dateTime.weekday(.wide).day()) .font(.system(size: 16, weight: .medium)) .foregroundColor(textColor) if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 13)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 13, weight: .medium)) .foregroundColor(moodColor) } } Spacer() } .padding(.horizontal, 16) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) ) } // MARK: - Compact Style (Timeline) private var compactStyle: some View { HStack(spacing: 12) { // Timeline indicator VStack(spacing: 0) { Circle() .fill(isMissing ? Color.gray.opacity(0.4) : moodColor) .frame(width: 12, height: 12) } // Date column VStack(alignment: .leading, spacing: 0) { Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 20, weight: .bold, design: .rounded)) .foregroundColor(textColor) Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 11, weight: .medium)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } .frame(width: 36) // Mood indicator bar if isMissing { RoundedRectangle(cornerRadius: 6) .fill(Color.gray.opacity(0.15)) .frame(height: 32) .overlay( Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 12, weight: .medium)) .foregroundColor(.gray) ) } else { RoundedRectangle(cornerRadius: 6) .fill(moodColor.opacity(0.2)) .frame(height: 32) .overlay( HStack(spacing: 6) { imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 16, height: 16) .foregroundColor(moodColor) Text(entry.moodString) .font(.system(size: 13, weight: .semibold)) .foregroundColor(moodColor) } ) } Spacer(minLength: 0) } .padding(.horizontal, 12) .padding(.vertical, 10) } // MARK: - Bubble Style private var bubbleStyle: some View { HStack(spacing: 0) { // Full-width colored background HStack(spacing: 14) { // Icon imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 28, height: 28) .foregroundColor(isMissing ? .gray : .white) VStack(alignment: .leading, spacing: 2) { Text(entry.forDate, format: .dateTime.weekday(.wide).day()) .font(.system(size: 15, weight: .semibold)) .foregroundColor(isMissing ? textColor : .white) if isMissing { Text(String(localized: "mood_value_missing_tap_to_add")) .font(.system(size: 13)) .foregroundColor(.gray) } else { Text(entry.moodString) .font(.system(size: 13, weight: .medium)) .foregroundColor(.white.opacity(0.85)) } } Spacer() Image(systemName: "chevron.right") .font(.system(size: 12, weight: .semibold)) .foregroundColor(isMissing ? textColor.opacity(0.3) : .white.opacity(0.6)) } .padding(.horizontal, 18) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: 16) .fill( isMissing ? (colorScheme == .dark ? Color(.systemGray5) : Color(.systemGray6)) : moodColor ) ) .shadow( color: isMissing ? .clear : moodColor.opacity(0.35), radius: 8, x: 0, y: 4 ) } } // MARK: - Grid Style (3 per row) private var gridStyle: some View { VStack(spacing: 6) { // Mood icon circle ZStack { Circle() .fill( isMissing ? Color.gray.opacity(0.2) : moodColor ) .aspectRatio(1, contentMode: .fit) imagePack.icon(forMood: entry.mood) .resizable() .aspectRatio(contentMode: .fit) .padding(16) .foregroundColor(isMissing ? .gray : .white) } .shadow( color: isMissing ? .clear : moodColor.opacity(0.3), radius: 6, x: 0, y: 3 ) // Day number Text(entry.forDate, format: .dateTime.day()) .font(.system(size: 16, weight: .bold, design: .rounded)) .foregroundColor(textColor) // Weekday abbreviation Text(entry.forDate, format: .dateTime.weekday(.abbreviated)) .font(.system(size: 11, weight: .medium)) .foregroundColor(textColor.opacity(0.5)) .textCase(.uppercase) } .frame(maxWidth: .infinity) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: 14) .fill(colorScheme == .dark ? Color(.systemGray6) : .white) ) } } struct EntryListView_Previews: PreviewProvider { @MainActor static let fakeData = DataController.shared.randomEntries(count: 1).first! static var previews: some View { VStack(spacing: 8) { EntryListView(entry: EntryListView_Previews.fakeData) EntryListView(entry: EntryListView_Previews.fakeData) } .padding() } }