From 671f4d4b6baa68c6834e0aeebda60c9417b431d0 Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Thu, 5 Feb 2026 15:03:37 -0500 Subject: [PATCH 1/7] Changed version and build number --- score-ios.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/score-ios.xcodeproj/project.pbxproj b/score-ios.xcodeproj/project.pbxproj index f8a2cf3..05f72a5 100644 --- a/score-ios.xcodeproj/project.pbxproj +++ b/score-ios.xcodeproj/project.pbxproj @@ -944,7 +944,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\""; DEVELOPMENT_TEAM = W7U2WA4D54; ENABLE_PREVIEWS = YES; @@ -960,7 +960,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.3; + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cornellappdev.score-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -978,7 +978,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\""; DEVELOPMENT_TEAM = W7U2WA4D54; ENABLE_PREVIEWS = YES; @@ -994,7 +994,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.3; + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cornellappdev.score-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; From a1b122df109d4d0b7ab0c10b996b841232c1ee12 Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Sun, 8 Feb 2026 21:52:53 -0500 Subject: [PATCH 2/7] Added error page with retry for highlights --- score-ios/Views/ListViews/GameErrorView.swift | 7 ++++--- score-ios/Views/ListViews/HighlightView.swift | 5 ++++- score-ios/Views/ListViews/PastGamesView.swift | 2 +- score-ios/Views/ListViews/UpcomingGamesView.swift | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/score-ios/Views/ListViews/GameErrorView.swift b/score-ios/Views/ListViews/GameErrorView.swift index 4e79952..e41cf68 100644 --- a/score-ios/Views/ListViews/GameErrorView.swift +++ b/score-ios/Views/ListViews/GameErrorView.swift @@ -10,7 +10,8 @@ import SwiftUI struct GameErrorView: View { - @ObservedObject var viewModel: GamesViewModel + let message: String + let onRetry: () -> Void var body: some View { ZStack { @@ -25,7 +26,7 @@ struct GameErrorView: View { .frame(width: 64, height: 64) .padding(.bottom, 16) - Text("Oops! Schedules failed to load.") + Text("Oops! \(message) failed to load.") .font(Constants.Fonts.Header.h2) .padding(.bottom, 8) @@ -35,7 +36,7 @@ struct GameErrorView: View { Spacer() Button { - viewModel.fetchGames() + onRetry() } label: { HStack { Image(systemName: "arrow.trianglehead.2.clockwise") diff --git a/score-ios/Views/ListViews/HighlightView.swift b/score-ios/Views/ListViews/HighlightView.swift index 6299c86..7ac828f 100644 --- a/score-ios/Views/ListViews/HighlightView.swift +++ b/score-ios/Views/ListViews/HighlightView.swift @@ -16,7 +16,10 @@ struct HighlightView: View { switch viewModel.dataState { case .idle, .loading: HighlightLoadingView() - + + case .error: + GameErrorView(message: "Highlights", onRetry: { viewModel.loadHighlights() }) + default: VStack{ headerView diff --git a/score-ios/Views/ListViews/PastGamesView.swift b/score-ios/Views/ListViews/PastGamesView.swift index 6e855c1..3dd31bb 100644 --- a/score-ios/Views/ListViews/PastGamesView.swift +++ b/score-ios/Views/ListViews/PastGamesView.swift @@ -68,7 +68,7 @@ struct PastGamesView: View { } if case .error = vm.dataState { - GameErrorView(viewModel: vm) + GameErrorView(message: "Schedules", onRetry: { vm.fetchGames() }) } } } diff --git a/score-ios/Views/ListViews/UpcomingGamesView.swift b/score-ios/Views/ListViews/UpcomingGamesView.swift index b6061ac..af673fb 100644 --- a/score-ios/Views/ListViews/UpcomingGamesView.swift +++ b/score-ios/Views/ListViews/UpcomingGamesView.swift @@ -71,7 +71,7 @@ struct UpcomingGamesView: View { } if case .error = vm.dataState { - GameErrorView(viewModel: vm) + GameErrorView(message: "Schedules", onRetry: { vm.fetchGames() }) } } } From eaecd8029bafcfbf4b60d5bffe38f70c8a2d3561 Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Sun, 8 Feb 2026 21:55:38 -0500 Subject: [PATCH 3/7] Made datastates go to loading when retrying after an error --- score-ios/ViewModels/GamesViewModel.swift | 2 +- score-ios/ViewModels/HighlightsViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/score-ios/ViewModels/GamesViewModel.swift b/score-ios/ViewModels/GamesViewModel.swift index e9cc9aa..f115143 100644 --- a/score-ios/ViewModels/GamesViewModel.swift +++ b/score-ios/ViewModels/GamesViewModel.swift @@ -144,7 +144,7 @@ class GamesViewModel: ObservableObject // TODO: Remove once backend is has implemented pagination with sorted dates and pages by game type func fetchGames() { // Set loading state before fetch - dataState = (hasNotFetchedYet ? .loading : .refreshing) + dataState = (dataState == .success ? .refreshing : .loading) self.privateUpcomingGames.removeAll() self.privatePastGames.removeAll() diff --git a/score-ios/ViewModels/HighlightsViewModel.swift b/score-ios/ViewModels/HighlightsViewModel.swift index 253156c..a6e746f 100644 --- a/score-ios/ViewModels/HighlightsViewModel.swift +++ b/score-ios/ViewModels/HighlightsViewModel.swift @@ -38,7 +38,7 @@ class HighlightsViewModel: ObservableObject { // MARK: - Loading func loadHighlights() { - dataState = (hasNotFetchedYet ? .loading : .refreshing) + dataState = (dataState == .success ? .refreshing : .loading) Task { do { From 7bec0f25d683776f874b5ff01b24167375a4a6c1 Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Sun, 8 Feb 2026 22:07:51 -0500 Subject: [PATCH 4/7] Fixed error returned when number of games is divisible by 50 --- score-ios/ViewModels/GamesViewModel.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/score-ios/ViewModels/GamesViewModel.swift b/score-ios/ViewModels/GamesViewModel.swift index f115143..92173a7 100644 --- a/score-ios/ViewModels/GamesViewModel.swift +++ b/score-ios/ViewModels/GamesViewModel.swift @@ -209,13 +209,12 @@ class GamesViewModel: ObservableObject } guard let fetchedGames = fetchedGames, !fetchedGames.isEmpty else { - // If this is the first fetch and no games, show empty data error if offset == 0 { + // First page returned empty —> no games self.dataState = .error(error: .emptyData) } else { -// // Otherwise process all accumulated games -// self.processGames(accumulatedGames) - self.dataState = .error(error: .networkError) + // Process what we have + self.processGames(accumulatedGames) } return } From 26691184f736e400bf85cfba4de9d20fce0bf8bf Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Sun, 8 Feb 2026 22:17:39 -0500 Subject: [PATCH 5/7] Made highlight images failing to load a solid grey background --- .../ListViews/HighlightTileArticle.swift | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/score-ios/Views/ListViews/HighlightTileArticle.swift b/score-ios/Views/ListViews/HighlightTileArticle.swift index 16b7354..5f1b437 100644 --- a/score-ios/Views/ListViews/HighlightTileArticle.swift +++ b/score-ios/Views/ListViews/HighlightTileArticle.swift @@ -18,28 +18,15 @@ struct HighlightTileArticle: View { // Background Image with dark overlay AsyncImage(url: URL(string: article.image)) { phase in switch phase { - case .empty: - Rectangle() - .fill(Constants.Colors.gray_icons.opacity(0.2)) - .overlay(Color.black.opacity(0.60)) // dark tint case .success(let image): image .resizable() .scaledToFill() .overlay(Color.black.opacity(0.60)) // dark tint - case .failure(_): + default: Rectangle() - .fill(Constants.Colors.gray_icons.opacity(0.3)) - .overlay( - Image(systemName: "photo") - .resizable() - .scaledToFit() - .foregroundColor(.white.opacity(0.7)) - .padding() - .overlay(Color.black.opacity(0.60)) // dark tint - ) - @unknown default: - EmptyView() + .fill(Constants.Colors.gray_icons.opacity(0.2)) + .overlay(Color.black.opacity(0.60)) // dark tint } } .frame(width: width, height: 192) From a6359633f265c8f5257050d2059a7af132a0b3f7 Mon Sep 17 00:00:00 2001 From: zb98 Date: Wed, 18 Feb 2026 18:11:14 -0500 Subject: [PATCH 6/7] Made image failing to load standard between videos and articles --- .../Views/ListViews/HighlightTileVideo.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/score-ios/Views/ListViews/HighlightTileVideo.swift b/score-ios/Views/ListViews/HighlightTileVideo.swift index ee3f5c2..b169fec 100644 --- a/score-ios/Views/ListViews/HighlightTileVideo.swift +++ b/score-ios/Views/ListViews/HighlightTileVideo.swift @@ -18,22 +18,15 @@ struct HighlightTileVideo: View { // Thumbnail AsyncImage(url: URL(string: video.thumbnail)) { phase in switch phase { - case .empty: - // While loading - Rectangle() - .fill(Constants.Colors.gray_icons.opacity(0.2)) case .success(let image): image .resizable() .scaledToFill() - case .failure(_): - // If loading fails - Image(systemName: "photo") - .resizable() - .scaledToFit() - .foregroundColor(.gray) - @unknown default: - EmptyView() + .overlay(Color.black.opacity(0.60)) // dark tint + default: + Rectangle() + .fill(Constants.Colors.gray_icons.opacity(0.2)) + .overlay(Color.black.opacity(0.60)) // dark tint } } .frame(width: width, height: 117) From 3f13080d42ce4b504c67106f88d1865ed5bbc875 Mon Sep 17 00:00:00 2001 From: Zain Bilal Date: Thu, 26 Feb 2026 11:47:34 -0500 Subject: [PATCH 7/7] add-to-calendar and the ticketing (#54) (#55) Fixed the UI of add to calendar and the ticketing button (if no link for ticketing is found, only shows the add to calendar button) When add to calendar is clicked, it first requests access to the calendar Adds the game with its name, location, and time to the Apple Calendar and opens Calendar, going to that specific date and time If a game was already added to the calendar, it displays a message saying the game was already added before If game can't be added because no access to calendar, displays a button to open the specific settings Fixed: Abstracted the alert logic into a function instead of the large block. Co-authored-by: durualayli <143341478+durualayli@users.noreply.github.com> --- score-ios.xcodeproj/project.pbxproj | 14 ++- score-ios/Info.plist | 8 +- score-ios/ViewModels/CalendarViewModel.swift | 94 ++++++++++++++++ score-ios/Views/DetailedViews/GameView.swift | 110 +++++++++++-------- 4 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 score-ios/ViewModels/CalendarViewModel.swift diff --git a/score-ios.xcodeproj/project.pbxproj b/score-ios.xcodeproj/project.pbxproj index 05f72a5..ebe8796 100644 --- a/score-ios.xcodeproj/project.pbxproj +++ b/score-ios.xcodeproj/project.pbxproj @@ -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 */; }; @@ -130,6 +131,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 13E348FA2F3D21280014EC63 /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = ""; }; 1C87865C2D8CD76900EBDF74 /* TrailingFadeGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingFadeGradient.swift; sourceTree = ""; }; 1C87865E2D8CDADC00EBDF74 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 2C1375CA2E7233390089EBC7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -502,6 +504,7 @@ D87787C62CFFAE3D00EA79E1 /* ViewModels */ = { isa = PBXGroup; children = ( + 13E348FA2F3D21280014EC63 /* CalendarViewModel.swift */, D87787C72CFFAE5200EA79E1 /* GamesViewModel.swift */, 7665A4062EB00528004A9903 /* HighlightsViewModel.swift */, D864B5AA2D793A7400A3A50E /* PastGameViewModel.swift */, @@ -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 */, @@ -946,16 +950,19 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; 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", @@ -980,16 +987,19 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; 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", diff --git a/score-ios/Info.plist b/score-ios/Info.plist index 5fd6b04..082f441 100644 --- a/score-ios/Info.plist +++ b/score-ios/Info.plist @@ -2,12 +2,6 @@ - UIUserInterfaceStyle - Light - UIViewControllerBasedStatusBarAppearance - - ITSAppUsesNonExemptEncryption - SCORE_DEV_URL $(SCORE_DEV_URL) SCORE_PROD_URL @@ -19,5 +13,7 @@ Poppins-Bold.ttf Poppins-Regular.ttf + UIViewControllerBasedStatusBarAppearance + diff --git a/score-ios/ViewModels/CalendarViewModel.swift b/score-ios/ViewModels/CalendarViewModel.swift new file mode 100644 index 0000000..5b9625e --- /dev/null +++ b/score-ios/ViewModels/CalendarViewModel.swift @@ -0,0 +1,94 @@ +// +// 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 eachAlert: Alert? + + private init() {} + + struct Alert: Identifiable { + let id = UUID() + let alertTitle: String + let alertMessage: String + let openSettings: Bool + } + + 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.showCalendarAlert ( + alertTitle: "Game already added.", + alertMessage: "This game is already added to your calendar." + ) + } + 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.showCalendarAlert ( + alertTitle: "Game can't be added.", + alertMessage: "There was an error adding Cornell vs. \(event.opponent.name) to your calendar." + ) + } + } + } else { + DispatchQueue.main.async { + self.showCalendarAlert ( + alertTitle: "Game can't be added.", + alertMessage: "Calendar access denied. Please enable full calendar access in Settings.", + openSettings: true + ) + } + } + } + } + + private func showCalendarAlert( + alertTitle: String, + alertMessage: String, + openSettings: Bool = false + ) { + self.eachAlert = Alert( + alertTitle: alertTitle, + alertMessage: alertMessage, + openSettings: openSettings + ) + } +} diff --git a/score-ios/Views/DetailedViews/GameView.swift b/score-ios/Views/DetailedViews/GameView.swift index dcbba14..2558879 100644 --- a/score-ios/Views/DetailedViews/GameView.swift +++ b/score-ios/Views/DetailedViews/GameView.swift @@ -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 @@ -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") @@ -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 ) @@ -203,41 +229,33 @@ 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(item: $calendarViewModel.eachAlert) { alert in + if alert.openSettings { + return Alert ( + title: Text(alert.alertTitle), + message: Text(alert.alertMessage), + primaryButton: .default(Text("Go to settings")) { + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } + }, + secondaryButton: .cancel() + ) + } else { + return Alert ( + title: Text(alert.alertTitle), + message: Text(alert.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)) {