diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh old mode 100644 new mode 100755 index 1c73cc3..d958093 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -4,15 +4,16 @@ # score-ios # # Created by Hsia Lu wu on 3/5/25. +# # echo "Downloading Secrets" brew install wget cd $CI_PRIMARY_REPOSITORY_PATH/ci_scripts mkdir ../ScoreSecrets -wget -O ../score_ios/ScoreSecrets/Keys.xcconfig "$KEYS" -wget -O ../score_ios/ScoreSecrets/apollo-codegen-config-dev.json "$CODEGEN_DEV" -wget -O ../score_ios/ScoreSecrets/apollo-codegen-config-prod.json "$CODEGEN_PROD" +wget -O ../ScoreSecrets/Keys.xcconfig "$KEYS" +wget -O ../ScoreSecrets/apollo-codegen-config-dev.json "$CODEGEN_DEV" +wget -O ../ScoreSecrets/apollo-codegen-config-prod.json "$CODEGEN_PROD" echo "Generating API file" ../apollo-ios-cli generate -p "../ScoreSecrets/apollo-codegen-config-prod.json" -f diff --git a/score-ios.xcodeproj/project.pbxproj b/score-ios.xcodeproj/project.pbxproj index 779464a..86d52c2 100644 --- a/score-ios.xcodeproj/project.pbxproj +++ b/score-ios.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1C87865D2D8CD76900EBDF74 /* TrailingFadeGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C87865C2D8CD76900EBDF74 /* TrailingFadeGradient.swift */; }; + 1C87865F2D8CDADC00EBDF74 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C87865E2D8CDADC00EBDF74 /* String+Extension.swift */; }; CE335CD32C922E8D0037F572 /* PrimaryColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE335CD22C922E8D0037F572 /* PrimaryColors.swift */; }; CE335CD52C922ECB0037F572 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE335CD42C922ECB0037F572 /* Constants.swift */; }; CE335CD72C922F390037F572 /* Dates.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE335CD62C922F390037F572 /* Dates.swift */; }; @@ -64,6 +66,7 @@ D86347B12CDBFF7C003DD8F6 /* UpcomingGamesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86347B02CDBFF7C003DD8F6 /* UpcomingGamesView.swift */; }; D86347DF2CE98B3C003DD8F6 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86347DE2CE98B3C003DD8F6 /* MainTabView.swift */; }; D86347E12CE98D37003DD8F6 /* TabViewIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86347E02CE98D37003DD8F6 /* TabViewIcon.swift */; }; + D864B5AB2D793A7400A3A50E /* PastGameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D864B5AA2D793A7400A3A50E /* PastGameViewModel.swift */; }; D87787C82CFFAE5A00EA79E1 /* GamesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87787C72CFFAE5200EA79E1 /* GamesViewModel.swift */; }; D87882282CC060FC00421F67 /* GameDetailedScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87882272CC060FC00421F67 /* GameDetailedScoreView.swift */; }; D89102042CED69EF004CE226 /* Apollo in Frameworks */ = {isa = PBXBuildFile; productRef = D89102032CED69EF004CE226 /* Apollo */; }; @@ -76,6 +79,9 @@ D8B1C9D12CD2CE3D0095E563 /* PastGamesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B1C9D02CD2CE3C0095E563 /* PastGamesView.swift */; }; D8B1C9D32CD2D20A0095E563 /* PastGameTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B1C9D22CD2D20A0095E563 /* PastGameTile.swift */; }; D8DD4E642CFD48ED00F2C46E /* Team.graphql in Resources */ = {isa = PBXBuildFile; fileRef = D8DD4E632CFD48E400F2C46E /* Team.graphql */; }; + FD5A38DB2D8F2BDD00CF5E30 /* GameLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5A38DA2D8F2BDD00CF5E30 /* GameLoadingView.swift */; }; + FD5A38DD2D8F30CC00CF5E30 /* GameErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5A38DC2D8F30CC00CF5E30 /* GameErrorView.swift */; }; + FD5A38DF2D8F3E1400CF5E30 /* ShimmerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5A38DE2D8F3E1400CF5E30 /* ShimmerModifier.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -96,6 +102,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 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 = ""; }; CE335CD22C922E8D0037F572 /* PrimaryColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryColors.swift; sourceTree = ""; }; CE335CD42C922ECB0037F572 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; CE335CD62C922F390037F572 /* Dates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dates.swift; sourceTree = ""; }; @@ -158,6 +166,8 @@ D86347B02CDBFF7C003DD8F6 /* UpcomingGamesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpcomingGamesView.swift; sourceTree = ""; }; D86347DE2CE98B3C003DD8F6 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; D86347E02CE98D37003DD8F6 /* TabViewIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewIcon.swift; sourceTree = ""; }; + D864B5A92D79169C00A3A50E /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; + D864B5AA2D793A7400A3A50E /* PastGameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PastGameViewModel.swift; sourceTree = ""; }; D87787C72CFFAE5200EA79E1 /* GamesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamesViewModel.swift; sourceTree = ""; }; D87882272CC060FC00421F67 /* GameDetailedScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDetailedScoreView.swift; sourceTree = ""; }; D89102062CED6A28004CE226 /* schema.graphqls */ = {isa = PBXFileReference; lastKnownFileType = text; path = schema.graphqls; sourceTree = ""; }; @@ -168,7 +178,9 @@ D8B1C9D02CD2CE3C0095E563 /* PastGamesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PastGamesView.swift; sourceTree = ""; }; D8B1C9D22CD2D20A0095E563 /* PastGameTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PastGameTile.swift; sourceTree = ""; }; D8DD4E632CFD48E400F2C46E /* Team.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = Team.graphql; sourceTree = ""; }; - FD31F6D22D792E10001458FA /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; + FD5A38DA2D8F2BDD00CF5E30 /* GameLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameLoadingView.swift; sourceTree = ""; }; + FD5A38DC2D8F30CC00CF5E30 /* GameErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameErrorView.swift; sourceTree = ""; }; + FD5A38DE2D8F3E1400CF5E30 /* ShimmerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerModifier.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -198,6 +210,21 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1C87865B2D8CD73C00EBDF74 /* ViewModifiers */ = { + isa = PBXGroup; + children = ( + 1C87865C2D8CD76900EBDF74 /* TrailingFadeGradient.swift */, + ); + path = ViewModifiers; + sourceTree = ""; + }; + CE335CCA2C9226EB0037F572 /* Configs */ = { + isa = PBXGroup; + children = ( + ); + path = Configs; + sourceTree = ""; + }; CE335CCC2C9226F90037F572 /* Models */ = { isa = PBXGroup; children = ( @@ -226,11 +253,14 @@ CE335CCE2C9227050037F572 /* Utils */ = { isa = PBXGroup; children = ( + 1C87865B2D8CD73C00EBDF74 /* ViewModifiers */, CE335CD22C922E8D0037F572 /* PrimaryColors.swift */, CE335CD42C922ECB0037F572 /* Constants.swift */, CE335CD62C922F390037F572 /* Dates.swift */, CE528FA32C9653C200C238B5 /* Error.swift */, CE3C9C422D011A23008BFB4C /* OrdinalSuffix.swift */, + FD5A38DE2D8F3E1400CF5E30 /* ShimmerModifier.swift */, + 1C87865E2D8CDADC00EBDF74 /* String+Extension.swift */, ); path = Utils; sourceTree = ""; @@ -282,7 +312,7 @@ CE725D2F2C89120100386943 = { isa = PBXGroup; children = ( - FD31F6D12D792DF4001458FA /* ci_scripts */, + D864B5A82D79168800A3A50E /* ci_scripts */, CEA25A972D752C6F00B9837A /* ScoreSecrets */, CE725D3A2C89120200386943 /* score-ios */, CE725D4B2C89120500386943 /* score-iosTests */, @@ -391,6 +421,8 @@ CE8ED50D2D6C3B8000A274DE /* SportSelectorView.swift */, CE8ED5112D6C3FCB00A274DE /* CarouselView.swift */, CE8ED5132D6C42D400A274DE /* GameSectionHeaderView.swift */, + FD5A38DA2D8F2BDD00CF5E30 /* GameLoadingView.swift */, + FD5A38DC2D8F30CC00CF5E30 /* GameErrorView.swift */, ); path = ListViews; sourceTree = ""; @@ -411,10 +443,19 @@ path = ScoreSecrets; sourceTree = ""; }; + D864B5A82D79168800A3A50E /* ci_scripts */ = { + isa = PBXGroup; + children = ( + D864B5A92D79169C00A3A50E /* ci_post_clone.sh */, + ); + path = ci_scripts; + sourceTree = ""; + }; D87787C62CFFAE3D00EA79E1 /* ViewModels */ = { isa = PBXGroup; children = ( D87787C72CFFAE5200EA79E1 /* GamesViewModel.swift */, + D864B5AA2D793A7400A3A50E /* PastGameViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -445,14 +486,6 @@ path = Networking; sourceTree = ""; }; - FD31F6D12D792DF4001458FA /* ci_scripts */ = { - isa = PBXGroup; - children = ( - FD31F6D22D792E10001458FA /* ci_post_clone.sh */, - ); - path = ci_scripts; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -518,7 +551,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1620; TargetAttributes = { CE725D372C89120200386943 = { CreatedOnToolsVersion = 15.1; @@ -641,7 +674,9 @@ D85802102D6C38700075B036 /* DynamicScoreBox.swift in Sources */, D8B1C9D32CD2D20A0095E563 /* PastGameTile.swift in Sources */, CE528FA22C9651C800C238B5 /* UpcomingGameTile.swift in Sources */, + FD5A38DD2D8F30CC00CF5E30 /* GameErrorView.swift in Sources */, CEA25A932D75279A00B9837A /* ScoreEnvironment.swift in Sources */, + FD5A38DB2D8F2BDD00CF5E30 /* GameLoadingView.swift in Sources */, CE8ED4FC2D6BF47C00A274DE /* DummyData.swift in Sources */, CE335CD92C9244230037F572 /* Game.swift in Sources */, D89102102CF0EBA4004CE226 /* DataCoordinator.swift in Sources */, @@ -650,8 +685,10 @@ D86347AF2CDBD2F4003DD8F6 /* PastGameCard.swift in Sources */, CE8ED5122D6C3FCB00A274DE /* CarouselView.swift in Sources */, CE528FF72C979DA000C238B5 /* GameView.swift in Sources */, + 1C87865D2D8CD76900EBDF74 /* TrailingFadeGradient.swift in Sources */, CE8ED4F82D6BF42B00A274DE /* Sport.swift in Sources */, D87882282CC060FC00421F67 /* GameDetailedScoreView.swift in Sources */, + D864B5AB2D793A7400A3A50E /* PastGameViewModel.swift in Sources */, D83EE8862CC9917C008B693C /* ScoreSummaryTile.swift in Sources */, CE8ED5002D6BF4BE00A274DE /* BoxScoreUpdate.swift in Sources */, D89461192CBF393B0010C532 /* UpcomingCard.swift in Sources */, @@ -660,6 +697,7 @@ D8B1C9D12CD2CE3D0095E563 /* PastGamesView.swift in Sources */, CE335CD32C922E8D0037F572 /* PrimaryColors.swift in Sources */, CE725D3C2C89120200386943 /* Home.swift in Sources */, + FD5A38DF2D8F3E1400CF5E30 /* ShimmerModifier.swift in Sources */, CE528FA02C96420700C238B5 /* PickerView.swift in Sources */, CE528FA42C9653C200C238B5 /* Error.swift in Sources */, CE335CD52C922ECB0037F572 /* Constants.swift in Sources */, @@ -671,6 +709,7 @@ CE8ED5142D6C42D400A274DE /* GameSectionHeaderView.swift in Sources */, D86347B12CDBFF7C003DD8F6 /* UpcomingGamesView.swift in Sources */, D86347DF2CE98B3C003DD8F6 /* MainTabView.swift in Sources */, + 1C87865F2D8CDADC00EBDF74 /* String+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -833,18 +872,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 17; DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\""; DEVELOPMENT_TEAM = ZGMCXU7X3U; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "score-ios/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Score; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -852,9 +891,11 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cornellappdev.score-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -865,18 +906,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 17; DEVELOPMENT_ASSET_PATHS = "\"score-ios/Preview Content\""; DEVELOPMENT_TEAM = ZGMCXU7X3U; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "score-ios/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = Score; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -884,16 +925,17 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.cornellappdev.score-ios"; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; CE725D602C89120500386943 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -913,7 +955,6 @@ CE725D612C89120500386943 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -933,7 +974,6 @@ CE725D632C89120500386943 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 9VB7SD8G8W; @@ -951,7 +991,6 @@ CE725D642C89120500386943 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 9VB7SD8G8W; diff --git a/score-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/score-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 092041f..f3f8c4a 100644 --- a/score-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/score-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "6b067d3fafa8b6816a1507517f5f83e403209fe5e4f2246c62127d371c045b62", "pins" : [ { "identity" : "apollo-ios", @@ -19,5 +20,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/score-ios.xcodeproj/xcshareddata/xcschemes/score-ios-prod.xcscheme b/score-ios.xcodeproj/xcshareddata/xcschemes/score-ios-prod.xcscheme index fd76c94..77e9f5b 100644 --- a/score-ios.xcodeproj/xcshareddata/xcschemes/score-ios-prod.xcscheme +++ b/score-ios.xcodeproj/xcshareddata/xcschemes/score-ios-prod.xcscheme @@ -1,6 +1,6 @@ + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + ITSAppUsesNonExemptEncryption + SCORE_DEV_URL $(SCORE_DEV_URL) SCORE_PROD_URL diff --git a/score-ios/Models/BoxScoreUpdate.swift b/score-ios/Models/BoxScoreUpdate.swift index d5d278d..5fda753 100644 --- a/score-ios/Models/BoxScoreUpdate.swift +++ b/score-ios/Models/BoxScoreUpdate.swift @@ -24,7 +24,7 @@ struct BoxScoreItem: Decodable { if let item = item { self.team = item.team ?? "" self.period = item.period ?? "" - self.time = item.time ?? "" + self.time = item.time ?? "--:--" self.description = item.description ?? "N/A" self.scorer = item.scorer ?? "" self.assist = item.assist ?? "" diff --git a/score-ios/Models/DummyData.swift b/score-ios/Models/DummyData.swift index e2162b2..1a575ba 100644 --- a/score-ios/Models/DummyData.swift +++ b/score-ios/Models/DummyData.swift @@ -10,20 +10,20 @@ import SwiftUI // TEMP Dummy data extension Game { static let dummyData: [Game] = [ - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2025, month: 2, day: 23, hour: 22, minute: 10), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [], gameUpdates: []), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2024, month: 12, day: 6, hour: 14, minute: 0), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: true, cornellScore: 10, opponentScore: 7, time: "05/19/2023", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD"), GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2023", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Cambridge", state: "MA", date: Date.dateComponents(year: 2024, month: 5, day: 21, hour: 10, minute: 0), sport: .Football, address: "1 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Princeton", state: "NJ", date: Date.dateComponents(year: 2024, month: 5, day: 20, hour: 10, minute: 0), sport: .Basketball, address: "2 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2025, month: 2, day: 23, hour: 22, minute: 10), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [], gameUpdates: []), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2024, month: 12, day: 6, hour: 14, minute: 0), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: true, cornellScore: 10, opponentScore: 7, time: "05/19/2023", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD"), GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2023", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), + Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Cambridge", state: "MA", date: Date.dateComponents(year: 2024, month: 5, day: 21, hour: 10, minute: 0), sport: .Football, address: "1 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "1:12", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Princeton", state: "NJ", date: Date.dateComponents(year: 2024, month: 5, day: 20, hour: 10, minute: 0), sport: .Basketball, address: "2 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "New Haven", state: "CT", date: Date.dateComponents(year: 2024, month: 5, day: 22, hour: 10, minute: 0), sport: .Soccer, address: "3 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Providence", state: "RI", date: Date.dateComponents(year: 2024, month: 5, day: 23, hour: 10, minute: 0), sport: .CrossCountry, address: "4 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Providence", state: "RI", date: Date.dateComponents(year: 2024, month: 5, day: 23, hour: 10, minute: 0), sport: .CrossCountry, address: "4 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Hanover", state: "NH", date: Date.dateComponents(year: 2024, month: 5, day: 24, hour: 10, minute: 0), sport: .IceHockey, address: "5 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "New York", state: "NY", date: Date.dateComponents(year: 2024, month: 5, day: 25, hour: 10, minute: 0), sport: .Lacrosse, address: "6 Fake St", sex: .Women, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2024, month: 5, day: 19, hour: 10, minute: 0), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Pennsylvania", state: "PA", date: Date.dateComponents(year: 2024, month: 5, day: 19, hour: 10, minute: 0), sport: .Basketball, address: "0 Fake St", sex: .Men, timeUpdates: [], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Cambridge", state: "MA", date: Date.dateComponents(year: 2024, month: 5, day: 21, hour: 10, minute: 0), sport: .Football, address: "1 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Princeton", state: "NJ", date: Date.dateComponents(year: 2024, month: 5, day: 20, hour: 10, minute: 0), sport: .Basketball, address: "2 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Princeton", state: "NJ", date: Date.dateComponents(year: 2024, month: 5, day: 20, hour: 10, minute: 0), sport: .Basketball, address: "2 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "New Haven", state: "CT", date: Date.dateComponents(year: 2024, month: 5, day: 22, hour: 10, minute: 0), sport: .Soccer, address: "3 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), - Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Providence", state: "RI", date: Date.dateComponents(year: 2024, month: 5, day: 23, hour: 10, minute: 0), sport: .CrossCountry, address: "4 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), +// Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Providence", state: "RI", date: Date.dateComponents(year: 2024, month: 5, day: 23, hour: 10, minute: 0), sport: .CrossCountry, address: "4 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "Hanover", state: "NH", date: Date.dateComponents(year: 2024, month: 5, day: 24, hour: 10, minute: 0), sport: .IceHockey, address: "5 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]), Game(opponent: Team(id: "673d2c20569abe4465e9f792", color: "blue", image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Cornell_University_seal.svg/1200px-Cornell_University_seal.svg.png", name: "Cornell"), city: "New York", state: "NY", date: Date.dateComponents(year: 2024, month: 5, day: 25, hour: 10, minute: 0), sport: .Lacrosse, address: "6 Fake St", sex: .Men, timeUpdates: [TimeUpdate(timestamp: 1, isTotal: false, cornellScore: 13, opponentScore: 7)], gameUpdates: [GameUpdate(timestamp: 1, isTotal: false, cornellScore: 10, opponentScore: 7, time: "05/19/2024", isCornell: true, eventParty: EventParty.Cornell, description: "Zhao, Alan field goal attempt from 24 GOOD")]) ] diff --git a/score-ios/Models/Game.swift b/score-ios/Models/Game.swift index a4154f7..b12f8e7 100644 --- a/score-ios/Models/Game.swift +++ b/score-ios/Models/Game.swift @@ -85,24 +85,24 @@ extension Game { // Parse breakdown and map into `TimeUpdate` array // [["1", "2"], ["2", "3"]] - if (breakdown != nil) { - let scoreBreakDown = breakdown! + if let breakdown { + let scoreBreakDown = breakdown let corScores = scoreBreakDown[0] let oppScores = scoreBreakDown[1] var corTotal = 0 var oppTotal = 0 - if (corScores != nil && oppScores != nil) { - corScores!.indices.forEach({ index in - let timeStamp = index+1 - if (corScores![index] != nil && oppScores![index] != nil) { - let corScore = Int(corScores![index]!) ?? 0 - let oppScore = Int(oppScores![index]!) ?? 0 + if let corScores, let oppScores { + corScores.indices.forEach({ index in + let timeStamp = index + 1 + if let corScore = corScores[index], let oppScore = oppScores[index] { + let corScore = Int(corScore) ?? 0 + let oppScore = Int(oppScore) ?? 0 let timeUpdate = TimeUpdate(timestamp: timeStamp, isTotal: false, cornellScore: corScore, opponentScore: oppScore) corTotal += corScore oppTotal += oppScore updates.append(timeUpdate) } - if (index == corScores!.count - 1) { + if (index == corScores.count - 1) { let total = TimeUpdate(timestamp: index + 1, isTotal: true, cornellScore: corTotal, opponentScore: oppTotal) updates.append(total) } diff --git a/score-ios/Models/Sex.swift b/score-ios/Models/Sex.swift index 38d58d7..c4896c3 100644 --- a/score-ios/Models/Sex.swift +++ b/score-ios/Models/Sex.swift @@ -35,6 +35,7 @@ enum Sex : Identifiable, CaseIterable, CustomStringConvertible { return "Womens" } } + // This is strictly for filtering purposes, all datum should have one of Men or Women static func index(of sex: Sex) -> Int? { return allCases.firstIndex(of: sex) diff --git a/score-ios/Models/Sport.swift b/score-ios/Models/Sport.swift index 1e55b2f..ebdf88f 100644 --- a/score-ios/Models/Sport.swift +++ b/score-ios/Models/Sport.swift @@ -14,33 +14,33 @@ enum Sport : String, Identifiable, CaseIterable, CustomStringConvertible { case All // Both - case Basketball - case CrossCountry +// case Basketball +// case CrossCountry case IceHockey case Lacrosse case Soccer - case Squash +// case Squash // case SwimmingDiving - case Tennis +// case Tennis // case TrackField // Women - case Fencing +// case Fencing case FieldHockey - case Gymnastics - case Rowing - case Sailing - case Softball - case Volleyball +// case Gymnastics +// case Rowing +// case Sailing +// case Softball +// case Volleyball // Men case Baseball case Football - case Golf +// case Golf // case RowingHeavyweight // case RowingLightweight - case SprintFootball - case Wrestling +// case SprintFootball +// case Wrestling // init from a string from backend (might include spaces) init?(normalizedValue: String) { @@ -60,52 +60,52 @@ enum Sport : String, Identifiable, CaseIterable, CustomStringConvertible { switch self { case .All: return "All" - case .Basketball: - return "Basketball" - case .CrossCountry: - return "Cross Country" +// case .Basketball: +// return "Basketball" +// case .CrossCountry: +// return "Cross Country" case .IceHockey: return "Ice Hockey" case .Lacrosse: return "Lacrosse" case .Soccer: return "Soccer" - case .Squash: - return "Squash" +// case .Squash: +// return "Squash" // case .SwimmingDiving: // return "Swimming" - case .Tennis: - return "Tennis" +// case .Tennis: +// return "Tennis" // case .TrackField: // return "Track and Field" - case .Fencing: - return "Fencing" +// case .Fencing: +// return "Fencing" case .FieldHockey: return "Field Hockey" - case .Gymnastics: - return "Gymnastics" - case .Rowing: - return "Rowing" - case .Sailing: - return "Sailing" - case .Softball: - return "Softball" - case .Volleyball: - return "Volleyball" +// case .Gymnastics: +// return "Gymnastics" +// case .Rowing: +// return "Rowing" +// case .Sailing: +// return "Sailing" +// case .Softball: +// return "Softball" +// case .Volleyball: +// return "Volleyball" case .Baseball: return "Baseball" case .Football: return "Football" - case .Golf: - return "Golf" +// case .Golf: +// return "Golf" // case .RowingHeavyweight: // return "HW Rowing" // case .RowingLightweight: // return "LW Rowing" - case .SprintFootball: - return "Sprint Football" - case .Wrestling: - return "Wrestling" +// case .SprintFootball: +// return "Sprint Football" +// case .Wrestling: +// return "Wrestling" } } } diff --git a/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.jpg b/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.jpg new file mode 100644 index 0000000..6b85889 Binary files /dev/null and b/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.jpg differ diff --git a/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png deleted file mode 100644 index c4cfeca..0000000 Binary files a/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png and /dev/null differ diff --git a/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index cefcc87..bf669b5 100644 --- a/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/score-ios/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "AppIcon.png", + "filename" : "AppIcon.jpg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/score-ios/Utils/Constants.swift b/score-ios/Utils/Constants.swift index e6cd9b7..1b50649 100644 --- a/score-ios/Utils/Constants.swift +++ b/score-ios/Utils/Constants.swift @@ -32,7 +32,8 @@ struct Constants { static let primary_red = Color(hex: 0xA5210D) static let gradient_red = Color(red: 179 / 255, green: 27 / 255, blue: 27 / 255, opacity: 0.4) static let gradient_blue = Color(red: 1 / 255, green: 31 / 255, blue: 91 / 255, opacity: 0.4) - + static let crimson = Color(hex: 0xA5210D) + // Customs static let selected = primary_red static let selectedText = white @@ -43,9 +44,32 @@ struct Constants { } - enum Fonts { - static let h1 = Font.custom("Poppins Medium", size: 24) - static let h2 = Font.custom("Poppins SemiBold", size: 18) + struct Fonts { + // Header fonts + struct Header { + static let h1 = Font.custom("Poppins SemiBold", size: 24) + static let h2 = Font.custom("Poppins Medium", size: 18) + static let h3 = Font.custom("Poppins Medium", size: 14) + static let h4 = Font.custom("Poppins Medium", size: 12) + } + + // Body fonts + struct Body { + static let light = Font.custom("Poppins Light", size: 14) + static let normal = Font.custom("Poppins Regular", size: 14) + static let medium = Font.custom("Poppins Medium", size: 14) + static let semibold = Font.custom("Poppins SemiBold", size: 14) + static let bold = Font.custom("Poppins Bold", size: 14) + } + + // Label fonts + struct Label { + static let light = Font.custom("Poppins Light", size: 12) + static let normal = Font.custom("Poppins Regular", size: 12) + static let medium = Font.custom("Poppins Medium", size: 12) + static let semibold = Font.custom("Poppins SemiBold", size: 12) + } + static let countdownNum = Font.custom("Poppins Medium", size: 36) static let gameScore = Font.custom("Poppins SemiBold", size: 18) static let gameTitle = Font.custom("Poppins Medium", size: 18) @@ -60,10 +84,10 @@ struct Constants { static let medium18 = Font.custom("Poppins Medium", size: 18) static let regular14 = Font.custom("Poppins Regular", size: 14) static let bold40 = Font.custom("Poppins Bold", size: 40) - + static let title = Font.system(size: 36, weight: .bold, design: .default) - static let header = Font.system(size: 24, weight: .bold, design: .default) - static let subheader = Font.system(size: 18, weight: .bold, design: .default) + static let header = Font.system(size: 24, weight: .semibold, design: .default) + static let subheader = Font.system(size: 18, weight: .semibold, design: .default) static let bodyBold = Font.system(size: 16, weight: .semibold, design: .default) static let body = Font.system(size: 16, weight: .regular, design: .default) static let caption = Font.system(size: 12, weight: .regular, design: .default) diff --git a/score-ios/Utils/Dates.swift b/score-ios/Utils/Dates.swift index 5e52042..1b029a1 100644 --- a/score-ios/Utils/Dates.swift +++ b/score-ios/Utils/Dates.swift @@ -40,33 +40,80 @@ extension Date { } static func parseDate(dateString: String, timeString: String) -> Date { - // parse the date without year + // Set up date formatter let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "MMM d (EEE)" // Matches "Feb 23 (Fri)" dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Ensures consistent parsing dateFormatter.timeZone = TimeZone(abbreviation: "UTC") // Adjust timezone if necessary - let parsedDate = dateFormatter.date(from: dateString) ?? Date() - - // parse the time + + // Try to parse with year format first + dateFormatter.dateFormat = "MMM d (EEE) yyyy" // Matches "Feb 23 (Fri) 2024" + var parsedDate = dateFormatter.date(from: dateString) + + // If that fails, fall back to parsing without year and determine the appropriate year + if parsedDate == nil { + dateFormatter.dateFormat = "MMM d (EEE)" // Matches "Feb 23 (Fri)" + parsedDate = dateFormatter.date(from: dateString) + + if let date = parsedDate { + let calendar = Calendar.current + let currentDate = Date() + let currentYear = calendar.component(.year, from: currentDate) + let currentMonth = calendar.component(.month, from: currentDate) + let parsedMonth = calendar.component(.month, from: date) + + // Create new date components from the parsed date + var dateComponents = calendar.dateComponents([.month, .day], from: date) + + // If month is between current month and January, assign current year + // Otherwise assign last year (handles past dates in same year) + if parsedMonth >= 1 && parsedMonth <= currentMonth { + dateComponents.year = currentYear + } else { + dateComponents.year = currentYear - 1 + } + + // Update parsedDate with the correct year + if let updatedDate = calendar.date(from: dateComponents) { + parsedDate = updatedDate + } + } + } + + // Standardize the time string format + var standardizedTimeString = timeString + standardizedTimeString = standardizedTimeString.replacingOccurrences(of: "p.m.", with: "PM") + standardizedTimeString = standardizedTimeString.replacingOccurrences(of: "a.m.", with: "AM") + standardizedTimeString = standardizedTimeString.replacingOccurrences(of: "pm", with: "PM") + standardizedTimeString = standardizedTimeString.replacingOccurrences(of: "am", with: "AM") + + // Cut off string after AM/PM + if let range = standardizedTimeString.range(of: "AM") { + standardizedTimeString = String(standardizedTimeString[.. Bool { + switch (lhs, rhs) { + case (.invalidInput(let lhsMessage), .invalidInput(let rhsMessage)): + return lhsMessage == rhsMessage + case (.networkError, .networkError): + return true + case (.emptyData, .emptyData): + return true + case (.unknownError, .unknownError): + return true + default: + return false + } + } + } diff --git a/score-ios/Utils/OrdinalSuffix.swift b/score-ios/Utils/OrdinalSuffix.swift index bfc7f1f..6814535 100644 --- a/score-ios/Utils/OrdinalSuffix.swift +++ b/score-ios/Utils/OrdinalSuffix.swift @@ -24,5 +24,8 @@ func ordinalSuffix(for number: Int) -> String { } func ordinalNumberString(for number: Int) -> String { + if number == -1 { + return "--" + } return "\(number)\(ordinalSuffix(for: number))" } diff --git a/score-ios/Utils/ShimmerModifier.swift b/score-ios/Utils/ShimmerModifier.swift new file mode 100644 index 0000000..85769f2 --- /dev/null +++ b/score-ios/Utils/ShimmerModifier.swift @@ -0,0 +1,72 @@ +// +// ShimmerModifier.swift +// score-ios +// +// Created by Jayson Hahn on 3/22/25. +// + +import Foundation +import SwiftUI + +/// A view modifier that applies a shimmer effect. +struct ShimmerEffect: ViewModifier { + + @State private var moveTo: CGFloat = -1.5 + + func body(content: Content) -> some View { + content + .hidden() + .overlay { + Rectangle() + .fill(Color.gray.opacity(0.3)) + .mask { + content + } + .overlay { + GeometryReader { + let size = $0.size + Rectangle() + .fill(Color.white.opacity(0.85)) + .mask { + Rectangle() + .fill( + .linearGradient( + colors: [ + .white.opacity(0), + .white.opacity(0.85).opacity(1), + .white.opacity(0) + ], + startPoint: .top, + endPoint: .bottom + ) + ) + .frame(width: 100) + .frame(maxHeight: .infinity) + .blur(radius: 30) + .rotationEffect(.init(degrees: 30)) + .offset(x: size.width * moveTo, y: 0) + } + } + .mask { + content + } + } + } + .onAppear { // Move onAppear out of the overlay chain + DispatchQueue.main.async { + moveTo = 1.5 + } + } + .animation(.linear(duration: 1.3).repeatForever(autoreverses: false), value: moveTo) + } +} + +extension View { + + @ViewBuilder + func shimmer() -> some View { + self + .modifier(ShimmerEffect()) + } + +} diff --git a/score-ios/Utils/String+Extension.swift b/score-ios/Utils/String+Extension.swift new file mode 100644 index 0000000..a36d26b --- /dev/null +++ b/score-ios/Utils/String+Extension.swift @@ -0,0 +1,17 @@ +// +// String+Extension.swift +// score-ios +// +// Created by Jay Zheng on 3/20/25. +// + +import SwiftUI + +extension String { + /// remove "University of " and replay it with "U", eg. University of Miama -> UMiami + func removingUniversityPrefix() -> String { + return self.localizedCaseInsensitiveContains("University of ") + ? self.replacingOccurrences(of: "University of ", with: "U ", options: .caseInsensitive) + : self + } +} diff --git a/score-ios/Utils/ViewModifiers/TrailingFadeGradient.swift b/score-ios/Utils/ViewModifiers/TrailingFadeGradient.swift new file mode 100644 index 0000000..8d12bc5 --- /dev/null +++ b/score-ios/Utils/ViewModifiers/TrailingFadeGradient.swift @@ -0,0 +1,41 @@ +// +// TrailingFadeGradient.swift +// score-ios +// +// Created by Jay Zheng on 3/20/25. +// + +import SwiftUI + +/// A view modifier that adds a trailing fade gradient to indicate scrollable content +struct TrailingFadeGradient: ViewModifier { + var width: CGFloat + var backgroundColor: Color + + init(width: CGFloat = 30, backgroundColor: Color = .white) { + self.width = width + self.backgroundColor = backgroundColor + } + + func body(content: Content) -> some View { + content + .overlay( + LinearGradient( + gradient: Gradient(colors: [.clear, backgroundColor]), + startPoint: .leading, + endPoint: .trailing + ) + .frame(width: width) + .allowsHitTesting(false), + alignment: .trailing + ) + } +} +// Add this as a view extension +extension View { + /// Adds a trailing fade gradient to indicate scrollable content + func withTrailingFadeGradient(width: CGFloat = 30, backgroundColor: Color = .white) -> some View { + self.modifier(TrailingFadeGradient(width: width, backgroundColor: backgroundColor)) + } +} + diff --git a/score-ios/ViewModels/GamesViewModel.swift b/score-ios/ViewModels/GamesViewModel.swift index 9573531..4eed585 100644 --- a/score-ios/ViewModels/GamesViewModel.swift +++ b/score-ios/ViewModels/GamesViewModel.swift @@ -8,18 +8,26 @@ import Foundation import SwiftUI -class GamesViewModel: ObservableObject +// State enum to track the loading state +enum DataState { + case idle // Initial state, nothing has been fetched yet + case loading // Fetch in progress + case success // Fetch completed successfully + case error(error: ScoreError) // Fetch failed with an error message +} + +class GamesViewModel: ObservableObject { - @Published var errorMessage: String? + @Published var dataState: DataState = .idle @Published var games: [Game] = [] // List of all games @Published var allUpcomingGames: [Game] = [] @Published var allPastGames: [Game] = [] - + // Carousel Logic @Published var selectedCardIndex: Int = 0 @Published var topUpcomingGames: [Game] = [] // Displayed in Carousel @Published var topPastGames: [Game] = [] - + // Filters Logic @Published var selectedSexIndex: Int = 0 @Published var selectedSex : Sex = .Both @@ -27,42 +35,62 @@ class GamesViewModel: ObservableObject @Published var selectedUpcomingGames: [Game] = [] // Based on the filters @Published var selectedPastGames: [Game] = [] @Published var sportSelectorOffset: CGFloat = 0 - + // Singleton structure so it is shared static let shared = GamesViewModel() private init() { } - + + var hasNotFetchedYet: Bool { + return dataState == .idle + } + // Filtering the data func filter() { self.selectedUpcomingGames = self.allUpcomingGames.filter{ game in // Filter by sex let matchesSex = (selectedSex == .Both) || (game.sex == selectedSex) - + // Filter by sport let matchesSport = (selectedSport == .All) || (game.sport == selectedSport) - + // Return true if both filters are satisfied return matchesSex && matchesSport } + self.selectedPastGames = self.allPastGames.filter{ game in // Filter by sex let matchesSex = (selectedSex == .Both) || (game.sex == selectedSex) - + // Filter by sport let matchesSport = (selectedSport == .All) || (game.sport == selectedSport) - + // Return true if both filters are satisfied return matchesSex && matchesSport } } - + // Networking func fetchGames() { + // Set loading state before fetch + dataState = .loading + NetworkManager.shared.fetchGames { fetchedGames, error in - self.games.removeAll() - self.allPastGames.removeAll() - self.allUpcomingGames.removeAll() - if let fetchedGames = fetchedGames { + DispatchQueue.main.async { + self.games.removeAll() + self.allPastGames.removeAll() + self.allUpcomingGames.removeAll() + + if let error = error { + self.dataState = .error(error: .networkError) + print("Error in fetchGames: \(error.localizedDescription)") + return + } + + guard let fetchedGames = fetchedGames else { + self.dataState = .error(error: .emptyData) + return + } + var updatedGames: [Game] = [] fetchedGames.indices.forEach { index in let gameData = fetchedGames[index] @@ -70,15 +98,15 @@ class GamesViewModel: ObservableObject if Sport.allCases.contains(game.sport) && game.sport != Sport.All { // append the game only if it is upcoming/live let now = Date() - let twoHours: TimeInterval = 2 * 60 * 60 + let twoHours: TimeInterval = 2 * 60 * 60 // TODO: How to decide if a game is live let calendar = Calendar.current let startOfToday = calendar.startOfDay(for: now) - + let isLive = game.date < now && now.timeIntervalSince(game.date) <= twoHours let isUpcoming = game.date > now let isFinishedToday = game.date < now && game.date >= startOfToday let isFinishedByToday = game.date < startOfToday - + updatedGames.append(game) if isLive { self.allUpcomingGames.insert(game, at: 0) @@ -99,13 +127,38 @@ class GamesViewModel: ObservableObject self.topUpcomingGames = Array(self.allUpcomingGames.prefix(3)) self.topPastGames = Array(self.allPastGames.suffix(3)) self.filter() + + // Update state to success + self.dataState = .success } - else if let error = error { - self.errorMessage = error.localizedDescription - print(ScoreEnvironment.baseURL) - print("Error in fetchGames: \(self.errorMessage ?? "Unknown error")") + } + } + + // Method to retry after an error + func retryFetch() { + fetchGames() + } +} + +extension DataState: Equatable { + + static func == (lhs: DataState, rhs: DataState) -> Bool { + switch (lhs, rhs) { + case (.idle, .idle): + return true + case (.loading, .loading): + return true + case (.success, .success): + return true + case (.error(let lhsError), .error(let rhsError)): + if lhsError == rhsError { + return true + } else { + return false } + default: + return false } - } + } diff --git a/score-ios/ViewModels/PastGameViewModel.swift b/score-ios/ViewModels/PastGameViewModel.swift new file mode 100644 index 0000000..6dd43c4 --- /dev/null +++ b/score-ios/ViewModels/PastGameViewModel.swift @@ -0,0 +1,58 @@ +// +// PastGameViewModel.swift +// score-ios +// +// Created by Hsia Lu wu on 3/5/25. +// + +import Foundation +import SwiftUI + +class PastGameViewModel: ObservableObject { + let game: Game + + var numberOfRounds: Int { + switch game.sport { + case .Baseball: return 9 + case .Soccer: return 2 + case .IceHockey: return 3 + case .FieldHockey, .Football, .Lacrosse: return 4 + default: return 1 + } + } + + // TODO: will be discarded once backend is changed to include a total score in scoreBreakdown for all sports + var cornellTotalScore: Int { + if game.timeUpdates.count == numberOfRounds { + return game.timeUpdates.reduce(0, { $0 + $1.cornellScore }) // sum up the score for each round + } else if game.timeUpdates.count == numberOfRounds + 1 { + // the last one is the sum + return game.timeUpdates[game.timeUpdates.count-1].cornellScore + } else if game.timeUpdates.count > numberOfRounds { + let scores = game.timeUpdates[0.. numberOfRounds { + let scores = game.timeUpdates[0.. numberOfRounds { - let scores = game.timeUpdates[0.. numberOfRounds { - var scores = game.timeUpdates[0.. some View { + private func firstRow(firstColWidth: CGFloat, columnWidth: CGFloat) -> some View { HStack(spacing: 0) { Text("") .font(Constants.Fonts.gameText) - .frame(width: 60, alignment: .leading) - + .frame(width: firstColWidth, alignment: .leading) - ForEach(1...numberOfRounds, id: \..self) { round in + ForEach(1...viewModel.numberOfRounds, id: \..self) { round in Text("\(round)") .frame(width: columnWidth) } @@ -90,62 +56,65 @@ extension DynamicScoreBox { Text("Total") .font(Constants.Fonts.gameText) - .frame(width: 36) .padding(.trailing, 3) - + .frame(width: columnWidth) } - .frame(width: 345, height: 40) + .padding(.vertical, 6) .background(Constants.Colors.primary_red) .foregroundStyle(Constants.Colors.white) } - private func secondRow(columnWidth: CGFloat) -> some View { + private func secondRow(firstColWidth: CGFloat, columnWidth: CGFloat) -> some View { HStack(spacing: 0) { Text("Cornell") .font(Constants.Fonts.gameText) - .frame(width: 55, alignment: .leading) .padding(.leading, 5) + .frame(width: firstColWidth, alignment: .leading) - ForEach(0.. some View { + private func thirdRow(firstColWidth: CGFloat, columnWidth: CGFloat) -> some View { HStack(spacing: 0) { - Text(game.opponent.name) - .lineLimit(1) - .font(Constants.Fonts.gameText) - .frame(width: 55, alignment: .leading) - .padding(.leading, 5) + ScrollView(.horizontal, showsIndicators: false){ + Text(game.opponent.name.removingUniversityPrefix()) + .lineLimit(1) + .font(Constants.Fonts.gameText) + .frame(alignment: .leading) + .padding(.leading, 5) + } + .frame(width: firstColWidth, alignment: .leading) + .withTrailingFadeGradient() - ForEach(0.. numberOfRounds { - var scores = game.timeUpdates[0.. numberOfRounds { - var scores = game.timeUpdates[0..: View { @State private var selectedCardIndex: Int = 0 var games: [Game] + var title: String let cardView: (Game) -> CardView let gameView: (Game) -> GameView var body: some View { - VStack (alignment: .center) { - Text("Upcoming") - .font(Constants.Fonts.semibold24) - .frame(maxWidth: .infinity, alignment: .leading) // Align to the left - .padding(.top, 24) - - // Carousel - TabView(selection: $selectedCardIndex) { - ForEach(games.indices, id: \.self) { index in - NavigationLink { - gameView(games[index]) - .navigationBarBackButtonHidden() - } label: { - cardView(games[index]) - .tag(index) + VStack (alignment: .center) { + Text(title) + .font(Constants.Fonts.semibold24) + .foregroundStyle(Constants.Colors.black) + .frame(maxWidth: .infinity, alignment: .leading) // Align to the left + .padding(.top, 24) + + // Carousel + TabView(selection: $selectedCardIndex) { + ForEach(games.indices, id: \.self) { index in + NavigationLink { + gameView(games[index]) + .navigationBarBackButtonHidden() + } label: { + cardView(games[index]) + .tag(index) + } } } - } - .frame(height: 220) - .tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic)) - - // TODO: make this geometry reader only occur for iPhones with notches? Not sure, will need to check older phones - GeometryReader { geometry in - if geometry.frame(in: .global).minY > 30 { - HStack(spacing: 32) { - ForEach(0..<3, id: \.self) { index in - Circle() - .fill(index == selectedCardIndex ? Constants.Colors.primary_red : Constants.Colors.unselected) - .frame(width: 10, height: 10) + .frame(height: 220) + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic)) + + // TODO: make this geometry reader only occur for iPhones with notches? Not sure, will need to check older phones + GeometryReader { geometry in + if geometry.frame(in: .global).minY > 30 { + HStack(spacing: 32) { + ForEach(0..<3, id: \.self) { index in + Circle() + .fill(index == selectedCardIndex ? Constants.Colors.primary_red : Constants.Colors.unselected) + .frame(width: 10, height: 10) + } } + .position(x: geometry.frame(in: .local).midX) } - .position(x: geometry.frame(in: .local).midX) } } - } - .padding(.bottom, 24) + + .padding(.bottom, 24) } } diff --git a/score-ios/Views/ListViews/FilterTile.swift b/score-ios/Views/ListViews/FilterTile.swift index e9e0d5e..ab62459 100644 --- a/score-ios/Views/ListViews/FilterTile.swift +++ b/score-ios/Views/ListViews/FilterTile.swift @@ -31,5 +31,5 @@ struct FilterTile : View { } #Preview () { - FilterTile(sport: .Basketball, selected: true) + FilterTile(sport: .IceHockey, selected: true) } diff --git a/score-ios/Views/ListViews/GameErrorView.swift b/score-ios/Views/ListViews/GameErrorView.swift new file mode 100644 index 0000000..4e79952 --- /dev/null +++ b/score-ios/Views/ListViews/GameErrorView.swift @@ -0,0 +1,56 @@ +// +// GameErrorView.swift +// score-ios +// +// Created by Jayson Hahn on 3/22/25. +// + +import Foundation +import SwiftUI + +struct GameErrorView: View { + + @ObservedObject var viewModel: GamesViewModel + + var body: some View { + ZStack { + Color.white + .edgesIgnoringSafeArea(.all) + + VStack { + Spacer() + + Image(systemName: "exclamationmark.bubble") + .resizable() + .frame(width: 64, height: 64) + .padding(.bottom, 16) + + Text("Oops! Schedules failed to load.") + .font(Constants.Fonts.Header.h2) + .padding(.bottom, 8) + + Text("Please try again later.") + .font(Constants.Fonts.Body.normal) + + Spacer() + + Button { + viewModel.fetchGames() + } label: { + HStack { + Image(systemName: "arrow.trianglehead.2.clockwise") + Text("Try again") + .font(Constants.Fonts.Body.medium) + } + .padding(.all, 10) + } + .background(Constants.Colors.crimson) + .foregroundColor(Constants.Colors.white) + .clipShape(Capsule()) + + Spacer() + } + } + } + +} diff --git a/score-ios/Views/ListViews/GameListView.swift b/score-ios/Views/ListViews/GameListView.swift index ec729e5..229f489 100644 --- a/score-ios/Views/ListViews/GameListView.swift +++ b/score-ios/Views/ListViews/GameListView.swift @@ -8,6 +8,7 @@ import SwiftUI struct GameListView: View { + let games: [Game] let tileView: (Game) -> TileView @@ -22,7 +23,7 @@ struct GameListView: View { let isCellCovered = cellGeometry.frame(in: .global).minY < 100 if !isCellCovered { NavigationLink { - GameView(game: game) + GameView(game: game, viewModel: PastGameViewModel(game: game)) .navigationBarBackButtonHidden() } label: { tileView(game) @@ -34,5 +35,6 @@ struct GameListView: View { .frame(height: 96) } } + .padding(.top, 16) } } diff --git a/score-ios/Views/ListViews/GameLoadingView.swift b/score-ios/Views/ListViews/GameLoadingView.swift new file mode 100644 index 0000000..827246f --- /dev/null +++ b/score-ios/Views/ListViews/GameLoadingView.swift @@ -0,0 +1,157 @@ +// +// GameLoadingView.swift +// score-ios +// +// Created by Jayson Hahn on 3/22/25. +// + +import Foundation +import SwiftUI + +/// The page types for loading states +enum LoadingPage: Equatable { + case upcoming + case past + + var title: String { + switch self { + case .upcoming: return "Upcoming" + case .past: return "Latest" + } + } + + var subtitle: String { + switch self { + case .upcoming: return "Schedule" + case .past: return "Scores" + } + } +} + +/// A full-page loading view that mimics the layout of the main game screens +/// Used for both the upcoming games and past games tabs +struct GameLoadingView: View { + + let page: LoadingPage + + // Layout constants + private enum Layout { + static let horizontalPadding: CGFloat = 24 + static let verticalSpacing: CGFloat = 10 + static let cardCornerRadius: CGFloat = 10 + static let indicatorSize: CGFloat = 10 + static let scheduleCornerRadius: CGFloat = 25 + static let sportIconSize: CGFloat = 25 + static let gameCardCornerRadius: CGFloat = 12 + static let gameCardHeight: CGFloat = 100 + } + + var body: some View { + ZStack { + Color(uiColor: .systemBackground) + .edgesIgnoringSafeArea(.all) + + GeometryReader { geometry in + VStack(alignment: .center, spacing: Layout.verticalSpacing) { + titleSection + carouselView + sexFilter + sportFilterSection + + Divider() + .frame(minHeight: 1) + .background(Constants.Colors.gray_border) + + gamesList + + Spacer(minLength: 0) + } + .frame(maxWidth: geometry.size.width, maxHeight: geometry.size.height) + .padding(.top, 50) + .shimmer() + .clipped() + } + } + } + + // MARK: - Component Views + + private var titleSection: some View { + HStack(spacing: 30) { + Text("Loading \(page.title)...") + .font(Constants.Fonts.Header.h1) + .padding(.horizontal, Layout.horizontalPadding) + Spacer() + } + } + + private var carouselView: some View { + VStack { + RoundedRectangle(cornerRadius: Layout.cardCornerRadius) + .frame(width: 345, height: 192) + .padding(.horizontal, Layout.horizontalPadding) + HStack(spacing: 30) { + Spacer() + ForEach(0..<3) { _ in + Circle() + .frame(width: Layout.indicatorSize, height: Layout.indicatorSize) + } + Spacer() + } + } + .padding(.vertical, 12) + } + + private var sexFilter: some View { + VStack(alignment: .leading, spacing: Layout.verticalSpacing) { + Text("Loading \(page.subtitle)...") + .font(Constants.Fonts.Header.h1) + .padding(.vertical, 10) + .padding(.horizontal, Layout.horizontalPadding) + + RoundedRectangle(cornerSize: CGSize(width: Layout.scheduleCornerRadius, height: Layout.scheduleCornerRadius)) + .frame(height: 50) + .padding(.horizontal, Layout.horizontalPadding) + } + } + + private var sportFilterSection: some View { + HStack(spacing: 15) { + ForEach(0..<5) { _ in + sportLoadingImage + } + Spacer() + } + .padding(.top, 20) + .padding(.bottom, 10) + .padding(.leading, Layout.horizontalPadding) + } + + private var sportLoadingImage: some View { + VStack(spacing: 10) { + Circle() + .frame(width: Layout.sportIconSize, height: Layout.sportIconSize) + + RoundedRectangle(cornerSize: CGSize(width: 10, height: 10)) + .frame(width: 60, height: 15) + } + } + + private var gamesList: some View { + VStack(spacing: 15) { + ForEach(0..<3) { _ in + RoundedRectangle(cornerSize: CGSize(width: Layout.gameCardCornerRadius, height: Layout.gameCardCornerRadius)) + .frame(height: Layout.gameCardHeight) + .padding(.horizontal, Layout.horizontalPadding) + } + } + .padding(.top, 15) + } + +} + +#Preview { + VStack { + GameLoadingView(page: .upcoming) + } +} diff --git a/score-ios/Views/ListViews/GameSectionHeaderView.swift b/score-ios/Views/ListViews/GameSectionHeaderView.swift index ef809e1..585ba34 100644 --- a/score-ios/Views/ListViews/GameSectionHeaderView.swift +++ b/score-ios/Views/ListViews/GameSectionHeaderView.swift @@ -8,14 +8,16 @@ import SwiftUI struct GameSectionHeaderView: View { + @StateObject private var vm = GamesViewModel.shared var headerTitle: String - + var body: some View { VStack { VStack { Text(headerTitle) .font(Constants.Fonts.semibold24) + .foregroundStyle(Constants.Colors.black) .frame(maxWidth: .infinity, alignment: .leading) // Align to the left PickerView(selectedSex: $vm.selectedSex, selectedIndex: $vm.selectedSexIndex) .padding(.bottom, 12) @@ -23,7 +25,7 @@ struct GameSectionHeaderView: View { SportSelectorView() } .padding(.bottom, 16) - + Divider() .background(.clear) } diff --git a/score-ios/Views/ListViews/PastGameTile.swift b/score-ios/Views/ListViews/PastGameTile.swift index 418d3b5..0f53a22 100644 --- a/score-ios/Views/ListViews/PastGameTile.swift +++ b/score-ios/Views/ListViews/PastGameTile.swift @@ -9,62 +9,25 @@ import SwiftUI struct PastGameTile: View { var game: Game - - private var numberOfRounds: Int { - switch game.sport { - case .Baseball: return 9 - case .Basketball, .Soccer, .Volleyball: return 2 - case .IceHockey: return 3 - case .FieldHockey, .Football, .Lacrosse, .SprintFootball: return 4 - default: return 1 - } - } - - private var cornellTotalScore: Int { - if game.timeUpdates.count == numberOfRounds { - return game.timeUpdates.reduce(0, { $0 + $1.cornellScore }) // sum up the score for each round - } else if game.timeUpdates.count == numberOfRounds + 1 { - // the last one is the sum - return game.timeUpdates[game.timeUpdates.count-1].cornellScore - } else if game.timeUpdates.count > numberOfRounds { - var scores = game.timeUpdates[0.. numberOfRounds { - var scores = game.timeUpdates[0.. opponentTotalScore - let tie = cornellTotalScore == opponentTotalScore + let corWon = viewModel.cornellTotalScore > viewModel.opponentTotalScore + let tie = viewModel.cornellTotalScore == viewModel.opponentTotalScore + let corScore = (viewModel.cornellTotalScore == -1) ? "-" : "\(viewModel.cornellTotalScore)" + let oppScore = (viewModel.opponentTotalScore == -1) ? "-" : "\(viewModel.opponentTotalScore)" HStack { // VStack of school names and logos and score ZStack { HStack { - Spacer() // Pushes the rectangle to the right side Spacer() Rectangle() .frame(width: 2, height: 70) // Adjust the thickness of the right border here .foregroundColor(Constants.Colors.gray_liner) // Color of the right border } - VStack { + VStack(spacing: 16) { // Opponent score HStack { AsyncImage(url: URL(string: game.opponent.image)) { image in @@ -72,29 +35,47 @@ struct PastGameTile: View { } .frame(width: 20, height: 20) - Text(game.opponent.name) - .font(Constants.Fonts.gameTitle) - .lineLimit(1) + if (corWon || tie) { + ScrollView(.horizontal, showsIndicators: false){ + Text(game.opponent.name.removingUniversityPrefix()) + .font(Constants.Fonts.gameTitle) + .foregroundStyle(Constants.Colors.gray_text) + .lineLimit(1) + } + .withTrailingFadeGradient() + } else { + ScrollView(.horizontal, showsIndicators: false){ + Text(game.opponent.name.removingUniversityPrefix()) + .font(Constants.Fonts.gameTitle) + .foregroundStyle(Constants.Colors.black) + .lineLimit(1) + } + .withTrailingFadeGradient() + } + Spacer() // Opponent Score with Arrow if corWon { - Text(String(opponentTotalScore)) + Text(oppScore) .foregroundStyle(Constants.Colors.gray_text) .font(Constants.Fonts.medium18) } else if !tie { + // opponent won HStack { - Text(String(opponentTotalScore)) + Text(oppScore) .font(Constants.Fonts.semibold18) + .foregroundStyle(Constants.Colors.gray_text) Image("pastGame_arrow_back") .resizable() .frame(width: 11, height: 14) } .offset(x: 20) } else { - Text(String(opponentTotalScore)) - .font(Constants.Fonts.semibold18) + Text(oppScore) + .font(Constants.Fonts.medium18) + .foregroundStyle(Constants.Colors.gray_text) } } @@ -105,30 +86,46 @@ struct PastGameTile: View { .resizable() .frame(width: 20, height: 20) - Text("Cornell") - .font(Constants.Fonts.gameTitle) + if (corWon) { + Text("Cornell") + .font(Constants.Fonts.gameTitle) + .foregroundStyle(Constants.Colors.black) + } else { + Text("Cornell") + .font(Constants.Fonts.gameTitle) + .foregroundStyle(Constants.Colors.gray_text) + } + Spacer() + // Cornell Score with Arrow if corWon { HStack { - Text(String(cornellTotalScore)) + Text(corScore) + .foregroundStyle(Constants.Colors.gray_text) .font(Constants.Fonts.semibold18) + Image("pastGame_arrow_back") .resizable() .frame(width: 11, height: 14) } .offset(x: 20) - } else { - Text(String(cornellTotalScore)) + } else if !tie { + // opponent won + Text(corScore) + .font(Constants.Fonts.medium18) .foregroundStyle(Constants.Colors.gray_text) + } else { + Text(corScore) .font(Constants.Fonts.medium18) + .foregroundStyle(Constants.Colors.gray_text) } } } - .padding(.leading, 16) - .padding(.trailing, 24) + .padding(.vertical, 16) + .padding(.horizontal, 20) .frame(maxWidth: .infinity) } @@ -136,7 +133,7 @@ struct PastGameTile: View { // game info: sport, sex, time - VStack { + VStack(spacing: 16) { HStack(spacing: 8) { // Sport icon // TODO: frame 24*24 @@ -160,8 +157,7 @@ struct PastGameTile: View { } } .padding(.trailing, 20) - .padding(.bottom, 22) - + HStack { Text(Date.dateToString(date: game.date)) .font(Constants.Fonts.gameDate) @@ -171,7 +167,7 @@ struct PastGameTile: View { } .padding(.leading, 24) } - .frame(width: 345, height: 96) + .frame(height: 96) .background(Constants.Colors.white) .clipShape(RoundedRectangle(cornerRadius: 12)) .background( @@ -183,5 +179,5 @@ struct PastGameTile: View { } #Preview { - PastGameTile(game: Game.dummyData[7]) + PastGameTile(game: Game.dummyData[7], viewModel: PastGameViewModel(game: Game.dummyData[7])) } diff --git a/score-ios/Views/ListViews/PastGamesView.swift b/score-ios/Views/ListViews/PastGamesView.swift index 26685a7..c38f43b 100644 --- a/score-ios/Views/ListViews/PastGamesView.swift +++ b/score-ios/Views/ListViews/PastGamesView.swift @@ -9,54 +9,64 @@ import SwiftUI struct PastGamesView: View { var paddingMain : CGFloat = 20 - + // State variables @StateObject private var vm = GamesViewModel.shared - + var body: some View { - NavigationView { - ScrollView(.vertical, showsIndicators: false) { - ZStack { - LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { - CarouselView(games: vm.topPastGames, - cardView: { game in - PastGameCard(game: game) - }, - gameView: { game in - GameView(game: game) - }) - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain) - - Section(header: GameSectionHeaderView(headerTitle: "Past Scores") - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain)) { - - // List of games - GameListView(games: vm.selectedPastGames) { game in - PastGameTile(game: game) + ZStack { + NavigationView { + ScrollView(.vertical, showsIndicators: false) { + ZStack { + LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { + CarouselView(games: vm.topPastGames, title: "Latest", + cardView: { game in + PastGameCard(game: game, viewModel: PastGameViewModel(game: game)) + }, + gameView: { game in + GameView(game: game, viewModel: PastGameViewModel(game: game)) + }) + .padding(.horizontal, paddingMain) + + Section(header: GameSectionHeaderView(headerTitle: "All Scores") + .padding(.horizontal, paddingMain)) { + + // List of games + GameListView(games: vm.selectedPastGames) { game in + PastGameTile(game: game, viewModel: PastGameViewModel(game: game)) + } + .padding(.horizontal, paddingMain) } - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain) + .background(Color.white) + .edgesIgnoringSafeArea(.top) } - .background(Color.white) - .edgesIgnoringSafeArea(.top) + .safeAreaInset(edge: .bottom, content: { + Color.clear.frame(height: 20) + }) + .frame(maxWidth: .infinity, maxHeight: .infinity) } - .safeAreaInset(edge: .bottom, content: { - Color.clear.frame(height: 20) - }) - .frame(maxWidth: .infinity, maxHeight: .infinity) } + .background(Color.white) + } + .onAppear { + if vm.hasNotFetchedYet { + vm.fetchGames() + } + } + .onChange(of: vm.selectedSport) { + vm.filter() + } + .onChange(of: vm.selectedSex) { + vm.filter() + } + + if case .loading = vm.dataState { + GameLoadingView(page: .past) + } + + if case .error = vm.dataState { + GameErrorView(viewModel: vm) } - } - .onAppear { - vm.fetchGames() - } - .onChange(of: vm.selectedSport) { - vm.filter() - } - .onChange(of: vm.selectedSex) { - vm.filter() } } } diff --git a/score-ios/Views/ListViews/PickerView.swift b/score-ios/Views/ListViews/PickerView.swift index 7f4b2a9..fe3e5a3 100644 --- a/score-ios/Views/ListViews/PickerView.swift +++ b/score-ios/Views/ListViews/PickerView.swift @@ -7,73 +7,65 @@ import SwiftUI -/// Custom PickerView code heavily inspired from https://www.reddit.com/r/SwiftUI/comments/qonfey/how_do_i_get_a_picker_that_looks_like_this_very/ - -private var buttonWidth : CGFloat = 111 -private var buttonHeight : CGFloat = 37 - struct PickerView: View { + @Binding var selectedSex: Sex @Binding var selectedIndex: Int - + + // Constants for visual style - can be adjusted as needed + private let capsulePadding: CGFloat = 6 + private let borderWidth: CGFloat = 2 + private let highlightHeight: CGFloat = 37 + var body: some View { - let offsetScalar: Int = selectedIndex == 0 ? -1 : selectedIndex - - ZStack (alignment: .leading) { - - GeometryReader { geometry in - Capsule() - .fill(Constants.Colors.selected) - .frame(width: buttonWidth, height: buttonHeight) - .offset(x: CGFloat(selectedIndex) * (geometry.size.width / CGFloat(Sex.allCases.count)) - CGFloat(5 * (offsetScalar))) - .animation(.spring(), value: selectedSex) - } - .frame(height: buttonHeight) - - // Options - HStack (spacing: 0) { - ForEach( - Array(Sex.allCases.enumerated()), - id: \.1 - ) { - index, sex in - if selectedSex == sex { - PickerSegmentView(sex: sex, selected: true) - } else { - PickerSegmentView(sex: sex, selected: false) - .onTapGesture { - withAnimation(.spring()) { - selectedSex = sex - selectedIndex = index - } + GeometryReader { geometry in + let totalWidth = geometry.size.width + let totalHeight = geometry.size.height + let segmentWidth = (totalWidth - (capsulePadding * 2)) / CGFloat(Sex.allCases.count) + + ZStack { + // Background capsule + Capsule() + .fill(Color.clear) // Clear background + + // Selected capsule indicator - smaller height + Capsule() + .fill(Constants.Colors.selected) + .frame(width: segmentWidth, height: highlightHeight) + .offset(x: CGFloat(selectedIndex) * segmentWidth - (totalWidth - capsulePadding * 2) / 2 + segmentWidth / 2) + .animation(.spring(), value: selectedIndex) + + // Option buttons + HStack(spacing: 0) { + ForEach(Array(Sex.allCases.enumerated()), id: \.1) { index, sex in + Button(action: { + withAnimation(.spring()) { + selectedSex = sex + selectedIndex = index } - .clipShape(Capsule()) + }) { + Text(sex.description) + .font(Constants.Fonts.gameTitle) + .foregroundStyle(selectedIndex == index ? Constants.Colors.selectedText : Constants.Colors.unselectedText) + .frame(width: segmentWidth, height: totalHeight - capsulePadding * 2) + .contentShape(Rectangle()) + } + .buttonStyle(PlainButtonStyle()) } } - } - .padding(6) - .clipShape(Capsule()) - .overlay( + .padding(capsulePadding) + + // Border overlay Capsule() - .stroke(Constants.Colors.gray_liner, lineWidth: 2) - .frame(width: 345, height: 49) - ) + .stroke(Constants.Colors.gray_liner, lineWidth: borderWidth) + .frame(width: totalWidth, height: totalHeight) + } } + .frame(height: 49) } -} -struct PickerSegmentView: View { - var sex : Sex - var selected : Bool - - var body: some View { - Text(sex.description) - .font(Constants.Fonts.gameTitle) - .foregroundStyle(selected ? Constants.Colors.selectedText : Constants.Colors.unselectedText) - .frame(width: buttonWidth, height: buttonHeight) - } } -//#Preview { -// PickerView(selectedSex: Sex.Both) -//} +#Preview { + PickerView(selectedSex: .constant(Sex.Both), selectedIndex: .constant(0)) +} diff --git a/score-ios/Views/ListViews/UpcomingGameTile.swift b/score-ios/Views/ListViews/UpcomingGameTile.swift index 6c87a9e..75483a0 100644 --- a/score-ios/Views/ListViews/UpcomingGameTile.swift +++ b/score-ios/Views/ListViews/UpcomingGameTile.swift @@ -8,44 +8,43 @@ import SwiftUI struct UpcomingGameTile: View { - + var game: Game - + var body: some View { let liveNow: Bool = game.date == Date.currentDate - - VStack { + + VStack(spacing: 16) { // Opponent Logo, Opponent Name | Sport Icon, Sex Icon HStack(spacing: 8) { HStack(spacing: 8) { AsyncImage(url: URL(string: game.opponent.image)) {image in image.resizable() } placeholder: { + // TODO: Make a placeholder image Constants.Colors.gray_icons } .frame(width: 20, height: 20) - - Text(game.opponent.name) - .font(Constants.Fonts.gameTitle) - .lineLimit(1) + + ScrollView(.horizontal, showsIndicators: false){ + Text(game.opponent.name.removingUniversityPrefix()) + .font(Constants.Fonts.gameTitle) + .foregroundStyle(Constants.Colors.black) + .lineLimit(1) + } + .withTrailingFadeGradient() } .padding(.leading, 20) - + Spacer() - + HStack(spacing: 8) { -// // Sport icon -// Image(game.sport.rawValue+"-g") -// .resizable() -// .renderingMode(.template) -// .scaledToFill() -// .frame(width: 19, height: 19) -// .foregroundStyle(Constants.Colors.iconGray) - + ZStack { Circle() .frame(width: 19, height: 19) .foregroundStyle(.white) + Image(game.sport.rawValue+"-g") .resizable() .renderingMode(.template) @@ -53,22 +52,23 @@ struct UpcomingGameTile: View { .frame(width: 19, height: 19) .foregroundStyle(Constants.Colors.iconGray) } - + // Sex icon ZStack { Circle() .frame(width: 19, height: 19) .foregroundStyle(.gray) + Image(game.sex.description) .resizable() .renderingMode(.template) .frame(width: 19, height: 19) .foregroundStyle(Constants.Colors.white) } - } + } .padding(.trailing, 20) } - + // Location Icon, City, State | Date HStack { HStack (spacing: 4) { @@ -77,10 +77,12 @@ struct UpcomingGameTile: View { .renderingMode(/*@START_MENU_TOKEN@*/.template/*@END_MENU_TOKEN@*/) .frame(width: 13.7, height: 20) .foregroundStyle(Constants.Colors.iconGray) - Text("\(game.city), \(game.state)") + + Text(gameLocation(city: game.city, state: game.state)) .font(Constants.Fonts.gameText) .foregroundStyle(Constants.Colors.gray_text) - } .padding(.leading, 20) + } + .padding(.leading, 20) Spacer() @@ -97,17 +99,28 @@ struct UpcomingGameTile: View { } } } - .frame(width: 345, height: 96) - .background(Constants.Colors.white) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .background( - RoundedRectangle(cornerRadius: 12) - .stroke(Constants.Colors.gray_border, lineWidth: 1) - .shadow(radius: 5) - ) + .padding(.vertical, 16) + .background(Constants.Colors.white) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .background( + RoundedRectangle(cornerRadius: 12) + .stroke(Constants.Colors.gray_border, lineWidth: 1) + .shadow(radius: 5) + ) } } #Preview { UpcomingGameTile(game: Game.dummyData[7]) } + +extension UpcomingGameTile { + + func gameLocation(city: String, state: String) -> String { + if city.isEmpty { + return "N/A" + } + return "\(city), \(state)" + } + +} diff --git a/score-ios/Views/ListViews/UpcomingGamesView.swift b/score-ios/Views/ListViews/UpcomingGamesView.swift index 7bbef31..82b056d 100644 --- a/score-ios/Views/ListViews/UpcomingGamesView.swift +++ b/score-ios/Views/ListViews/UpcomingGamesView.swift @@ -8,56 +8,67 @@ import SwiftUI struct UpcomingGamesView: View { + // State variables - var paddingMain : CGFloat = 20 + var paddingMain: CGFloat = 20 @State private var selectedCardIndex: Int = 0 @StateObject private var vm = GamesViewModel.shared - + // Main view var body: some View { - NavigationView { - ScrollView (.vertical, showsIndicators: false) { - - ZStack { - LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { - CarouselView(games: vm.topUpcomingGames, - cardView: { game in - UpcomingGameCard(game: game) - }, - gameView: { game in - GameView(game: game) - }) - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain) - - Section(header: GameSectionHeaderView(headerTitle: "Upcoming Game Schedule") - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain)) { - - // List of games - GameListView(games: vm.selectedUpcomingGames) { game in - UpcomingGameTile(game: game) + ZStack { + NavigationView { + ScrollView (.vertical, showsIndicators: false) { + ZStack { + LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { + CarouselView(games: vm.topUpcomingGames, title: "Upcoming", + cardView: { game in + UpcomingGameCard(game: game) + }, + gameView: { game in + GameView(game: game, viewModel: PastGameViewModel(game: game)) + }) + .padding(.horizontal, paddingMain) + + + Section(header: GameSectionHeaderView(headerTitle: "Game Schedule") + .padding(.horizontal, paddingMain)) { + + // List of games + GameListView(games: vm.selectedUpcomingGames) { game in + UpcomingGameTile(game: game) } - .padding(.leading, paddingMain) - .padding(.trailing, paddingMain) - }.background(Color.white) - .edgesIgnoringSafeArea(.top) + .padding(.horizontal, paddingMain) + } + .background(Color.white) + .edgesIgnoringSafeArea(.top) + } + .safeAreaInset(edge: .bottom, content: { + Color.clear.frame(height: 20) + }) + .frame(maxWidth: .infinity, maxHeight: .infinity) } - .safeAreaInset(edge: .bottom, content: { - Color.clear.frame(height: 20) - }) - .frame(maxWidth: .infinity, maxHeight: .infinity) } } - } - .onAppear { - vm.fetchGames() - } - .onChange(of: vm.selectedSport) { - vm.filter() - } - .onChange(of: vm.selectedSex) { - vm.filter() + .onAppear { + if vm.hasNotFetchedYet { + vm.fetchGames() + } + } + .onChange(of: vm.selectedSport) { + vm.filter() + } + .onChange(of: vm.selectedSex) { + vm.filter() + } + + if case .loading = vm.dataState { + GameLoadingView(page: .upcoming) + } + + if case .error = vm.dataState { + GameErrorView(viewModel: vm) + } } } } diff --git a/score-ios/Views/MainViews/MainTabView.swift b/score-ios/Views/MainViews/MainTabView.swift index 0376b5c..6a0c915 100644 --- a/score-ios/Views/MainViews/MainTabView.swift +++ b/score-ios/Views/MainViews/MainTabView.swift @@ -8,10 +8,12 @@ import SwiftUI struct MainTabView: View { + // MARK: Properties + @Binding var selection: Int @StateObject private var gamesViewModel = GamesViewModel.shared - + var body: some View { NavigationStack { ZStack(alignment: .bottom) { @@ -22,26 +24,29 @@ struct MainTabView: View { PastGamesView() .environmentObject(gamesViewModel) } - } - - HStack { - ForEach(0..<2, id: \.self) { - index in - TabViewIcon(selectionIndex: $selection, itemIndex: index) - .frame(width: 67, height: 45) - if index != 1 { - Spacer() + + HStack { + ForEach(0..<2, id: \.self) { + index in + TabViewIcon(selectionIndex: $selection, itemIndex: index) + .frame(width: 67, height: 45) + .padding(.top, 10) + if index != 1 { + Spacer() + } } } + .padding(.horizontal, 86) + .padding(.bottom, 40) + .frame(maxWidth: .infinity) + .background( + Color.white + .shadow(color: Color.black.opacity(0.25), radius: 10, x: 0, y: -6) + ) + } .ignoresSafeArea(edges: .bottom) - .padding(.leading, 86) - .padding(.trailing, 86) - .padding(.top, 10) - .frame(maxWidth: .infinity) - .background(Constants.Colors.white) - .shadow(radius: 6) - + .background(Color.white) } } } diff --git a/score-ios/Views/MainViews/TabViewIcon.swift b/score-ios/Views/MainViews/TabViewIcon.swift index 799dd51..2937b87 100644 --- a/score-ios/Views/MainViews/TabViewIcon.swift +++ b/score-ios/Views/MainViews/TabViewIcon.swift @@ -8,27 +8,28 @@ import SwiftUI struct TabViewIcon: View { + // MARK: - Properties - @Binding var selectionIndex: Int + @Binding var selectionIndex: Int + + let itemIndex: Int + private let tabItems = ["schedule", "scoreboard"] - let itemIndex: Int - private let tabItems = ["schedule", "scoreboard"] - var body: some View { Button { - selectionIndex = itemIndex - } label: { - VStack { - Image(itemIndex == selectionIndex ? "\(tabItems[itemIndex])-selected" : tabItems[itemIndex]) - .resizable() - .frame(width: 28, height: 28) - .tint(Constants.Colors.gray_icons) - Text(itemIndex == 0 ? "Schedule" : "Scores") - .font(Constants.Fonts.buttonLabel) - .foregroundStyle(itemIndex == selectionIndex ? Constants.Colors.primary_red : Constants.Colors.unselectedText) - } - } + selectionIndex = itemIndex + } label: { + VStack { + Image(itemIndex == selectionIndex ? "\(tabItems[itemIndex])-selected" : tabItems[itemIndex]) + .resizable() + .frame(width: 28, height: 28) + .tint(Constants.Colors.gray_icons) + Text(itemIndex == 0 ? "Schedule" : "Scores") + .font(Constants.Fonts.buttonLabel) + .foregroundStyle(itemIndex == selectionIndex ? Constants.Colors.primary_red : Constants.Colors.unselectedText) + } + } } }