diff --git a/ios/beartracks/beartracks-scout.xcodeproj/project.pbxproj b/ios/beartracks/beartracks-scout.xcodeproj/project.pbxproj index d1ca436e..5cb2d113 100644 --- a/ios/beartracks/beartracks-scout.xcodeproj/project.pbxproj +++ b/ios/beartracks/beartracks-scout.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ F565840F2B54D52A00F587C0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; F58565C32B7572B400C1606E /* beartracks-scout.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "beartracks-scout.entitlements"; sourceTree = ""; }; F5AD47CB2B54FF0500345122 /* ReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewView.swift; sourceTree = ""; }; + F5B57DEE2BA2989E00C9D95F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -90,6 +91,7 @@ F565840C2B54D52A00F587C0 /* Assets.xcassets */, F55ABD6C2B9C40C4006E7FAF /* Localizable.xcstrings */, F565840E2B54D52A00F587C0 /* Preview Content */, + F5B57DEE2BA2989E00C9D95F /* PrivacyInfo.xcprivacy */, ); path = "beartracks-scout"; sourceTree = ""; diff --git a/ios/beartracks/beartracks-scout/EndView.swift b/ios/beartracks/beartracks-scout/EndView.swift index 437bf3fe..9ea77d9a 100644 --- a/ios/beartracks/beartracks-scout/EndView.swift +++ b/ios/beartracks/beartracks-scout/EndView.swift @@ -32,7 +32,7 @@ struct EndView: View { controller.switches.2 = false } } - Toggle("Buddy climb", isOn: $controller.switches.2) + Toggle("Harmony", isOn: $controller.switches.2) .disabled(!controller.switches.1) } .padding() diff --git a/ios/beartracks/beartracks-scout/GameView.swift b/ios/beartracks/beartracks-scout/GameView.swift index 2bddcd51..c436bc93 100644 --- a/ios/beartracks/beartracks-scout/GameView.swift +++ b/ios/beartracks/beartracks-scout/GameView.swift @@ -13,12 +13,17 @@ struct GameView: View { @State private var holdLengths: (TimeInterval, TimeInterval, TimeInterval) = (0.0, 0.0, 0.0) @State private var timer: Timer? @State private var actionState: ActionState = .neutral + @State private var releaseState: ReleaseState = .neutral @State private var ballOffset: CGSize = .zero enum ActionState { case neutral, intake, travel, outtake } + enum ReleaseState { + case neutral, speaker, amplifier, other + } + var body: some View { NavigationStack { if controller.getTeamNumber() != "--" && controller.getMatchNumber() != 0 { @@ -30,31 +35,38 @@ struct GameView: View { Spacer() VStack { Text("intake") + .foregroundStyle(Color.gray) Text(String(format: "%.1f", holdLengths.0)) + .foregroundStyle(Color.init(red: 69 / 255, green: 133 / 255, blue: 136 / 255)) } Spacer() VStack { - Text("move") + Text("travel") + .foregroundStyle(Color.gray) Text(String(format: "%.1f", holdLengths.1)) + .foregroundStyle(Color.init(red: 177 / 255, green: 98 / 255, blue: 134 / 255)) } Spacer() VStack { Text("outtake") + .foregroundStyle(Color.gray) Text(String(format: "%.1f", holdLengths.2)) + .foregroundStyle(Color.init(red: 104 / 255, green: 157 / 255, blue: 106 / 255)) } Spacer() } .frame(maxWidth: .infinity, alignment: .center) .padding(.top) - if controller.matchTimes.count == 0 || UserDefaults.standard.bool(forKey: "showLabels") { - VStack { - HStack { + VStack { + HStack { + if releaseState == .other { Text("other") - .foregroundStyle(getLabelColor()) - .frame(maxWidth: .infinity, alignment: .leading) + .foregroundStyle(Color.init(red: 254 / 255, green: 128 / 255, blue: 25 / 255)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) + } else { Text("—") - .foregroundStyle(getLabelColor()) - .frame(maxWidth: .infinity, alignment: .trailing) + .foregroundStyle(getLabelColor(state: true, type: nil)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) } } } @@ -115,41 +127,48 @@ struct GameView: View { self.holdLengths = (0, 0, 0) } self.actionState = .neutral + self.releaseState = .neutral })) - if controller.matchTimes.count == 0 || UserDefaults.standard.bool(forKey: "showLabels") { - VStack { - Spacer() - HStack { - Text("amp —") - .foregroundStyle(getLabelColor()) - .frame(maxWidth: .infinity, alignment: .trailing) - } - Spacer() - Spacer() - HStack { - Spacer() - Text("intake") - .foregroundStyle(getLabelColor()) - Spacer() - Text("travel") - .foregroundStyle(getLabelColor()) - Spacer() - Text("outtake") - .foregroundStyle(getLabelColor()) - Spacer() + VStack { + Spacer() + HStack { + if releaseState == .amplifier { + Text("amplifier") + .foregroundStyle(Color.init(red: 250 / 255, green: 189 / 255, blue: 47 / 255)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) + } else { + Text("—") + .foregroundStyle(getLabelColor(state: true, type: nil)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) } + } + Spacer(); Spacer() + HStack { Spacer() + + Text("intake") + .foregroundStyle(getLabelColor(state: false, type: .intake)) Spacer() + Text("travel") + .foregroundStyle(getLabelColor(state: false, type: .travel)) Spacer() + Text("outtake") + .foregroundStyle(getLabelColor(state: false, type: .outtake)) Spacer() - Spacer() - HStack { - Text("speaker —") - .foregroundStyle(getLabelColor()) - .frame(maxWidth: .infinity, alignment: .trailing) + } + Spacer(); Spacer(); Spacer(); Spacer(); Spacer() + HStack { + if releaseState == .speaker { + Text("speaker") + .foregroundStyle(Color.init(red: 184 / 255, green: 187 / 255, blue: 38 / 255)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) + } else { + Text("—") + .foregroundStyle(getLabelColor(state: true, type: nil)) + .frame(maxWidth: .infinity, alignment: getLabelAlignment()) } - Spacer() } + Spacer() } } .padding(.bottom) @@ -210,26 +229,32 @@ struct GameView: View { } } - private func updateBallOffset( - dragValue: DragGesture.Value, totalWidth: CGFloat, totalHeight: CGFloat - ) { - if abs(self.ballOffset.height) > totalHeight * 0.2 - && abs(dragValue.translation.height) <= totalHeight * 0.2 - { + private func updateBallOffset(dragValue: DragGesture.Value, totalWidth: CGFloat, totalHeight: CGFloat) { + if abs(self.ballOffset.height) > totalHeight * 0.2 && abs(dragValue.translation.height) <= totalHeight * 0.2 { UIImpactFeedbackGenerator(style: .rigid).impactOccurred() - } else if abs(self.ballOffset.height) <= totalHeight * 0.2 - && abs(dragValue.translation.height) > totalHeight * 0.2 - { + } else if abs(self.ballOffset.height) <= totalHeight * 0.2 && abs(dragValue.translation.height) > totalHeight * 0.2 { UIImpactFeedbackGenerator(style: .light).impactOccurred() } let newOffset = CGSize( - width: min( - max(dragValue.translation.width, (totalWidth * -0.5) + 30), (totalWidth * 0.5) - 30), + width: min(max(dragValue.translation.width, (totalWidth * -0.5) + 30), (totalWidth * 0.5) - 30), height: dragValue.translation.height ) - self.ballOffset = newOffset + + if abs(self.ballOffset.height) >= totalHeight * 0.2 { + if self.ballOffset.height < 0 { + if abs(self.ballOffset.height) >= totalHeight * 0.475 { + releaseState = .other + } else { + releaseState = .amplifier + } + } else { + releaseState = .speaker + } + } else { + self.releaseState = .neutral + } } private func getUIImage(position: ActionState, height: CGFloat) -> Image { @@ -282,11 +307,32 @@ struct GameView: View { } } - private func getLabelColor() -> Color { - if controller.matchTimes.isEmpty { + private func getLabelColor(state: Bool, type: ActionState?) -> Color { + if controller.matchTimes.isEmpty && state { return Color.primary } else { - return Color.gray + if state || actionState != type { + return Color.gray + } else { + switch actionState { + case .neutral: + return Color.init(red: 251 / 255, green: 241 / 255, blue: 199 / 255) + case .intake: + return Color.init(red: 69 / 255, green: 133 / 255, blue: 136 / 255) + case .travel: + return Color.init(red: 177 / 255, green: 98 / 255, blue: 134 / 255) + case .outtake: + return Color.init(red: 104 / 255, green: 157 / 255, blue: 106 / 255) + } + } + } + } + + private func getLabelAlignment() -> Alignment { + if UserDefaults.standard.bool(forKey: "leftHand") { + return .trailing + } else { + return .leading } } } diff --git a/ios/beartracks/beartracks-scout/Localizable.xcstrings b/ios/beartracks/beartracks-scout/Localizable.xcstrings index 3c4533af..8f814e75 100644 --- a/ios/beartracks/beartracks-scout/Localizable.xcstrings +++ b/ios/beartracks/beartracks-scout/Localizable.xcstrings @@ -59,6 +59,7 @@ } }, "Alliance wing notes handled (%lld)" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -69,6 +70,7 @@ } }, "amp —" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -79,7 +81,6 @@ } }, "amplifier" : { - "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -140,6 +141,7 @@ } }, "Buddy climb" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -349,6 +351,16 @@ } } }, + "Harmony" : { + "localizations" : { + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Armonía" + } + } + } + }, "How was the driving? Did the driver seem confident?" : { "localizations" : { "es-419" : { @@ -369,6 +381,16 @@ } } }, + "Left-Handed Labels" : { + "localizations" : { + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etiquetas para zurdos" + } + } + } + }, "log in" : { "localizations" : { "es-419" : { @@ -452,6 +474,7 @@ } }, "move" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -461,7 +484,18 @@ } } }, + "Neutral notes handled (%lld)" : { + "localizations" : { + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notas neutrales manejadas (%lld)" + } + } + } + }, "Neutral zone notes handled (%lld)" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -532,6 +566,7 @@ } }, "Persist Area Labels" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -663,7 +698,6 @@ } }, "speaker" : { - "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -684,6 +718,7 @@ } }, "speaker —" : { + "extractionState" : "stale", "localizations" : { "es-419" : { "stringUnit" : { @@ -842,6 +877,16 @@ } } } + }, + "Wing notes handled (%lld)" : { + "localizations" : { + "es-419" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notas de ala manejadas (%lld)" + } + } + } } }, "version" : "1.0" diff --git a/ios/beartracks/beartracks-scout/PrivacyInfo.xcprivacy b/ios/beartracks/beartracks-scout/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..36a611c0 --- /dev/null +++ b/ios/beartracks/beartracks-scout/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/ios/beartracks/beartracks-scout/ScoutingController.swift b/ios/beartracks/beartracks-scout/ScoutingController.swift index d3a80da2..3e63286b 100644 --- a/ios/beartracks/beartracks-scout/ScoutingController.swift +++ b/ios/beartracks/beartracks-scout/ScoutingController.swift @@ -102,7 +102,7 @@ class ScoutingController: ObservableObject { let matchData = ScoutingDataExport( season: 2024, event: UserDefaults.standard.string(forKey: "eventCode") ?? "CAFR", match_num: matchNumber, level: "Qualification", team: Int(teamNumber) ?? 0, - game: encodedMatchTimes, defend: defense, driving: driving, overall: overall) + game: encodedMatchTimes, defend: defense, driving: driving, overall: overall + "\n\niOS v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "5")") do { let jsonData = try JSONEncoder().encode(matchData) var request = URLRequest(url: url) diff --git a/ios/beartracks/beartracks-scout/SettingsManager.swift b/ios/beartracks/beartracks-scout/SettingsManager.swift index 1abc00f0..41990d35 100644 --- a/ios/beartracks/beartracks-scout/SettingsManager.swift +++ b/ios/beartracks/beartracks-scout/SettingsManager.swift @@ -15,7 +15,7 @@ class SettingsManager { "eventCode": "CAFR", "season": "2024", "darkMode": true, - "showLabels": false + "leftHand": false ] UserDefaults.standard.register(defaults: defaults) } diff --git a/ios/beartracks/beartracks-scout/SettingsView.swift b/ios/beartracks/beartracks-scout/SettingsView.swift index 50e22f1e..850340bb 100644 --- a/ios/beartracks/beartracks-scout/SettingsView.swift +++ b/ios/beartracks/beartracks-scout/SettingsView.swift @@ -12,7 +12,7 @@ struct SettingsView: View { UserDefaults.standard.string(forKey: "eventCode") ?? "" @State private var seasonInput: String = UserDefaults.standard.string(forKey: "season") ?? "" @State private var darkMode: Bool = UserDefaults.standard.bool(forKey: "darkMode") - @State private var showLabels: Bool = UserDefaults.standard.bool(forKey: "showLabels") + @State private var leftHand: Bool = UserDefaults.standard.bool(forKey: "leftHand") @State private var showAlert = false @State private var settingsOptions: [DataMetadata] = [] @State private var showConfirm = false @@ -69,9 +69,9 @@ struct SettingsView: View { UserDefaults.standard.set(darkMode, forKey: "darkMode") showAlert = true } - Toggle("Persist Area Labels", isOn: $showLabels) - .onChange(of: showLabels) { _ in - UserDefaults.standard.set(showLabels, forKey: "showLabels") + Toggle("Left-Handed Labels", isOn: $leftHand) + .onChange(of: leftHand) { _ in + UserDefaults.standard.set(leftHand, forKey: "leftHand") } } Section { diff --git a/ios/beartracks/beartracks-scout/StartView.swift b/ios/beartracks/beartracks-scout/StartView.swift index 996b2474..193280d6 100644 --- a/ios/beartracks/beartracks-scout/StartView.swift +++ b/ios/beartracks/beartracks-scout/StartView.swift @@ -65,18 +65,20 @@ struct StartView: View { Stepper { Text("Preloaded notes handled (\(controller.switches.5))") } onIncrement: { - UIImpactFeedbackGenerator(style: .medium).impactOccurred() - controller.switches.6 += 1 - controller.switches.5 += 1 + if controller.switches.5 == 0 { + UIImpactFeedbackGenerator(style: .medium).impactOccurred() + controller.switches.6 += 1 + controller.switches.5 += 1 + } } onDecrement: { - if controller.switches.5 > 0 { + if controller.switches.5 == 1 { UIImpactFeedbackGenerator(style: .medium).impactOccurred() controller.switches.5 -= 1 scoresDecrement() } } Stepper { - Text("Alliance wing notes handled (\(controller.switches.4))") + Text("Wing notes handled (\(controller.switches.4))") } onIncrement: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() controller.switches.6 += 1 @@ -89,7 +91,7 @@ struct StartView: View { } } Stepper { - Text("Neutral zone notes handled (\(controller.switches.3))") + Text("Neutral notes handled (\(controller.switches.3))") } onIncrement: { UIImpactFeedbackGenerator(style: .medium).impactOccurred() controller.switches.6 += 1 diff --git a/src/db_main.rs b/src/db_main.rs index 1b54375d..e65d3ba0 100644 --- a/src/db_main.rs +++ b/src/db_main.rs @@ -263,6 +263,22 @@ pub async fn get_team_numbers(pool: &Pool, season: String) -> Result, E .map_err(error::ErrorInternalServerError) } +pub async fn get_team_numbers_by_event(pool: &Pool, season: String, event: String) -> Result, Error> { + // clone pool + let pool = pool.clone(); + + // get database connection + let conn = web::block(move || pool.get()).await?.map_err(error::ErrorInternalServerError)?; + + // run query function based on provided enum + web::block(move || { + let mut stmt = conn.prepare("SELECT team FROM main WHERE season=?1 AND event=?2 ORDER BY id DESC;")?; + stmt.query_map([season, event], |row| Ok(row.get(0)?)).and_then(Iterator::collect) + }) + .await? + .map_err(error::ErrorInternalServerError) +} + // incoming data structure for a new main form submission #[derive(Deserialize)] pub struct MainInsert { diff --git a/src/game_api.rs b/src/game_api.rs index 00a8ac81..0fd8af64 100644 --- a/src/game_api.rs +++ b/src/game_api.rs @@ -80,10 +80,10 @@ pub async fn get_owned_cards(pool: &db_auth::Pool, user: db_auth::User) -> Resul ) .await?; Ok(ClientInfo { - id: -1, - username: "none".to_string(), - team: -1, - score: -1, + id: user.id, + username: user.username, + team: user.team, + score: user.score, game_data: GameUserData { cards: vec![99999, 99998, 99997], hand: vec![99999, 99998, 99997], @@ -129,10 +129,10 @@ pub async fn get_owned_cards_by_user(pool: &db_auth::Pool, user: String) -> Resu ) .await?; Ok(ClientInfo { - id: -1, - username: "none".to_string(), - team: -1, - score: -1, + id: user_updated.id, + username: user_updated.username, + team: user_updated.team, + score: user_updated.score, game_data: GameUserData { cards: vec![99999, 99998, 99997], hand: vec![99999, 99998, 99997], @@ -160,13 +160,13 @@ pub async fn get_owned_cards_by_user(pool: &db_auth::Pool, user: String) -> Resu } } -pub async fn open_loot_box(auth_pool: &db_auth::Pool, main_pool: &db_main::Pool, user_param: db_auth::User) -> Result { +pub async fn open_loot_box(auth_pool: &db_auth::Pool, main_pool: &db_main::Pool, user_param: db_auth::User, event: String) -> Result { let user_queried = db_auth::get_user_id(auth_pool, user_param.id.to_string()).await; if !user_queried.is_ok() { return Ok(-1); } let user = user_queried.unwrap(); - let teams = db_main::get_team_numbers(main_pool, "2024".to_string()).await; + let teams = db_main::get_team_numbers_by_event(main_pool, "2024".to_string(), event).await; if teams.is_ok() { let team_list = teams.unwrap(); if team_list.is_empty() { diff --git a/src/main.rs b/src/main.rs index 95d47600..86a38a31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -526,7 +526,7 @@ async fn misc_get_whoami(user: db_auth::User) -> Result { } // if you aren't D6MFYYVHA8 you may want to change this -const APPLE_APP_SITE_ASSOC: &str = "{\"webcredentials\":{\"apps\":[\"D6MFYYVHA8.com.jayagra.beartracks\",\"D6MFYYVHA8.com.jayagra.beartracks-scout\",\"D6MFYYVHA8.com.jayagra.beartracks-manage\",\"D6MFYYVHA8.com.jayagra.beartracks.watchkitapp\"]}}"; +const APPLE_APP_SITE_ASSOC: &str = "{\"webcredentials\":{\"apps\":[\"D6MFYYVHA8.com.jayagra.beartracks\",\"D6MFYYVHA8.com.jayagra.beartracks-scout\",\"D6MFYYVHA8.com.jayagra.beartracks-manage\",\"D6MFYYVHA8.com.jayagra.beartracks.watchkitapp\",\"D6MFYYVHA8.com.jayagra.lydiasmvp\"]}}"; async fn misc_apple_app_site_association() -> Result { Ok(HttpResponse::Ok().content_type(ContentType::json()).body(APPLE_APP_SITE_ASSOC)) } @@ -574,10 +574,10 @@ async fn game_get_cards_by_username(db: web::Data, req: HttpRequest) // get random team from scouted teams -async fn game_open_lootbox(db: web::Data, user: db_auth::User) -> Result { +async fn game_open_lootbox(req: HttpRequest, db: web::Data, user: db_auth::User) -> Result { Ok(HttpResponse::Ok() .insert_header(("Cache-Control", "no-cache")) - .json(game_api::open_loot_box(&db.auth, &db.main, user).await?)) + .json(game_api::open_loot_box(&db.auth, &db.main, user, req.match_info().get("event").unwrap().parse().unwrap()).await?)) } // set player's hand @@ -1006,7 +1006,7 @@ async fn main() -> io::Result<()> { .route(web::get().to(game_get_team)), ) .service( - web::resource("/api/v1/game/open_lootbox") + web::resource("/api/v1/game/open_lootbox/{event}") .route(web::get().to(game_open_lootbox)), ) // POST