From b7efad2eed0ac28a17784b2a81c9c20d57a1be3d Mon Sep 17 00:00:00 2001 From: "emre.kartal" Date: Mon, 10 Jun 2024 17:37:46 +0200 Subject: [PATCH] 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) + } + }