From b7efad2eed0ac28a17784b2a81c9c20d57a1be3d Mon Sep 17 00:00:00 2001 From: "emre.kartal" Date: Mon, 10 Jun 2024 17:37:46 +0200 Subject: [PATCH 1/4] get custom answers and add the creation of bet custom --- Apple | 1 + .../Components/ChoiceFinalAnswerCell.swift | 27 +++++---- .../AllIn/Components/DropDownAnswerMenu.swift | 58 +++++++------------ .../AllIn/Components/ParticipationModal.swift | 46 +++++---------- .../ViewModels/CreationBetViewModel.swift | 47 +++++++++++++-- .../AllIn/ViewModels/DetailsViewModel.swift | 36 +++++++----- .../AllIn/Views/CreationBetView.swift | 47 +++++++-------- .../AllInApp/AllIn/Views/DetailsView.swift | 6 +- .../Sources/Api/Factory/FactoryApiBet.swift | 2 +- Sources/Api/Sources/Api/UserApiManager.swift | 1 - .../Model/Sources/Model/AnswerDetail.swift | 10 +++- Sources/Model/Sources/Model/Bet.swift | 9 ++- Sources/Model/Sources/Model/CustomBet.swift | 17 ++++++ 13 files changed, 173 insertions(+), 134 deletions(-) create mode 160000 Apple diff --git a/Apple b/Apple new file mode 160000 index 0000000..5d1ea84 --- /dev/null +++ b/Apple @@ -0,0 +1 @@ +Subproject commit 5d1ea8492aa6d4be39036165d00016d6951ae91a diff --git a/Sources/AllInApp/AllIn/Components/ChoiceFinalAnswerCell.swift b/Sources/AllInApp/AllIn/Components/ChoiceFinalAnswerCell.swift index 5665ea7..58e2831 100644 --- a/Sources/AllInApp/AllIn/Components/ChoiceFinalAnswerCell.swift +++ b/Sources/AllInApp/AllIn/Components/ChoiceFinalAnswerCell.swift @@ -14,23 +14,30 @@ struct ChoiceFinalAnswerCell: View { var selected = false var answer: AnswerDetail var rawColor = AllInColors.blueAccentColor - var body: some View { - ZStack{ - HStack{ + ZStack { + HStack { Spacer() Text(answer.response) - .textStyle(weight: .bold, color: selected ? .white :rawColor, size: 40).padding(.vertical, 10) + .textStyle(weight: .bold, color: selected ? .white : rawColor, size: 40) + .padding(.vertical, 10) Spacer() } - HStack{ + HStack { Spacer() - OddCapsule(backgroundColor: selected ? .white : AllInColors.purpleAccentColor, foregroundColor: selected ? AllInColors.purpleAccentColor : .white ,odd: answer.odds ).padding(.trailing,20).scaleEffect(0.9) + OddCapsule( + backgroundColor: selected ? .white : AllInColors.purpleAccentColor, + foregroundColor: selected ? AllInColors.purpleAccentColor : .white, + odd: answer.odds + ) + .padding(.trailing, 20) + .scaleEffect(0.9) } - }.background(selected ? AllInColors.purpleAccentColor : .white).cornerRadius(17).scaleEffect(selected ? 1.02 : 1).animation( - .easeInOut(duration: 0.3), - value: selected - ) + } + .background(selected ? AllInColors.purpleAccentColor : .white) + .cornerRadius(17) + .scaleEffect(selected ? 1.02 : 1) + .animation(.easeInOut(duration: 0.3), value: selected) } } diff --git a/Sources/AllInApp/AllIn/Components/DropDownAnswerMenu.swift b/Sources/AllInApp/AllIn/Components/DropDownAnswerMenu.swift index 7825627..f0c39a7 100644 --- a/Sources/AllInApp/AllIn/Components/DropDownAnswerMenu.swift +++ b/Sources/AllInApp/AllIn/Components/DropDownAnswerMenu.swift @@ -1,12 +1,3 @@ -// -// DropDownAnswerMenu.swift -// AllIn -// -// Created by Lucas Delanier on 16/01/2024. -// - -import SwiftUI - // // DropDownMenu.swift // AllIn @@ -15,20 +6,21 @@ import SwiftUI // import SwiftUI +import Model struct DropDownAnswerMenu: View { @State var expand = false - @Binding var selectedOption: Int - var options: [(Int, String, Float)] + @Binding var selectedAnswer: AnswerDetail + var answers: [AnswerDetail] var body: some View { VStack(spacing: 0, content: { - Button(action: { self.expand.toggle() }) { + Button(action: { withTransaction(Transaction(animation: nil)) { self.expand.toggle() } }) { HStack{ - Text(options[selectedOption].1.description) + Text(selectedAnswer.response) .textStyle(weight: .bold, color: AllInColors.blueAccentColor, size: 20) - Text(options[selectedOption].2.description) + Text(selectedAnswer.odds.description) .textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 10) Spacer() @@ -43,19 +35,21 @@ struct DropDownAnswerMenu: View { .foregroundColor(AllInColors.delimiterGrey) .padding(.bottom, 18) VStack(spacing: 0) { - ForEach(0.. Void)? + @Binding var selectedAnswer: AnswerDetail + @Binding var mise: String + var phrase: String + var answers: [AnswerDetail] + var participationAddedCallback: () -> Void + var checkAndSetError: () -> Bool + var possibleGain: Int { - if let stake = Float(mise), let selectedOption = options.first(where: { $0.0 == self.selectedOption }) { - return Int(round(stake * selectedOption.2)) + if let stake = Float(mise) { + return Int(round(stake * selectedAnswer.odds)) } else { return 0 } } - init(answer: Binding, mise: Binding, description: String, participationAddedCallback: (() -> Void)? = nil) { - self._selectedOption = answer - self._mise = mise - self.description = description - self.participationAddedCallback = participationAddedCallback - } - - let options: [(Int, String, Float)] = [ - (0, "OUI", 1.2), - (1, "NON", 3.3), - ] - var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ @@ -56,12 +48,12 @@ struct ParticipationModal: View { } .padding(.leading, 15) VStack(alignment: .leading){ - Text(description) + Text(phrase) .font(.system(size: 13)) .foregroundColor(AllInColors.primaryTextColor) .fontWeight(.light) - DropDownAnswerMenu(selectedOption: $selectedOption, options: options) + DropDownAnswerMenu(selectedAnswer: $selectedAnswer, answers: answers) TextField("", text: $mise, prompt: Text("generic_stake") .foregroundColor(AllInColors.lightGrey300Color) @@ -104,7 +96,7 @@ struct ParticipationModal: View { .padding(.top, 10) .padding(.bottom, 0) Button { - participationAddedCallback?() + participationAddedCallback() } label: { Text("Miser") .font(.system(size: 23)) @@ -125,16 +117,4 @@ struct ParticipationModal: View { .background(AllInColors.underComponentBackgroundColor) } } - - func checkAndSetError() -> Bool { - if let stake = Int(mise) { - if stake <= AppStateContainer.shared.user?.nbCoins ?? 0 && stake > 0 { - return false - } else { - return true - } - } else { - return true - } - } } diff --git a/Sources/AllInApp/AllIn/ViewModels/CreationBetViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/CreationBetViewModel.swift index cba9c44..568f759 100644 --- a/Sources/AllInApp/AllIn/ViewModels/CreationBetViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/CreationBetViewModel.swift @@ -23,19 +23,36 @@ class CreationBetViewModel: ObservableObject { @Published var endRegisterDate = Date() @Published var endBetDate = Date() @Published var betAdded = false - @Published var selectedOption = 0 + @Published var selectedTypeBet = 0 { + didSet { + values.removeAll() + groupedItems.removeAll() + response = "" + } + } + @Published var values: [String] = [] @Published var invited: Set = [] @Published var themeFieldError: String? @Published var descriptionFieldError: String? @Published var endRegisterDateFieldError: String? @Published var endBetDateFieldError: String? + @Published var responsesFieldError: String? @Published var errorMessage: String? @Published var showErrorMessage = false @Published var friends: [User] = [] + let options: [(Int, String, String)] = [ + (0, "questionMarkIcon", String(localized: "bet_type_binary")), + (1, "footballIcon", String(localized: "bet_type_match")), + (2, "paintbrushIcon", String(localized: "bet_type_custom")) + ] + + @Published var response = "" + @Published var groupedItems: [[String]] = [[String]] () + init() { getFriends() } @@ -50,14 +67,14 @@ class CreationBetViewModel: ObservableObject { func create() { - guard checkAndSetError(forTheme: true, forDescription: true, forEndRegisterDate: true, forEndBetDate: true) else { + guard checkAndSetError(forTheme: true, forDescription: true, forEndRegisterDate: true, forEndBetDate: true, forResponse: true) else { return } resetAllFieldErrors() if let user = AppStateContainer.shared.user { - manager.addBet(bet: toBet(theme: theme, description: description, endRegister: endRegisterDate, endBet: endBetDate, isPrivate: isPrivate, status: .inProgress, creator: user.username, invited: Array(invited), type: selectedOption)) { statusCode in + manager.addBet(bet: toBet(theme: theme, description: description, endRegister: endRegisterDate, endBet: endBetDate, isPrivate: isPrivate, status: .inProgress, creator: user.username, invited: Array(invited), type: selectedTypeBet)) { statusCode in print(statusCode) switch statusCode { case 201: @@ -72,12 +89,13 @@ class CreationBetViewModel: ObservableObject { } } - func checkAndSetError(forTheme checkTheme: Bool, forDescription checkDescription: Bool, forEndRegisterDate checkEndRegisterDate: Bool, forEndBetDate checkEndBetDate: Bool) -> Bool { + func checkAndSetError(forTheme checkTheme: Bool, forDescription checkDescription: Bool, forEndRegisterDate checkEndRegisterDate: Bool, forEndBetDate checkEndBetDate: Bool, forResponse checkResponse: Bool) -> Bool { var newThemeFieldError: String? var newDescriptionFieldError: String? var newEndRegisterDateFieldError: String? var newEndBetDateFieldError: String? + var newResponsesFieldError: String? var hasError = false @@ -105,6 +123,19 @@ class CreationBetViewModel: ObservableObject { hasError = true } + if checkResponse, selectedTypeBet == 2 { + if values.count < 2 { + newResponsesFieldError = "Il doit y'avoir 2 réponses minimum" + hasError = true + } else { + let uniqueValues = Set(values) + if uniqueValues.count != values.count { + newResponsesFieldError = "Les réponses doivent être uniques" + hasError = true + } + } + } + if !hasError { // No error return true @@ -115,6 +146,7 @@ class CreationBetViewModel: ObservableObject { descriptionFieldError = newDescriptionFieldError endRegisterDateFieldError = newEndRegisterDateFieldError endBetDateFieldError = newEndBetDateFieldError + responsesFieldError = newResponsesFieldError } return false } @@ -125,6 +157,7 @@ class CreationBetViewModel: ObservableObject { descriptionFieldError = nil endRegisterDateFieldError = nil endBetDateFieldError = nil + responsesFieldError = nil } } @@ -140,7 +173,11 @@ class CreationBetViewModel: ObservableObject { case 1: return MatchBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator, nameTeam1: "", nameTeam2: "") case 2: - return CustomBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator) + var bet = CustomBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator) + for answer in values { + bet.addCustomResponse(answer) + } + return bet default: return BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator) } diff --git a/Sources/AllInApp/AllIn/ViewModels/DetailsViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/DetailsViewModel.swift index ec1472b..913a165 100644 --- a/Sources/AllInApp/AllIn/ViewModels/DetailsViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/DetailsViewModel.swift @@ -15,9 +15,9 @@ class DetailsViewModel: ObservableObject { @Inject var manager: Manager var id: String - @Published var answer = 0 + @Published var selectedAnswer = AnswerDetail(response: "", totalStakes: 0, totalParticipants: 0, highestStake: 0, odds: 0) @Published var mise: String = "" - + @Published var betDetail: BetDetail? init(id: String) { @@ -28,28 +28,22 @@ class DetailsViewModel: ObservableObject { func getItem(withId id: String) { manager.getBet(withId: id) { bet in DispatchQueue.main.async { - for par in bet.participations { - print(par.id) - } self.betDetail = bet + if let firstAnswer = bet.answers.first { + self.selectedAnswer = firstAnswer + } } } } func addParticipate() { if let stake = Int(mise) { - var rep: String = "" - if answer == 0 { - rep = "Yes" - } else { - rep = "No" - } - manager.addParticipation(withId: id, withAnswer: rep, andStake: stake) { statusCode in + manager.addParticipation(withId: id, withAnswer: selectedAnswer.response, andStake: stake) { statusCode in switch statusCode { case 201: AppStateContainer.shared.user?.nbCoins -= stake WidgetCenter.shared.reloadAllTimelines() - + self.getItem(withId: self.id) default: break @@ -57,10 +51,20 @@ class DetailsViewModel: ObservableObject { } } mise = "" - answer = 0 + if let firstAnswer = betDetail!.answers.first { + self.selectedAnswer = firstAnswer + } } - func checkAndSetError() { - + func checkAndSetError() -> Bool { + if let stake = Int(mise) { + if stake <= AppStateContainer.shared.user?.nbCoins ?? 0 && stake > 0 { + return false + } else { + return true + } + } else { + return true + } } } diff --git a/Sources/AllInApp/AllIn/Views/CreationBetView.swift b/Sources/AllInApp/AllIn/Views/CreationBetView.swift index 4d033c5..d84a969 100644 --- a/Sources/AllInApp/AllIn/Views/CreationBetView.swift +++ b/Sources/AllInApp/AllIn/Views/CreationBetView.swift @@ -28,17 +28,6 @@ struct CreationBetView: View { }() let screenWidth = UIScreen.main.bounds.width - @State private var response = "" - @State private var values: [String] = [] - - let options: [(Int, String, String)] = [ - (0, "questionMarkIcon", String(localized: "bet_type_binary")), - (1, "footballIcon", String(localized: "bet_type_match")), - (2, "paintbrushIcon", String(localized: "bet_type_custom")) - ] - - @State var groupedItems: [[String]] = [[String]] () - private func updateGroupedItems() { var updatedGroupedItems: [[String]] = [[String]] () @@ -46,7 +35,7 @@ struct CreationBetView: View { var width: CGFloat = 0 var dynamicWidthLimit: CGFloat - for value in values { + for value in viewModel.values { let label = UILabel() label.text = value label.sizeToFit() @@ -65,7 +54,7 @@ struct CreationBetView: View { } updatedGroupedItems.append(tempItems) - groupedItems = updatedGroupedItems + viewModel.groupedItems = updatedGroupedItems } var body: some View { @@ -108,6 +97,7 @@ struct CreationBetView: View { .foregroundColor(AllInColors.lightGrey300Color) .font(.system(size: 14)) .fontWeight(.light)) + .autocorrectionDisabled(true) .padding() .background( RoundedRectangle(cornerRadius: 9) @@ -151,6 +141,7 @@ struct CreationBetView: View { .foregroundColor(AllInColors.lightGrey300Color) .font(.system(size: 14)) .fontWeight(.light), axis: .vertical) + .autocorrectionDisabled(true) .lineLimit(4, reservesSpace: true) .padding() .background( @@ -360,14 +351,14 @@ struct CreationBetView: View { VStack(spacing: 5) { VStack() { - DropDownMenu(selectedOption: $viewModel.selectedOption, options: options) + DropDownMenu(selectedOption: $viewModel.selectedTypeBet, options: viewModel.options) } .padding([.bottom], 15) .frame(width: 340) Group { - switch viewModel.selectedOption { + switch viewModel.selectedTypeBet { case 0: Text("bet_creation_yes_no_bottom_text_1") .textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13) @@ -385,12 +376,18 @@ struct CreationBetView: View { .textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13) .padding(.bottom, 15) + if let responseError = $viewModel.responsesFieldError.wrappedValue { + Text(responseError) + .textStyle(weight: .bold, color: .red, size: 10) + } + VStack(spacing: 5) { HStack(spacing: 0) { - TextField("", text: $response, prompt: Text("bet_creation_response_title") + TextField("", text: $viewModel.response, prompt: Text("bet_creation_response_title") .foregroundColor(AllInColors.lightGrey200Color) .font(.system(size: 16)) .fontWeight(.medium)) + .autocorrectionDisabled(true) .padding() .background( Rectangle() @@ -400,17 +397,17 @@ struct CreationBetView: View { ) .frame(width: 250, height: 38) .foregroundColor(.black) - .onChange(of: response) { newValue in + .onChange(of: viewModel.response) { newValue in if newValue.count > 20 { - response = String(newValue.prefix(20)) + viewModel.response = String(newValue.prefix(20)) } } Button(action: { - if !response.isEmpty && values.count < 5 { - values.append(response) + if !viewModel.response.isEmpty && viewModel.values.count < 5 { + viewModel.values.append(viewModel.response) updateGroupedItems() - response = "" + viewModel.response = "" } }) { Text("generic_add") @@ -423,12 +420,12 @@ struct CreationBetView: View { } HStack { Spacer() - Text(String(localized: "bet_creation_max_answers \(5 - values.count)")) + Text(String(localized: "bet_creation_max_answers \(5 - viewModel.values.count)")) .textStyle(weight: .regular, color: AllInColors.primaryTextColor, size: 12) } VStack(spacing: 10) { - ForEach(groupedItems, id: \.self) { items in + ForEach(viewModel.groupedItems, id: \.self) { items in HStack { ForEach(items, id: \.self) { text in HStack { @@ -436,8 +433,8 @@ struct CreationBetView: View { .foregroundColor(.white) .lineLimit(1) Button(action: { - if let index = values.firstIndex(of: text) { - values.remove(at: index) + if let index = viewModel.values.firstIndex(of: text) { + viewModel.values.remove(at: index) updateGroupedItems() } }) { diff --git a/Sources/AllInApp/AllIn/Views/DetailsView.swift b/Sources/AllInApp/AllIn/Views/DetailsView.swift index 09bcb2b..58a4e3c 100644 --- a/Sources/AllInApp/AllIn/Views/DetailsView.swift +++ b/Sources/AllInApp/AllIn/Views/DetailsView.swift @@ -122,7 +122,6 @@ struct DetailsView: View { .padding(.bottom, 10) ScrollView(showsIndicators: false) { ForEach(viewModel.betDetail?.participations ?? []) { participation in - // TODO UserInfo(username: participation.username, picture: nil , value: participation.stake).padding(.horizontal, 10) } } @@ -145,10 +144,11 @@ struct DetailsView: View { .padding(.horizontal, 10) } .sheet(isPresented: $isModalParticipated) { - ParticipationModal(answer: $viewModel.answer, mise: $viewModel.mise, description: viewModel.betDetail?.bet.phrase ?? "Not loaded", participationAddedCallback: { + ParticipationModal(selectedAnswer: $viewModel.selectedAnswer, mise: $viewModel.mise, phrase: viewModel.betDetail?.bet.phrase ?? "", answers: viewModel.betDetail?.answers ?? [], participationAddedCallback: { viewModel.addParticipate() isModalParticipated.toggle() - }) + }, + checkAndSetError: viewModel.checkAndSetError) .presentationDetents([.fraction(0.55)]) } .edgesIgnoringSafeArea(.bottom) diff --git a/Sources/Api/Sources/Api/Factory/FactoryApiBet.swift b/Sources/Api/Sources/Api/Factory/FactoryApiBet.swift index 67811b7..3a7019c 100644 --- a/Sources/Api/Sources/Api/Factory/FactoryApiBet.swift +++ b/Sources/Api/Sources/Api/Factory/FactoryApiBet.swift @@ -24,7 +24,7 @@ public class FactoryApiBet: FactoryBet { "endRegistration": formatZonedDateTime(dateTime: bet.endRegisterDate), "endBet": formatZonedDateTime(dateTime: bet.endBetDate), "isPrivate": String(bet.isPrivate), - "response": ["Yes","No"], + "response": bet.getResponses(), "userInvited": bet.invited, "type": betTypeString(fromType: String(describing: type(of: bet))) ] diff --git a/Sources/Api/Sources/Api/UserApiManager.swift b/Sources/Api/Sources/Api/UserApiManager.swift index 5669526..4e6a347 100644 --- a/Sources/Api/Sources/Api/UserApiManager.swift +++ b/Sources/Api/Sources/Api/UserApiManager.swift @@ -67,7 +67,6 @@ public struct UserApiManager: UserDataManager { print ("ALLIN : get bets won") do { if let httpResponse = response as? HTTPURLResponse, let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] { - print(jsonArray) for json in jsonArray { if let bet = FactoryApiBet().toBetResultDetail(from: json) { print(bet) diff --git a/Sources/Model/Sources/Model/AnswerDetail.swift b/Sources/Model/Sources/Model/AnswerDetail.swift index fb0baef..8df621e 100644 --- a/Sources/Model/Sources/Model/AnswerDetail.swift +++ b/Sources/Model/Sources/Model/AnswerDetail.swift @@ -8,7 +8,7 @@ import Foundation /// A class representing detailed information about a specific answer option for a bet. -public class AnswerDetail: ObservableObject, Identifiable, Codable { +public class AnswerDetail: ObservableObject, Codable, Equatable, Identifiable { /// The response or outcome associated with this answer. public private(set) var response: String @@ -41,4 +41,12 @@ public class AnswerDetail: ObservableObject, Identifiable, Codable { self.odds = odds } + public static func == (lhs: AnswerDetail, rhs: AnswerDetail) -> Bool { + return lhs.response == rhs.response && + lhs.totalStakes == rhs.totalStakes && + lhs.totalParticipants == rhs.totalParticipants && + lhs.highestStake == rhs.highestStake && + lhs.odds == rhs.odds + } + } diff --git a/Sources/Model/Sources/Model/Bet.swift b/Sources/Model/Sources/Model/Bet.swift index ba016a6..28369f3 100644 --- a/Sources/Model/Sources/Model/Bet.swift +++ b/Sources/Model/Sources/Model/Bet.swift @@ -8,7 +8,7 @@ import Foundation /// A class representing a betting entity, including details about the bet theme, participants, and deadlines. -public class Bet: ObservableObject, Identifiable, Codable { +public class Bet: ObservableObject, Codable { /// The id for the bet. public private(set) var id: String @@ -112,4 +112,11 @@ public class Bet: ObservableObject, Identifiable, Codable { self.author = author } + /// Function that returns an empty list of responses + /// + /// - Returns: An empty list of responses + public func getResponses() -> [String] { + return [] + } + } diff --git a/Sources/Model/Sources/Model/CustomBet.swift b/Sources/Model/Sources/Model/CustomBet.swift index e057643..9660ac6 100644 --- a/Sources/Model/Sources/Model/CustomBet.swift +++ b/Sources/Model/Sources/Model/CustomBet.swift @@ -10,4 +10,21 @@ import Foundation /// A subclass of Bet that represents a custom bet, allowing users to define their own parameters and rules. public class CustomBet: Bet { + /// List of custom responses + private var customResponses: [String] = [] + + /// Override the getResponses function to return custom responses + /// + /// - Returns: A list of custom responses + public override func getResponses() -> [String] { + return customResponses + } + + /// Add a custom response + /// + /// - Parameter response: The custom response to add + public func addCustomResponse(_ response: String) { + customResponses.append(response) + } + } From bb2a2baac39ead1bdff080091325ff108e38b191 Mon Sep 17 00:00:00 2001 From: ludelanier Date: Mon, 10 Jun 2024 21:30:12 +0200 Subject: [PATCH 2/4] make the review for the custom bets --- Apple | 1 - .../PinkFlame.imageset/Contents.json | 21 +++ .../PinkFlame.imageset/pinkFlame.png | Bin 0 -> 3622 bytes .../blueFlame.imageset/Contents.json | 21 +++ .../blueFlame.imageset/blueFlame.png | Bin 0 -> 3489 bytes .../AllIn/Components/BetLineLoading.swift | 148 ++------------- .../AllIn/Components/BinaryBetLine.swift | 168 ++++++++++++++++++ .../AllIn/Components/CustomBetLine.swift | 141 +++++++++++++++ .../AllInApp/AllIn/Ressources/Colors.swift | 6 + .../AllInApp/AllIn/Views/DetailsView.swift | 6 +- .../AllInApp.xcodeproj/project.pbxproj | 8 + .../Model/Sources/Model/AnswerDetail.swift | 9 +- 12 files changed, 390 insertions(+), 139 deletions(-) delete mode 160000 Apple create mode 100644 Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/Contents.json create mode 100644 Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/pinkFlame.png create mode 100644 Sources/AllInApp/AllIn/Assets.xcassets/blueFlame.imageset/Contents.json create mode 100644 Sources/AllInApp/AllIn/Assets.xcassets/blueFlame.imageset/blueFlame.png create mode 100644 Sources/AllInApp/AllIn/Components/BinaryBetLine.swift create mode 100644 Sources/AllInApp/AllIn/Components/CustomBetLine.swift diff --git a/Apple b/Apple deleted file mode 160000 index 5d1ea84..0000000 --- a/Apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5d1ea8492aa6d4be39036165d00016d6951ae91a diff --git a/Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/Contents.json b/Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/Contents.json new file mode 100644 index 0000000..f5dfe82 --- /dev/null +++ b/Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "pinkFlame.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/pinkFlame.png b/Sources/AllInApp/AllIn/Assets.xcassets/PinkFlame.imageset/pinkFlame.png new file mode 100644 index 0000000000000000000000000000000000000000..c6accb43e489757fc1c94493e8449baeeb50601c GIT binary patch literal 3622 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 P00000NkvXXu0mjfW4DiZ literal 0 HcmV?d00001 diff --git a/Sources/AllInApp/AllIn/Components/BetLineLoading.swift b/Sources/AllInApp/AllIn/Components/BetLineLoading.swift index bd3ff71..ec71365 100644 --- a/Sources/AllInApp/AllIn/Components/BetLineLoading.swift +++ b/Sources/AllInApp/AllIn/Components/BetLineLoading.swift @@ -11,12 +11,12 @@ import Model struct BetLineLoading: View { @State var showInfos: Bool = false - var participations: [Participation] + var bet: BetDetail var value: CGFloat { - let totalParticipations = participations.count - let numberOfYes = participations.filter { $0.answer.uppercased() == "YES" }.count - let numberOfNo = participations.filter { $0.answer.uppercased() == "NO" }.count + let totalParticipations = bet.participations.count + let numberOfYes = bet.participations.filter { $0.answer.uppercased() == "YES" }.count + let numberOfNo = bet.participations.filter { $0.answer.uppercased() == "NO" }.count if(numberOfNo == 0 && numberOfYes == 0){ return 0.5 } @@ -25,144 +25,22 @@ struct BetLineLoading: View { } var yesParticipations: [Participation] { - participations.filter { $0.answer.uppercased() == "YES" } + bet.participations.filter { $0.answer.uppercased() == "YES" } } var noParticipations: [Participation] { - participations.filter { $0.answer.uppercased() == "NO" } + bet.participations.filter { $0.answer.uppercased() == "NO" } } var body: some View { - VStack(alignment: .leading, spacing: 0) { - HStack(spacing: 5) { - Text("OUI") - .font(.system(size: 25)) - .fontWeight(.bold) - .foregroundColor(AllInColors.blue200) - Spacer() - Text("NON") - .font(.system(size: 25)) - .fontWeight(.bold) - .foregroundColor(AllInColors.pink100) + switch bet.bet { + case is BinaryBet: + BinaryBetLine(bet: bet) + case is CustomBet: + CustomBetLine(bet: bet) + default: + BinaryBetLine(bet: bet) } - - GeometryReader { geometry in - ZStack(alignment: .leading) { - HStack{ - Spacer() - Rectangle() - .frame(width: min(CGFloat(1-self.value) * geometry.size.width, geometry.size.width), height: 17) - .foregroundStyle(AllInColors.PinkBetGradiant).cornerRadius(999) - } - - HStack(spacing: 0) { - Rectangle() - .frame(width: min(CGFloat(self.value) * geometry.size.width, geometry.size.width), height: 17) - .foregroundStyle(AllInColors.BlueBetGradiant) - .cornerRadius(999) - Image("loadingHeartIcon") - .resizable() - .frame(width: 29, height: 32) - .padding(.leading, -10) - } - - } - .padding(.bottom, 5) - } - .frame(height: 40) - - HStack { - Spacer() - Text("bet_status_details_drawer") - .textStyle(weight: .medium, color: AllInColors.primaryTextColor, size: 10) - Image(showInfos ? "chevronUpIcon" : "chevronDownIcon") - .resizable() - .frame(width: 10, height: 7) - .scaledToFill() - } - .onTapGesture { - withAnimation { - showInfos.toggle() - } - } - .padding(.bottom, 5) - .padding(.trailing, 5) - - if showInfos { - VStack(spacing: 1) { - HStack(spacing: 5) { - Image("blueAllCoinIcon") - .resizable() - .frame(width:12, height: 12) - Text(yesParticipations.reduce(0, {x,y in x + y.stake}).description) - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.blue200) - Spacer() - Text(noParticipations.reduce(0, {x,y in x + y.stake}).description) - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.pink100) - Image("pinkAllCoinIcon") - .resizable() - .frame(width: 12, height: 12) - } - HStack(spacing: 5){ - Image("bluePersonIcon") - .resizable() - .frame(width: 12, height: 12) - Text(yesParticipations.count.description) - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.blue200) - Spacer() - Text(noParticipations.count.description) - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.pink100) - Image("pinkPersonIcon") - .resizable() - .frame(width: 12, height: 12) - - } - HStack(spacing: 5){ - Image("blueBadgeIcon") - .resizable() - .frame(width: 12, height: 12) - Text(yesParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0") - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.blue200) - Spacer() - Text(noParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0") - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.pink100) - Image("pinkBadgeIcon") - .resizable() - .frame(width: 12, height: 12) - - } - HStack(spacing: 5){ - Image("blueTrophyIcon") - .resizable() - .frame(width: 12, height: 12) - Text("1.2") - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.blue200) - Spacer() - Text("1.2") - .font(.system(size: 15)) - .fontWeight(.bold) - .foregroundColor(AllInColors.pink100) - Image("pinkTrophyIcon") - .resizable() - .frame(width:12, height: 12) - } - } - } - } } } diff --git a/Sources/AllInApp/AllIn/Components/BinaryBetLine.swift b/Sources/AllInApp/AllIn/Components/BinaryBetLine.swift new file mode 100644 index 0000000..68fb4ab --- /dev/null +++ b/Sources/AllInApp/AllIn/Components/BinaryBetLine.swift @@ -0,0 +1,168 @@ +// +// BinaryBetLine.swift +// AllIn +// +// Created by Lucas Delanier on 10/06/2024. +// + + +import SwiftUI +import Model + +struct BinaryBetLine: View { + + @State var showInfos: Bool = false + var bet: BetDetail + + var value: CGFloat { + let totalParticipations = bet.participations.count + let numberOfYes = bet.participations.filter { $0.answer.uppercased() == "YES" }.count + let numberOfNo = bet.participations.filter { $0.answer.uppercased() == "NO" }.count + if(numberOfNo == 0 && numberOfYes == 0){ + return 0.5 + } + + return totalParticipations > 0 ? CGFloat(numberOfYes) / CGFloat(totalParticipations) : 0.0 + } + + var yesParticipations: [Participation] { + bet.participations.filter { $0.answer.uppercased() == "YES" } + } + + var noParticipations: [Participation] { + bet.participations.filter { $0.answer.uppercased() == "NO" } + } + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 5) { + Text("OUI") + .font(.system(size: 25)) + .fontWeight(.bold) + .foregroundColor(AllInColors.blue200) + Spacer() + Text("NON") + .font(.system(size: 25)) + .fontWeight(.bold) + .foregroundColor(AllInColors.pink100) + } + + GeometryReader { geometry in + ZStack(alignment: .leading) { + HStack{ + Spacer() + Rectangle() + .frame(width: min(CGFloat(1-self.value) * geometry.size.width, geometry.size.width), height: 17) + .foregroundStyle(AllInColors.PinkBetGradiant).cornerRadius(999) + } + + HStack(spacing: 0) { + Rectangle() + .frame(width: min(CGFloat(self.value) * geometry.size.width, geometry.size.width), height: 17) + .foregroundStyle(AllInColors.BlueBetGradiant) + .cornerRadius(999) + Image("loadingHeartIcon") + .resizable() + .frame(width: 29, height: 32) + .padding(.leading, -10) + } + + } + .padding(.bottom, 5) + } + .frame(height: 40) + + HStack { + Spacer() + Text("bet_status_details_drawer") + .textStyle(weight: .medium, color: AllInColors.primaryTextColor, size: 10) + Image(showInfos ? "chevronUpIcon" : "chevronDownIcon") + .resizable() + .frame(width: 10, height: 7) + .scaledToFill() + } + .onTapGesture { + withAnimation { + showInfos.toggle() + } + } + .padding(.bottom, 5) + .padding(.trailing, 5) + + if showInfos { + VStack(spacing: 1) { + HStack(spacing: 5) { + Image("blueAllCoinIcon") + .resizable() + .frame(width:12, height: 12) + Text(yesParticipations.reduce(0, {x,y in x + y.stake}).description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.blue200) + Spacer() + Text(noParticipations.reduce(0, {x,y in x + y.stake}).description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.pink100) + Image("pinkAllCoinIcon") + .resizable() + .frame(width: 12, height: 12) + } + HStack(spacing: 5){ + Image("bluePersonIcon") + .resizable() + .frame(width: 12, height: 12) + Text(yesParticipations.count.description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.blue200) + Spacer() + Text(noParticipations.count.description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.pink100) + Image("pinkPersonIcon") + .resizable() + .frame(width: 12, height: 12) + + } + HStack(spacing: 5){ + Image("blueBadgeIcon") + .resizable() + .frame(width: 12, height: 12) + Text(yesParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.blue200) + Spacer() + Text(noParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.pink100) + Image("pinkBadgeIcon") + .resizable() + .frame(width: 12, height: 12) + + } + HStack(spacing: 5){ + Image("blueTrophyIcon") + .resizable() + .frame(width: 12, height: 12) + Text("1.2") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.blue200) + Spacer() + Text("1.2") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(AllInColors.pink100) + Image("pinkTrophyIcon") + .resizable() + .frame(width:12, height: 12) + } + } + } + } + } +} diff --git a/Sources/AllInApp/AllIn/Components/CustomBetLine.swift b/Sources/AllInApp/AllIn/Components/CustomBetLine.swift new file mode 100644 index 0000000..6035669 --- /dev/null +++ b/Sources/AllInApp/AllIn/Components/CustomBetLine.swift @@ -0,0 +1,141 @@ +// +// CustomBetLine.swift +// AllIn +// +// Created by Lucas Delanier on 10/06/2024. +// + +import SwiftUI +import Model + +struct CustomBetLine: View { + + @State var showInfos: [String: Bool] = [:] + var bet: BetDetail + + var participationsForAnswer: [String: [Participation]] { + Dictionary(grouping: bet.participations, by: { $0.answer.uppercased() }) + } + + var sortedAnswers: [AnswerDetail] { + bet.answers.sorted { $0.odds < $1.odds } + } + + func getTextStyle(for answer: AnswerDetail) -> Font.Weight { + return answer == sortedAnswers.first ? .bold : .light + } + + func getColor(for answer: AnswerDetail) -> Color { + return answer == sortedAnswers.first ? AllInColors.pinkAccentColor : AllInColors.blueAccentColor + } + + func getGradiant(for answer: AnswerDetail) -> LinearGradient { + return answer == sortedAnswers.first ? AllInColors.PinkBetGradiant : AllInColors.BlueBetLineGradiant + } + + func getFlameImage(for answer: AnswerDetail) -> String { + return answer == sortedAnswers.first ? "PinkFlame" : "BlueFlame" + } + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + ForEach(sortedAnswers, id: \.response) { answer in + let participations = participationsForAnswer[answer.response.uppercased()] ?? [] + let totalParticipations = bet.participations.count + let percentage = totalParticipations > 0 ? CGFloat(participations.count) / CGFloat(totalParticipations) : 0.0 + + VStack { + HStack { + Text(answer.response) + .font(.system(size: 15)) + .fontWeight(getTextStyle(for: answer)) + .foregroundColor(getColor(for: answer)) + Spacer() + } + + GeometryReader { geometry in + ZStack(alignment: .leading) { + HStack(spacing: 0) { + Rectangle() + .frame(width: min(percentage * geometry.size.width, geometry.size.width), height: 17) + .foregroundStyle(getGradiant(for: answer)) + .cornerRadius(999, corners: [.topLeft, .bottomLeft]) + Image(getFlameImage(for: answer)) + .resizable() + .frame(width: 29, height: 32) + .padding(.leading, -6) + Text("\(Int(percentage * 100))%") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundStyle(getColor(for: answer)) + .padding(.leading, 2) + } + } + .padding(.bottom, 5) + } + .frame(height: 40) + + HStack { + Spacer() + Text("bet_status_details_drawer") + .textStyle(weight: .medium, color: AllInColors.primaryTextColor, size: 10) + Image(showInfos[answer.response] ?? false ? "chevronUpIcon" : "chevronDownIcon") + .resizable() + .frame(width: 10, height: 7) + .scaledToFill() + } + .onTapGesture { + withAnimation { + showInfos[answer.response, default: false].toggle() + } + } + .padding(.trailing, 5) + + if showInfos[answer.response] ?? false { + HStack{ + VStack(alignment: .leading,spacing: 1) { + HStack(spacing: 5) { + Image(answer == sortedAnswers.first ? "pinkAllCoinIcon" : "blueAllCoinIcon") + .resizable() + .frame(width: 12, height: 12) + Text(participations.reduce(0, { x, y in x + y.stake }).description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(getColor(for: answer)) + } + HStack(spacing: 5) { + Image(answer == sortedAnswers.first ? "pinkPersonIcon" : "bluePersonIcon") + .resizable() + .frame(width: 12, height: 12) + Text(participations.count.description) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(getColor(for: answer)) + } + HStack(spacing: 5) { + Image(answer == sortedAnswers.first ? "pinkBadgeIcon" : "blueBadgeIcon") + .resizable() + .frame(width: 12, height: 12) + Text(participations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0") + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(getColor(for: answer)) + } + HStack(spacing: 5) { + Image(answer == sortedAnswers.first ? "pinkTrophyIcon" : "blueTrophyIcon") + .resizable() + .frame(width: 12, height: 12) + Text(String(format: "%.2f", answer.odds)) + .font(.system(size: 15)) + .fontWeight(.bold) + .foregroundColor(getColor(for: answer)) + } + } + Spacer() + } + } + } + } + } + } +} diff --git a/Sources/AllInApp/AllIn/Ressources/Colors.swift b/Sources/AllInApp/AllIn/Ressources/Colors.swift index 99506b3..b1801c5 100644 --- a/Sources/AllInApp/AllIn/Ressources/Colors.swift +++ b/Sources/AllInApp/AllIn/Ressources/Colors.swift @@ -77,6 +77,12 @@ struct AllInColors { endPoint: .trailing ) + static let BlueBetLineGradiant = LinearGradient( + gradient: Gradient(colors: [AllInColors.blue200, AllInColors.blueAccentColor]), + startPoint: .leading, + endPoint: .trailing + ) + static let PinkBetGradiant = LinearGradient( gradient: Gradient(colors: [AllInColors.pink100, AllInColors.pink200]), startPoint: .leading, diff --git a/Sources/AllInApp/AllIn/Views/DetailsView.swift b/Sources/AllInApp/AllIn/Views/DetailsView.swift index 58a4e3c..2d7f344 100644 --- a/Sources/AllInApp/AllIn/Views/DetailsView.swift +++ b/Sources/AllInApp/AllIn/Views/DetailsView.swift @@ -113,8 +113,10 @@ struct DetailsView: View { ResultBanner(finalAnswer: (viewModel.betDetail?.wonParticipation)!, odds: (viewModel.betDetail?.odds)!) } VStack(alignment: .leading, spacing: 5) { - BetLineLoading(participations: viewModel.betDetail?.participations ?? []) - .padding(.vertical,15) + if let bet = viewModel.betDetail{ + BetLineLoading(bet: bet).padding(.vertical, 20) + + } Text("bet_status_participants_list") .font(.system(size: 18)) .foregroundStyle(AllInColors.grey100Color) diff --git a/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj b/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj index 09d030d..aaa866b 100644 --- a/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj +++ b/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 120919182B56D0AE00D0FA29 /* ParticipationModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120919172B56D0AE00D0FA29 /* ParticipationModal.swift */; }; 1209191A2B56DC6C00D0FA29 /* DropDownAnswerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120919192B56DC6C00D0FA29 /* DropDownAnswerMenu.swift */; }; + 12228A212C1760D2008524F4 /* BinaryBetLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12228A202C1760D2008524F4 /* BinaryBetLine.swift */; }; + 12228A232C176117008524F4 /* CustomBetLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12228A222C176117008524F4 /* CustomBetLine.swift */; }; 123225D92B67B46100D30BB3 /* BetEndingValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123225D82B67B46100D30BB3 /* BetEndingValidationView.swift */; }; 123225DB2B67E41400D30BB3 /* ChoiceFinalAnswerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123225DA2B67E41400D30BB3 /* ChoiceFinalAnswerCell.swift */; }; 123590B42B51792000F7AEBD /* DetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123590B32B51792000F7AEBD /* DetailsView.swift */; }; @@ -149,6 +151,8 @@ 120919192B56DC6C00D0FA29 /* DropDownAnswerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownAnswerMenu.swift; sourceTree = ""; }; 122278B72B4BDE1100E632AA /* DependencyInjection.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DependencyInjection.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 122278B92B4BDE9500E632AA /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Model/Package.swift; sourceTree = ""; }; + 12228A202C1760D2008524F4 /* BinaryBetLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryBetLine.swift; sourceTree = ""; }; + 12228A222C176117008524F4 /* CustomBetLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBetLine.swift; sourceTree = ""; }; 123225D82B67B46100D30BB3 /* BetEndingValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetEndingValidationView.swift; sourceTree = ""; }; 123225DA2B67E41400D30BB3 /* ChoiceFinalAnswerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChoiceFinalAnswerCell.swift; sourceTree = ""; }; 123590B32B51792000F7AEBD /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = ""; }; @@ -444,6 +448,8 @@ 129D051C2B6E7FF0003D3E08 /* OddCapsule.swift */, 12A9E4932C07132600AB8677 /* EmptyInfo.swift */, 123F31DA2C0F26E8009B6D65 /* userPicture.swift */, + 12228A202C1760D2008524F4 /* BinaryBetLine.swift */, + 12228A222C176117008524F4 /* CustomBetLine.swift */, ); path = Components; sourceTree = ""; @@ -675,6 +681,7 @@ files = ( EC6B96CC2B24B7E500FC1C58 /* IAuthService.swift in Sources */, ECB26A172B4073F100FE06B3 /* CreationBetViewModel.swift in Sources */, + 12228A232C176117008524F4 /* CustomBetLine.swift in Sources */, EC3077092B24CF7F0060E34D /* Colors.swift in Sources */, EC6B969E2B24B4CC00FC1C58 /* ContentView.swift in Sources */, EC89F7BD2B250D66003821CE /* LoginView.swift in Sources */, @@ -721,6 +728,7 @@ EC01937E2B25C52E005D81E6 /* TopBar.swift in Sources */, ECA9D1CB2B2DA2320076E0EC /* DropDownFriends.swift in Sources */, 123590B82B5541BA00F7AEBD /* ParticipateButton.swift in Sources */, + 12228A212C1760D2008524F4 /* BinaryBetLine.swift in Sources */, EC01FCC32B56650400BB2390 /* DetailsViewModel.swift in Sources */, ECB26A1B2B40746C00FE06B3 /* FriendsViewModel.swift in Sources */, ECB7BC682B2F1ADF002A6654 /* LoginViewModel.swift in Sources */, diff --git a/Sources/Model/Sources/Model/AnswerDetail.swift b/Sources/Model/Sources/Model/AnswerDetail.swift index 8df621e..5b56cdd 100644 --- a/Sources/Model/Sources/Model/AnswerDetail.swift +++ b/Sources/Model/Sources/Model/AnswerDetail.swift @@ -8,7 +8,7 @@ import Foundation /// A class representing detailed information about a specific answer option for a bet. -public class AnswerDetail: ObservableObject, Codable, Equatable, Identifiable { +public class AnswerDetail: ObservableObject, Codable, Equatable, Identifiable, Hashable { /// The response or outcome associated with this answer. public private(set) var response: String @@ -49,4 +49,11 @@ public class AnswerDetail: ObservableObject, Codable, Equatable, Identifiable { lhs.odds == rhs.odds } + public func hash(into hasher: inout Hasher) { + hasher.combine(response) + hasher.combine(totalStakes) + hasher.combine(totalParticipants) + hasher.combine(highestStake) + hasher.combine(odds) + } } From 77e6790f59dfc9b6ed8b81bbbb1adf1183841f39 Mon Sep 17 00:00:00 2001 From: ludelanier Date: Mon, 10 Jun 2024 21:35:35 +0200 Subject: [PATCH 3/4] fix order --- Sources/AllInApp/AllIn/Components/CustomBetLine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AllInApp/AllIn/Components/CustomBetLine.swift b/Sources/AllInApp/AllIn/Components/CustomBetLine.swift index 6035669..e452c5e 100644 --- a/Sources/AllInApp/AllIn/Components/CustomBetLine.swift +++ b/Sources/AllInApp/AllIn/Components/CustomBetLine.swift @@ -18,7 +18,7 @@ struct CustomBetLine: View { } var sortedAnswers: [AnswerDetail] { - bet.answers.sorted { $0.odds < $1.odds } + bet.answers.sorted { $0.totalParticipants > $1.totalParticipants } } func getTextStyle(for answer: AnswerDetail) -> Font.Weight { From f6683880887dba47f098ba37eb837aa6285d8f3b Mon Sep 17 00:00:00 2001 From: ludelanier Date: Tue, 11 Jun 2024 10:14:19 +0200 Subject: [PATCH 4/4] fix error when loading --- .../Ressources/en.lproj/Localizable.strings | 5 + .../Ressources/fr.lproj/Localizable.strings | 5 + .../AllInApp/AllIn/Views/DetailsView.swift | 152 ++++++++++-------- 3 files changed, 94 insertions(+), 68 deletions(-) diff --git a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings index 09abd58..d95e2d9 100644 --- a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings @@ -187,3 +187,8 @@ "empty_bets_title"= "No bet matches your search"; "empty_friends_title" = "You don't have any friends yet"; "empty_friends_explain" = "Add them from this screen"; + +/// Error Messages + +"error_title" = "Error: Failed to Load Content."; +"error_message" = "The content you are trying to load could not be retrieved. Please check your internet connection and try again"; diff --git a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings index 9934153..e781668 100644 --- a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings @@ -186,3 +186,8 @@ "empty_bets_title"= "Aucun Bet ne correspond à votre recherche"; "empty_friends_title" = "Vous n’avez pas encore d’amis"; "empty_friends_explain" = "Ajoutez les depuis cet écran"; + +/// Error Messages + +"error_title" = "Erreur : Échec du chargement du contenu."; +"error_message" = "Le contenu que vous essayez de charger n'a pas pu être récupéré. Veuillez vérifier votre connexion internet et réessayer."; diff --git a/Sources/AllInApp/AllIn/Views/DetailsView.swift b/Sources/AllInApp/AllIn/Views/DetailsView.swift index 2d7f344..3472e83 100644 --- a/Sources/AllInApp/AllIn/Views/DetailsView.swift +++ b/Sources/AllInApp/AllIn/Views/DetailsView.swift @@ -57,89 +57,105 @@ struct DetailsView: View { } .padding(.horizontal, 15) .background(StatusValues.1) - - VStack(spacing: 0) { - VStack(alignment: .leading, spacing: 5) { - HStack(spacing: 3) { - Spacer() - Text("bet_proposed_by_format") - .font(.system(size: 10)) - .foregroundColor(AllInColors.grey800Color) - Text((viewModel.betDetail?.bet.author ?? "Unknown").capitalized) - .font(.system(size: 10)) - .fontWeight(.semibold) - .foregroundColor(AllInColors.primaryTextColor) - - } - Text(viewModel.betDetail?.bet.theme ?? "Not loaded") - .font(.system(size: 15)) - .foregroundColor(AllInColors.grey800Color) - Text(viewModel.betDetail?.bet.phrase ?? "Not loaded") - .font(.system(size: 20)) - .fontWeight(.bold) - .padding(.bottom, 10) - HStack { - HStack { + if viewModel.betDetail != nil{ + ScrollView { + VStack(alignment: .leading, spacing: 5) { + HStack(spacing: 3) { Spacer() - Text("bet_starting") - .font(.system(size: 15)) + Text("bet_proposed_by_format") + .font(.system(size: 10)) .foregroundColor(AllInColors.grey800Color) + Text((viewModel.betDetail?.bet.author ?? "Unknown").capitalized) + .font(.system(size: 10)) + .fontWeight(.semibold) + .foregroundColor(AllInColors.primaryTextColor) + } - .frame(width: 105) - .padding(.trailing, 10) - TextCapsule(date: viewModel.betDetail?.bet.endRegisterDate ?? Date()) - Spacer() - - }.padding(.bottom, 10) - HStack { + Text(viewModel.betDetail?.bet.theme ?? "Not loaded") + .font(.system(size: 15)) + .foregroundColor(AllInColors.grey800Color) + Text(viewModel.betDetail?.bet.phrase ?? "Not loaded") + .font(.system(size: 20)) + .fontWeight(.bold) + .padding(.bottom, 10) HStack { + HStack { + Spacer() + Text("bet_starting") + .font(.system(size: 15)) + .foregroundColor(AllInColors.grey800Color) + } + .frame(width: 105) + .padding(.trailing, 10) + TextCapsule(date: viewModel.betDetail?.bet.endRegisterDate ?? Date()) + Spacer() + + }.padding(.bottom, 10) + HStack { + HStack { + Spacer() + Text("bet_ends") + .font(.system(size: 15)) + .foregroundColor(AllInColors.grey800Color) + } + .frame(width: 105) + .padding(.trailing, 10) + TextCapsule(date: viewModel.betDetail?.bet.endBetDate ?? Date()) Spacer() - Text("bet_ends") - .font(.system(size: 15)) - .foregroundColor(AllInColors.grey800Color) } - .frame(width: 105) - .padding(.trailing, 10) - TextCapsule(date: viewModel.betDetail?.bet.endBetDate ?? Date()) - Spacer() } - } - .padding(.all, 15) - .padding(.vertical, 10) - .background(AllInColors.componentBackgroundColor) - .cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0) - - if isFinished { - ResultBanner(finalAnswer: (viewModel.betDetail?.wonParticipation)!, odds: (viewModel.betDetail?.odds)!) - } - VStack(alignment: .leading, spacing: 5) { - if let bet = viewModel.betDetail{ - BetLineLoading(bet: bet).padding(.vertical, 20) - + .padding(.all, 15) + .padding(.top, 6) + .border(width: 1, edges: [.bottom], color: AllInColors.delimiterGrey) + .background(AllInColors.componentBackgroundColor) + .cornerRadius(20, corners: [.topLeft,.topRight]) + + if isFinished { + ResultBanner(finalAnswer: (viewModel.betDetail?.wonParticipation)!, odds: (viewModel.betDetail?.odds)!) } - Text("bet_status_participants_list") - .font(.system(size: 18)) - .foregroundStyle(AllInColors.grey100Color) - .fontWeight(.bold) - .padding(.bottom, 10) - ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 0) { + if let bet = viewModel.betDetail{ + BetLineLoading(bet: bet).padding(.vertical, 20) + + } + Text("bet_status_participants_list") + .font(.system(size: 18)) + .foregroundStyle(AllInColors.grey100Color) + .fontWeight(.bold) + .padding(.bottom, 10) + ForEach(viewModel.betDetail?.participations ?? []) { participation in UserInfo(username: participation.username, picture: nil , value: participation.stake).padding(.horizontal, 10) } + .padding(.bottom) + + Spacer() } - .padding(.bottom, geometry.safeAreaInsets.bottom + 28) + .padding([.trailing,.leading], 15) - Spacer() } - .padding([.bottom,.trailing,.leading], 15) + .frame(maxWidth: .infinity, maxHeight: (geometry.size.height + geometry.safeAreaInsets.bottom) - 50) .background(AllInColors.underComponentBackgroundColor) - .border(width: 1, edges: [.top], color: AllInColors.delimiterGrey) - Spacer() - + .cornerRadius(15, corners: [.topLeft, .topRight]) + } + else{ + ScrollView { + HStack(alignment: .center){ + Spacer() + Image(systemName: "exclamationmark.triangle.fill").font(.system(size: 45)) + VStack(alignment:.leading){ + Text("error_title").font(.system(size: 10)) + Text("error_message").font(.system(size: 10)) + } + Spacer() + }.opacity(0.6) + .padding(.vertical, 20) + + } + .frame(maxWidth: .infinity, maxHeight: (geometry.size.height + geometry.safeAreaInsets.bottom) - 50) + .background(AllInColors.underComponentBackgroundColor) + .cornerRadius(15, corners: [.topLeft, .topRight]) } - .frame(maxWidth: .infinity, maxHeight: (geometry.size.height + geometry.safeAreaInsets.bottom) - 50) - .background(AllInColors.componentBackgroundColor) - .cornerRadius(15, corners: [.topLeft, .topRight]) ParticipateButton(isOpen: $isModalPresented, isParticapatedOpen: $isModalParticipated, bet: viewModel.betDetail?.bet) .padding(.bottom, geometry.safeAreaInsets.bottom + 5) @@ -150,7 +166,7 @@ struct DetailsView: View { viewModel.addParticipate() isModalParticipated.toggle() }, - checkAndSetError: viewModel.checkAndSetError) + checkAndSetError: viewModel.checkAndSetError) .presentationDetents([.fraction(0.55)]) } .edgesIgnoringSafeArea(.bottom)