From 0c07c509f3226e1080994107e45f9bbf75785d9b Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:01:55 -0500 Subject: [PATCH] Add feedback submission sheet Also fixed a bug where locations with overlapping close and open times would show "Closing Soon" for 30 minutes just to then switch to "Open" when it rolled over. --- Shared/Components/TigerCenterParsers.swift | 10 ++ .../FDMPMealPeriods.swift | 0 .../{Static => Constant}/TCtoFDMPMap.swift | 0 TigerDine.xcodeproj/project.pbxproj | 20 +-- TigerDine/ContentView.swift | 9 + TigerDine/Views/DonationView.swift | 1 + TigerDine/Views/FeedbackView.swift | 170 ++++++++++++++++++ TigerDine/Views/Fragments/MailView.swift | 56 ++++++ 8 files changed, 256 insertions(+), 10 deletions(-) rename Shared/Data/{Static => Constant}/FDMPMealPeriods.swift (100%) rename Shared/Data/{Static => Constant}/TCtoFDMPMap.swift (100%) create mode 100644 TigerDine/Views/FeedbackView.swift create mode 100644 TigerDine/Views/Fragments/MailView.swift diff --git a/Shared/Components/TigerCenterParsers.swift b/Shared/Components/TigerCenterParsers.swift index 50886fc..59a9827 100644 --- a/Shared/Components/TigerCenterParsers.swift +++ b/Shared/Components/TigerCenterParsers.swift @@ -42,6 +42,16 @@ func parseMultiOpenStatus(diningTimes: [DiningTimes]?, referenceTime: Date) -> O closeTime: diningTimes[i].closeTime, referenceTime: referenceTime ) + + // If we're closing soon for the current opening period, but then we're opening again at the same time we close in the next + // period, we should just stick with "Open" because showing "Closing Soon" for 30 minutes and then going straight back to + // "Open" is kinda confusing. This issue has been observed with Petals specifically. + if openStatus == .closingSoon && i != diningTimes.indices.last { + if diningTimes[i].closeTime == diningTimes[i + 1].openTime { + openStatus = .open + } + } + // If the first event pass came back closed, loop again in case a later event has a different status. This is mostly to // accurately catch Gracie's/Brick City Cafe's multiple open periods each day. if openStatus != .closed { diff --git a/Shared/Data/Static/FDMPMealPeriods.swift b/Shared/Data/Constant/FDMPMealPeriods.swift similarity index 100% rename from Shared/Data/Static/FDMPMealPeriods.swift rename to Shared/Data/Constant/FDMPMealPeriods.swift diff --git a/Shared/Data/Static/TCtoFDMPMap.swift b/Shared/Data/Constant/TCtoFDMPMap.swift similarity index 100% rename from Shared/Data/Static/TCtoFDMPMap.swift rename to Shared/Data/Constant/TCtoFDMPMap.swift diff --git a/TigerDine.xcodeproj/project.pbxproj b/TigerDine.xcodeproj/project.pbxproj index d18f674..c7555d6 100644 --- a/TigerDine.xcodeproj/project.pbxproj +++ b/TigerDine.xcodeproj/project.pbxproj @@ -57,8 +57,8 @@ membershipExceptions = ( Components/SharedUtils.swift, Components/TigerCenterParsers.swift, - Data/Static/FDMPMealPeriods.swift, - Data/Static/TCtoFDMPMap.swift, + Data/Constant/FDMPMealPeriods.swift, + Data/Constant/TCtoFDMPMap.swift, Types/TigerCenterTypes.swift, ); target = 374CDA572F10A19500D8C50A /* TigerDineWidgets */; @@ -292,7 +292,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -304,7 +304,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining.Widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -325,7 +325,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -337,7 +337,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining.Widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -481,7 +481,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -500,7 +500,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -518,7 +518,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 33; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -537,7 +537,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.2.1; PRODUCT_BUNDLE_IDENTIFIER = "dev.ninjacheetah.RIT-Dining"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/TigerDine/ContentView.swift b/TigerDine/ContentView.swift index a93e0e4..98a5d64 100644 --- a/TigerDine/ContentView.swift +++ b/TigerDine/ContentView.swift @@ -19,6 +19,7 @@ struct ContentView: View { @State private var loadFailed: Bool = false @State private var showingDonationSheet: Bool = false + @State private var showingFeedbackSheet: Bool = false @State private var searchText: String = "" @State private var path = NavigationPath() @@ -149,6 +150,11 @@ struct ContentView: View { Image(systemName: "info.circle") Text("About") } + Button(action: { + showingFeedbackSheet = true + }) { + Label("Feedback", systemImage: "paperplane") + } Button(action: { showingDonationSheet = true }) { @@ -187,6 +193,9 @@ struct ContentView: View { .sheet(isPresented: $showingDonationSheet) { DonationView() } + .sheet(isPresented: $showingFeedbackSheet) { + FeedbackView() + } } } diff --git a/TigerDine/Views/DonationView.swift b/TigerDine/Views/DonationView.swift index b1ab440..d08c3ce 100644 --- a/TigerDine/Views/DonationView.swift +++ b/TigerDine/Views/DonationView.swift @@ -10,6 +10,7 @@ import SwiftUI struct DonationView: View { @Environment(\.dismiss) var dismiss @Environment(\.openURL) private var openURL + @State private var symbolDrawn: Bool = true var body: some View { diff --git a/TigerDine/Views/FeedbackView.swift b/TigerDine/Views/FeedbackView.swift new file mode 100644 index 0000000..cb020f3 --- /dev/null +++ b/TigerDine/Views/FeedbackView.swift @@ -0,0 +1,170 @@ +// +// FeedbackView.swift +// TigerDine +// +// Created by Campbell on 2/16/26. +// + +import SwiftUI +import MessageUI + +struct FeedbackView: View { + @Environment(\.dismiss) var dismiss + @Environment(\.openURL) private var openURL + + @State private var showingMailView = false + @State private var mailResult: Result? = nil + + var body: some View { + NavigationView { + ScrollView { + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: "paperplane") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundStyle(Color.accentColor) + Text("Submit Feedback") + .fontWeight(.bold) + .font(.title) + } + Text("Did I break something? Oops.") + Text("Or maybe you just have a suggestion to make TigerDine even cooler. Either way, I'd love to hear your feedback! (Or maybe the hours for a location are off, in which case that feedback is RIT's to handle.)") + .foregroundStyle(.secondary) + Text("Incorrect Location Hours") + .padding(.top, 12) + Button(action: { + openURL(URL(string: "https://www.rit.edu/dining/locations")!) + }) { + HStack(alignment: .center) { + Image(systemName: "clock.badge.questionmark") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundStyle(Color.accentColor) + VStack(alignment: .leading) { + Text("Confirm Against the RIT Website") + .fontWeight(.bold) + Text("Check that the hours displayed in TigerDine match RIT's website.") + .foregroundStyle(.secondary) + .multilineTextAlignment(.leading) + } + Spacer() + Image(systemName: "chevron.forward") + } + .padding(.all, 6) + .background ( + RoundedRectangle(cornerRadius: 8) + .fill(Color.secondary.opacity(0.1)) + ) + } + .buttonStyle(.plain) + Button(action: { + openURL(URL(string: "https://www.rit.edu/its/support")!) + }) { + HStack(alignment: .center) { + Image(systemName: "clock.badge.exclamationmark") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundStyle(Color.accentColor) + VStack(alignment: .leading) { + Text("Submit an ITS Ticket") + .fontWeight(.bold) + Text("If hours are also incorrect on RIT's website, submit a ticket to ITS.") + .foregroundStyle(.secondary) + .multilineTextAlignment(.leading) + } + Spacer() + Image(systemName: "chevron.forward") + } + .padding(.all, 6) + .background ( + RoundedRectangle(cornerRadius: 8) + .fill(Color.secondary.opacity(0.1)) + ) + } + .buttonStyle(.plain) + Text("If the hours do not match between TigerDine and RIT's website, please contact me instead and I'll look into it.") + .foregroundStyle(.secondary) + .font(.caption) + Text("TigerDine Issues and Feedback") + .padding(.top, 12) + Button(action: { + openURL(URL(string: "https://github.com/NinjaCheetah/TigerDine/issues")!) + }) { + HStack(alignment: .center) { + Image(systemName: "ant.circle") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundStyle(Color.accentColor) + VStack(alignment: .leading) { + Text("Submit a GitHub Issue") + .fontWeight(.bold) + Text("Report a bug or suggest a feature on TigerDine's GitHub repository.") + .foregroundStyle(.secondary) + .multilineTextAlignment(.leading) + } + Spacer() + Image(systemName: "chevron.forward") + } + .padding(.all, 6) + .background ( + RoundedRectangle(cornerRadius: 8) + .fill(Color.secondary.opacity(0.1)) + ) + } + .buttonStyle(.plain) + Button(action: { + showingMailView = true + }) { + HStack(alignment: .center) { + Image(systemName: "envelope.circle") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .foregroundStyle(Color.accentColor) + VStack(alignment: .leading) { + Text("Send Me an Email") + .fontWeight(.bold) + Text("Not a GitHub user? Feel free to submit feedback via email.") + .foregroundStyle(.secondary) + .multilineTextAlignment(.leading) + } + Spacer() + Image(systemName: "chevron.forward") + } + .padding(.all, 6) + .background ( + RoundedRectangle(cornerRadius: 8) + .fill(Color.secondary.opacity(0.1)) + ) + } + .buttonStyle(.plain) + .disabled(!MailView.canSendMail()) + .sheet(isPresented: $showingMailView) { + MailView(result: $mailResult) + } + Text("Just don't spam my inbox, please and thank you.") + .foregroundStyle(.secondary) + .font(.caption) + } + .frame(maxWidth: .infinity) + .toolbar { + Button(action: { + dismiss() + }) { + Image(systemName: "xmark") + } + } + } + .padding(.horizontal, 16) + } + } +} + +#Preview { + FeedbackView() +} diff --git a/TigerDine/Views/Fragments/MailView.swift b/TigerDine/Views/Fragments/MailView.swift new file mode 100644 index 0000000..098c7ce --- /dev/null +++ b/TigerDine/Views/Fragments/MailView.swift @@ -0,0 +1,56 @@ +// +// MailView.swift +// TigerDine +// +// Created by Campbell on 2/16/26. +// + +import SwiftUI +import MessageUI + +// More gross yucky UIKit code :( +// Unfortunately there's no native SwiftUI MailView. +struct MailView: UIViewControllerRepresentable { + @Environment(\.dismiss) var dismiss + @Binding var result: Result? + + class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + var parent: MailView + + init(_ parent: MailView) { + self.parent = parent + } + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + defer { + parent.dismiss() + } + if let error = error { + parent.result = .failure(error) + } else { + parent.result = .success(result) + } + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIViewController(context: Context) -> MFMailComposeViewController { + let vc = MFMailComposeViewController() + vc.mailComposeDelegate = context.coordinator + vc.setToRecipients(["58050615+NinjaCheetah@users.noreply.github.com"]) + vc.setSubject("TigerDine Feedback") + //vc.setMessageBody("", isHTML: false) + return vc + } + + func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: Context) { + + } + + static func canSendMail() -> Bool { + return MFMailComposeViewController.canSendMail() + } +}