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.
This commit is contained in:
2026-02-17 00:01:55 -05:00
parent a4e3af3e75
commit 0c07c509f3
8 changed files with 256 additions and 10 deletions

View File

@@ -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 {

View File

@@ -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 = "";

View File

@@ -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()
}
}
}

View File

@@ -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 {

View File

@@ -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<MFMailComposeResult, Error>? = 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()
}

View File

@@ -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<MFMailComposeResult, Error>?
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()
}
}