// // CustomizeView.swift // Feels (iOS) // // Created by Trey Tartt on 2/19/22. // import SwiftUI struct CustomizeView: View { @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.shape.rawValue, store: GroupUserDefaults.groupDefaults) private var shape: BGShape = .circle @AppStorage(UserDefaultsStore.Keys.personalityPack.rawValue, store: GroupUserDefaults.groupDefaults) private var personalityPack: PersonalityPack = .Default @AppStorage(UserDefaultsStore.Keys.customMoodTintUpdateNumber.rawValue, store: GroupUserDefaults.groupDefaults) private var customMoodTintUpdateNumber: Int = 0 @AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor @AppStorage(UserDefaultsStore.Keys.showNSFW.rawValue, store: GroupUserDefaults.groupDefaults) private var showNSFW: Bool = false @State private var sampleListEntry = PersistenceController.shared.randomEntries(count: 1).first! @State private var showOver18Alert = false @StateObject private var customMoodTint = UserDefaultsStore.getCustomMoodTint() @State var shapeRefreshToggleThing: Bool = false @StateObject private var selectedWidget = StupidAssCustomWidgetObservableObject() @StateObject private var daysFilter = DaysFilterClass() let weekdays = [("Sun", 1), ("mon", 2), ("tue", 3), ("wed", 4), ("thur", 5), ("fri", 6), ("sat", 7)] let iconSets: [(String,String)] = [ ("AppIconGoodImage", "AppIconGood"), ("AppIconAverageImage", "AppIconAverage"), ("AppIconBadImage", "AppIconBad"), ("AppIconBlueGreenImage", "AppIconBlueGreen"), ("AppIconNeonGreenImage", "AppIconNeonGreen"), ("AppIconPinkImage", "AppIconPink"), ("AppIconPurpleImage", "AppIconPurple") ] var body: some View { ScrollView { VStack { Group { createCustomWidget changeIcon themePicker Divider() sampleEntryView pickMoodImagePack } Group { pickMoodTintPack pickTextColor } Divider() dayFilterView Divider() shapePicker Divider() pickPeronsalityPack } } .onAppear(perform: { EventLogger.log(event: "show_customize_view") }) .padding() .sheet(isPresented: $selectedWidget.showFuckingSheet) { if let fuckingWrapped = selectedWidget.fuckingWrapped { CreateWidgetView(customWidget: fuckingWrapped) } } .background( theme.currentTheme.bg .edgesIgnoringSafeArea(.all) ) } private var changeIcon: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { ScrollView(.horizontal) { HStack { Button(action: { UIApplication.shared.setAlternateIconName(nil) EventLogger.log(event: "change_icon_title", withData: ["title": "default"]) }, label: { Image("AppIconImage", bundle: .main) .resizable() .frame(width: 50, height:50) .cornerRadius(10) }) ForEach(iconSets, id: \.self.0){ iconSet in Button(action: { UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in // FIXME: Handle error } EventLogger.log(event: "change_icon_title", withData: ["title": iconSet.1]) }, label: { Image(iconSet.0, bundle: .main) .resizable() .frame(width: 50, height:50) .cornerRadius(10) }) } } .padding() } .background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor)) .padding() .cornerRadius(10) } } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var themePicker: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { HStack { Spacer() ForEach(Theme.allCases, id:\.rawValue) { aTheme in Button(action: { theme = aTheme EventLogger.log(event: "change_theme_id", withData: ["id": aTheme.rawValue]) }, label: { VStack { aTheme.currentTheme.preview .overlay( Circle() .stroke(Color(UIColor.systemGray), style: StrokeStyle(lineWidth: 2)) ) Text(aTheme.title) .foregroundColor(textColor) .font(.body) } }) .contentShape(Rectangle()) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(theme == aTheme ? theme.currentTheme.bgColor : .clear) .padding(-5) ) Spacer() } } .padding(.top) } .padding() } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var createCustomWidget: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { ScrollView(.horizontal) { HStack { ForEach(UserDefaultsStore.getCustomWidgets(), id: \.uuid) { widget in CustomWidgetView(customWidgetModel: widget) .frame(width: 50, height: 50) .cornerRadius(10) .onTapGesture { EventLogger.log(event: "show_widget") selectedWidget.fuckingWrapped = widget.copy() as? CustomWidgetModel selectedWidget.showFuckingSheet = true } } RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.secondaryBGColor) .frame(width: 50, height: 50) .overlay( Image(systemName: "plus") ) .onTapGesture { EventLogger.log(event: "tap_create_new_widget") selectedWidget.fuckingWrapped = CustomWidgetModel.randomWidget selectedWidget.showFuckingSheet = true } } .padding() } .background(RoundedRectangle(cornerRadius: 10).fill().foregroundColor(theme.currentTheme.bgColor)) .padding() .cornerRadius(10) Text("[How to add widget](https://support.apple.com/guide/iphone/add-widgets-iphb8f1bf206/ios)") .accentColor(textColor) .padding(.bottom) } } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var pickMoodImagePack: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { ForEach(MoodImages.allCases, id: \.rawValue) { images in ZStack { VStack { HStack { ForEach(Mood.allValues, id: \.self) { mood in images.icon(forMood: mood) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 35, height: 35) .foregroundColor( moodTint.color(forMood: mood) ) } .frame(minWidth: 0, maxWidth: .infinity) } .contentShape(Rectangle()) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(imagePack == images ? theme.currentTheme.bgColor : .clear) .padding([.top, .bottom], -3) ) .onTapGesture { let impactMed = UIImpactFeedbackGenerator(style: .heavy) impactMed.impactOccurred() imagePack = images EventLogger.log(event: "change_image_pack_id", withData: ["id": images.rawValue]) } if images.rawValue != (MoodImages.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 { Divider() } } } } } .padding() } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var pickMoodTintPack: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { ForEach(MoodTints.defaultOptions, id: \.rawValue) { tint in HStack { ForEach(Mood.allValues, id: \.self) { mood in Circle() .frame(width: 35, height: 35) .foregroundColor( tint.color(forMood: mood) ) } .frame(minWidth: 0, maxWidth: .infinity) } .contentShape(Rectangle()) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(moodTint == tint ? theme.currentTheme.bgColor : .clear) .padding([.top, .bottom], -3) ) .onTapGesture { let impactMed = UIImpactFeedbackGenerator(style: .heavy) impactMed.impactOccurred() moodTint = tint EventLogger.log(event: "change_mood_tint_id", withData: ["id": tint.rawValue]) } Divider() } ZStack { Color.clear Rectangle() .frame(height: 35) .frame(minWidth: 0, maxWidth: .infinity) .foregroundColor(.clear) .contentShape(Rectangle()) .onTapGesture { moodTint = .Custom let impactMed = UIImpactFeedbackGenerator(style: .heavy) impactMed.impactOccurred() } HStack { ColorPicker("", selection: $customMoodTint.colorOne) .onChange(of: customMoodTint.colorOne, perform: { _ in saveCustomMoodTint() }) .labelsHidden() .frame(minWidth: 0, maxWidth: .infinity) ColorPicker("", selection: $customMoodTint.colorTwo) .labelsHidden() .frame(minWidth: 0, maxWidth: .infinity) .onChange(of: customMoodTint.colorTwo, perform: { _ in saveCustomMoodTint() }) ColorPicker("", selection: $customMoodTint.colorThree) .labelsHidden() .frame(minWidth: 0, maxWidth: .infinity) .onChange(of: customMoodTint.colorThree, perform: { _ in saveCustomMoodTint() }) ColorPicker("", selection: $customMoodTint.colorFour) .labelsHidden() .frame(minWidth: 0, maxWidth: .infinity) .onChange(of: customMoodTint.colorFour, perform: { _ in saveCustomMoodTint() }) ColorPicker("", selection: $customMoodTint.colorFive) .labelsHidden() .frame(minWidth: 0, maxWidth: .infinity) .onChange(of: customMoodTint.colorFive, perform: { _ in saveCustomMoodTint() }) } .background( Color.clear ) } .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(moodTint == .Custom ? theme.currentTheme.bgColor : .clear) .padding([.top, .bottom], -3) ) } .padding() } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var pickTextColor: some View { ZStack { theme.currentTheme.secondaryBGColor ColorPicker(String(localized: "customize_view_view_text_color"), selection: $textColor) .padding() .foregroundColor(textColor) } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var sampleEntryView: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { HStack { Spacer() Image(systemName: "arrow.triangle.2.circlepath.circle") .resizable() .frame(width: 20, height: 20, alignment: .trailing) .foregroundColor(Color(UIColor.systemGray)) .onTapGesture { sampleListEntry = PersistenceController.shared.randomEntries(count: 1).first! } } Spacer() }.padding() VStack(alignment:.leading) { Text(String(localized: "customize_view_view_example_row")) .padding([.leading, .top]) .foregroundColor(textColor) Divider() EntryListView(entry: sampleListEntry) .padding() } } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private func saveCustomMoodTint() { UserDefaultsStore.saveCustomMoodTint(customTint: customMoodTint) moodTint = .Custom EventLogger.log(event: "change_mood_tint_id", withData: ["id": MoodTints.Custom.rawValue]) customMoodTintUpdateNumber += 1 } private var pickPeronsalityPack: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { ForEach(PersonalityPack.allCases, id: \.self) { aPack in VStack(spacing: 10) { Text(String(aPack.title())) .font(.body) .foregroundColor(textColor) Text(aPack.randomPushNotificationStrings().title) .font(.body) .foregroundColor(Color(UIColor.systemGray)) Text(aPack.randomPushNotificationStrings().body) .font(.body) .foregroundColor(Color(UIColor.systemGray)) } .frame(minWidth: 0, maxWidth: .infinity) .padding() .contentShape(Rectangle()) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(personalityPack == aPack ? theme.currentTheme.bgColor : .clear) .padding(5) ) .onTapGesture { if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW { showOver18Alert = true EventLogger.log(event: "show_over_18_alert") } else { let impactMed = UIImpactFeedbackGenerator(style: .heavy) impactMed.impactOccurred() personalityPack = aPack EventLogger.log(event: "change_personality_pack", withData: ["pack_title": aPack.title()]) LocalNotification.rescheduleNotifiations() } } .blur(radius: aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW ? 5 : 0) .alert(isPresented: $showOver18Alert) { let primaryButton = Alert.Button.default(Text(String(localized: "customize_view_over18alert_ok"))) { showNSFW = true } let secondaryButton = Alert.Button.cancel(Text(String(localized: "customize_view_over18alert_no"))) { showNSFW = false } return Alert(title: Text(String(localized: "customize_view_over18alert_title")), message: Text(String(localized: "customize_view_over18alert_body")), primaryButton: primaryButton, secondaryButton: secondaryButton) } if aPack.rawValue != (PersonalityPack.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 { Divider() } } } } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var shapePicker: some View { ZStack { theme.currentTheme.secondaryBGColor VStack { HStack { Spacer() Text(shapeRefreshToggleThing.description.localizedLowercase) .hidden() Image(systemName: "arrow.triangle.2.circlepath.circle") .resizable() .frame(width: 20, height: 20, alignment: .trailing) .foregroundColor(Color(UIColor.systemGray)) .onTapGesture { shapeRefreshToggleThing.toggle() } } Spacer() } .padding() VStack(alignment:.leading) { Text(String(localized: "customize_view_view_pick_shape")) .padding([.leading]) .foregroundColor(textColor) Divider() HStack { ForEach(BGShape.allCases, id: \.rawValue) { ashape in ashape.view(withText: Text("20"), bgColor: moodTint.color(forMood: Mood.allValues.randomElement()!), textColor: textColor) .frame(height: 50) .frame(minWidth: 0, maxWidth: .infinity) .onTapGesture { let impactMed = UIImpactFeedbackGenerator(style: .heavy) impactMed.impactOccurred() shape = ashape EventLogger.log(event: "change_mood_shape_id", withData: ["id": shape.rawValue]) } .contentShape(Rectangle()) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(shape == ashape ? theme.currentTheme.bgColor : .clear) .padding(-5) ) } } } .padding() } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } private var dayFilterView: some View { ZStack { theme.currentTheme.secondaryBGColor HStack { ForEach(weekdays.indices, id: \.self) { dayIdx in let day = String(weekdays[dayIdx].0) let value = weekdays[dayIdx].1 Button(day.capitalized, action: { if daysFilter.currentFilters.contains(value) { daysFilter.removeFilter(filter: value) } else { daysFilter.addFilter(newFilter: value) } }) .frame(maxWidth: .infinity) .foregroundColor(daysFilter.currentFilters.contains(value) ? .green : .red) } } .padding() } .fixedSize(horizontal: false, vertical: true) .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight]) } } struct CustomizeView_Previews: PreviewProvider { static var previews: some View { Group { CustomizeView() CustomizeView() .preferredColorScheme(.dark) } } }