Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions score-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
13E348FB2F3D212D0014EC63 /* CalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13E348FA2F3D21280014EC63 /* CalendarViewModel.swift */; };
1C87865D2D8CD76900EBDF74 /* TrailingFadeGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C87865C2D8CD76900EBDF74 /* TrailingFadeGradient.swift */; };
1C87865F2D8CDADC00EBDF74 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C87865E2D8CDADC00EBDF74 /* String+Extension.swift */; };
2384C7B81B22428D94240957 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840304A20FA141C291346BA8 /* Highlight.swift */; };
Expand Down Expand Up @@ -130,6 +131,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
13E348FA2F3D21280014EC63 /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = "<group>"; };
1C87865C2D8CD76900EBDF74 /* TrailingFadeGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingFadeGradient.swift; sourceTree = "<group>"; };
1C87865E2D8CDADC00EBDF74 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
2C1375CA2E7233390089EBC7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -502,6 +504,7 @@
D87787C62CFFAE3D00EA79E1 /* ViewModels */ = {
isa = PBXGroup;
children = (
13E348FA2F3D21280014EC63 /* CalendarViewModel.swift */,
D87787C72CFFAE5200EA79E1 /* GamesViewModel.swift */,
7665A4062EB00528004A9903 /* HighlightsViewModel.swift */,
D864B5AA2D793A7400A3A50E /* PastGameViewModel.swift */,
Expand Down Expand Up @@ -743,6 +746,7 @@
FD5A38DB2D8F2BDD00CF5E30 /* GameLoadingView.swift in Sources */,
B136701ECD164EE9AC64667F /* Article.swift in Sources */,
64005CCECEAC4FD4BA8F51D2 /* YouTubeVideo.swift in Sources */,
13E348FB2F3D212D0014EC63 /* CalendarViewModel.swift in Sources */,
2384C7B81B22428D94240957 /* Highlight.swift in Sources */,
CE8ED4FC2D6BF47C00A274DE /* DummyData.swift in Sources */,
CE335CD92C9244230037F572 /* Game.swift in Sources */,
Expand Down Expand Up @@ -946,16 +950,19 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\"";
DEVELOPMENT_TEAM = W7U2WA4D54;
DEVELOPMENT_TEAM = H5ZTDCQ89H;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "score-ios/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Score;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "Allow calendar access to add Cornell games that aren't in your calendar.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -980,16 +987,19 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\"";
DEVELOPMENT_TEAM = W7U2WA4D54;
DEVELOPMENT_TEAM = H5ZTDCQ89H;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "score-ios/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Score;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "Allow calendar access to add Cornell games that aren't in your calendar.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
8 changes: 2 additions & 6 deletions score-ios/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>SCORE_DEV_URL</key>
<string>$(SCORE_DEV_URL)</string>
<key>SCORE_PROD_URL</key>
Expand All @@ -19,5 +13,7 @@
<string>Poppins-Bold.ttf</string>
<string>Poppins-Regular.ttf</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
75 changes: 75 additions & 0 deletions score-ios/ViewModels/CalendarViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// CalendarViewModel.swift
// score-ios
//
// Created by Duru Alayli on 2/11/26.
//

import EventKit
import Foundation
import UIKit

final class CalendarViewModel: ObservableObject{
static let shared = CalendarViewModel()
private let eventStore = EKEventStore()
@Published var showAlert: Bool = false
@Published var openSettings: Bool = false
@Published var alertTitle: String = ""
@Published var alertMessage: String = ""

private init() {}

func requestAccessandAdd(event: Game) {
eventStore.requestFullAccessToEvents{ [weak self] (granted, error) in
guard let self = self else { return }
if granted && error == nil {

let title = "Cornell vs. \(event.opponent.name) \(event.sex) \(event.sport)"
let existing = self.eventStore.predicateForEvents(
withStart: event.date,
end: event.date.addingTimeInterval(7200),
calendars: [self.eventStore.defaultCalendarForNewEvents].compactMap { $0 }
)
let existingEvents = self.eventStore.events(matching: existing)

if existingEvents.contains(where: { $0.title == title && $0.startDate == event.date }) {
DispatchQueue.main.async {
self.alertTitle = "Game already added."
self.alertMessage = "This game is already added to your calendar."
self.showAlert = true
}
return
}

let calendarEvent = EKEvent(eventStore: self.eventStore)
calendarEvent.title = title
calendarEvent.startDate = event.date
calendarEvent.endDate = event.date.addingTimeInterval(7200)
calendarEvent.location = event.address
calendarEvent.calendar = self.eventStore.defaultCalendarForNewEvents

do {
try eventStore.save(calendarEvent, span: .thisEvent)
DispatchQueue.main.async {
if let url = URL(string: "calshow:\(event.date.timeIntervalSinceReferenceDate)") {
UIApplication.shared.open(url)
}
}
} catch {
DispatchQueue.main.async {
self.alertTitle = "Game can't be added."
self.alertMessage = "There was an error adding Cornell vs. \(event.opponent.name) to your calendar."
self.showAlert = true
}
}
} else {
DispatchQueue.main.async {
self.alertTitle = "Game can't be added."
self.alertMessage = "Calendar access denied. Please enable full calendar access in Settings."
self.showAlert = true
self.openSettings = true
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird diff lol

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no action needed though as far as I can tell

}
111 changes: 65 additions & 46 deletions score-ios/Views/DetailedViews/GameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
struct GameView : View {
var game : Game
@ObservedObject var viewModel: PastGameViewModel
@ObservedObject var calendarViewModel = CalendarViewModel.shared
@State var viewState: Int = 0
@State var dayFromNow: Int = 0
@State var hourFromNow: Int = 0
Expand Down Expand Up @@ -119,14 +120,14 @@ extension GameView {
Text("\(game.sex.description) \(game.sport.description)")
.font(Constants.Fonts.subheader)
.foregroundStyle(Constants.Colors.black)

ScrollView(.horizontal, showsIndicators: false){
Text("Cornell vs. " + game.opponent.name.removingUniversityPrefix())
.font(Constants.Fonts.header)
.foregroundStyle(Constants.Colors.black)
}
.withTrailingFadeGradient()

HStack(spacing: 10) {
HStack {
Image("Location-g")
Expand Down Expand Up @@ -176,25 +177,50 @@ extension GameView {
.padding(.top, 8)
}
.padding(.top, 20)

// Ticketing Link Button
if let link = game.ticketLink,
let url = URL(string: link) {
HStack (spacing: 16){
// Ticketing Link Button
if let link = game.ticketLink,
let url = URL(string: link) {
Button(action: {
UIApplication.shared.open(url)
}) {
HStack (spacing: 9){
Image("Ticket")
.resizable()
.frame(width: 22, height: 22)
Text("Buy Tickets")
.foregroundStyle(Constants.Colors.white)
.font(.system(size: 16, weight: .medium))
.font(Constants.Fonts.buttonLabel)
}
.foregroundColor(.white)
.padding(12)
.background(
Constants.Colors.primary_red
)
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.black.opacity(0.1), lineWidth: 1)
.shadow(color: Color.black.opacity(0.25), radius: 5, x: 0, y: 2)
)
.clipShape(RoundedRectangle(cornerRadius: 30))
}
}

// Calendar Button
Button(action: {
UIApplication.shared.open(url)
calendarViewModel.requestAccessandAdd(event:game)
}) {
HStack {
Image("Ticket")
HStack (spacing: 8){
Image("Calendar")
.resizable()
.frame(width: 25, height: 25)
Text("Buy Tickets")
.foregroundStyle(Constants.Colors.white)
.font(.system(size: 16, weight: .medium))
.frame(width: 24, height: 24)
Text("Add to Calendar")
.font(Constants.Fonts.buttonLabel)
.foregroundStyle(Constants.Colors.white)
}
.foregroundColor(.white)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.padding(12)
.background(
Constants.Colors.primary_red
)
Expand All @@ -203,41 +229,34 @@ extension GameView {
.stroke(Color.black.opacity(0.1), lineWidth: 1)
.shadow(color: Color.black.opacity(0.25), radius: 5, x: 0, y: 2)
)
.clipShape(RoundedRectangle(cornerRadius: 30)) // Clip to shape to ensure rounded corners
.clipShape(RoundedRectangle(cornerRadius: 30))
}
.alert(isPresented: $calendarViewModel.showAlert) {
if calendarViewModel.openSettings {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should abstract this large block away into a function, rather than directly attaching it to the view.

Alert(
title: Text(calendarViewModel.alertTitle),
message: Text(calendarViewModel.alertMessage),
primaryButton: .default(Text("Go to settings")) {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
},
secondaryButton: .cancel()
)
}
else {
Alert(
title: Text(calendarViewModel.alertTitle),
message: Text(calendarViewModel.alertMessage)
)
}
}
.padding(.top, 80)
}

// Calendar Button
// TODO: make this back when we have login
// Button(action: {
// // TODO: action
// }) {
// HStack {
// Image("Calendar")
// .resizable()
// .frame(width: 24, height: 24)
// Text("Add to Calendar")
// .font(Constants.Fonts.buttonLabel)
// .foregroundStyle(Constants.Colors.white)
// }
// .foregroundColor(.white)
// .padding(.horizontal, 16)
// .padding(.vertical, 10)
// .background(
// Constants.Colors.primary_red
// )
// .overlay(
// RoundedRectangle(cornerRadius: 30)
// .stroke(Color.black.opacity(0.1), lineWidth: 1)
// .shadow(color: Color.black.opacity(0.25), radius: 5, x: 0, y: 2)
// )
// .clipShape(RoundedRectangle(cornerRadius: 30)) // Clip to shape to ensure rounded corners
// }
// .padding(.top, 68)
// }
.padding(.top, 80)
}

}


private var summaryTab: some View {
NavigationLink(destination: ScoringSummary(game: game)) {
Expand Down