From 80b5f9ba51a9faa2bae6024228c41d4f4e0d90bf Mon Sep 17 00:00:00 2001 From: "emre.kartal" <emre.kartal@etu.uca.fr> Date: Mon, 27 May 2024 15:16:33 +0200 Subject: [PATCH 1/3] Fix get bets --- Sources/AllInApp/AllIn/Views/ProfileView.swift | 4 +++- Sources/Api/Sources/Api/BetApiManager.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/AllInApp/AllIn/Views/ProfileView.swift b/Sources/AllInApp/AllIn/Views/ProfileView.swift index 6fb658f..48edfec 100644 --- a/Sources/AllInApp/AllIn/Views/ProfileView.swift +++ b/Sources/AllInApp/AllIn/Views/ProfileView.swift @@ -87,8 +87,10 @@ struct ProfileView: View { Spacer() Image(systemName: "chevron.right") + .resizable() + .frame(width: 8, height: 12) .foregroundColor(parameters[index].itemColor) - .padding(.trailing, 8) + .padding(.trailing, 14) } .padding(.vertical, 15) .background(parameters[index].backgroundColor) diff --git a/Sources/Api/Sources/Api/BetApiManager.swift b/Sources/Api/Sources/Api/BetApiManager.swift index e094e42..d0e1f0e 100644 --- a/Sources/Api/Sources/Api/BetApiManager.swift +++ b/Sources/Api/Sources/Api/BetApiManager.swift @@ -20,7 +20,7 @@ public struct BetApiManager: BetDataManager { let url = URL(string: allInApi + "bets/gets")! var request = URLRequest(url: url) - request.httpMethod = "GET" + request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") -- 2.36.3 From 431c95e84c6fb1929cb88feaec6e52bb3604adf3 Mon Sep 17 00:00:00 2001 From: ludelanier <lucas.delanier@etu.uca.fr> Date: Mon, 27 May 2024 16:46:50 +0200 Subject: [PATCH 2/3] nice --- .../AllInApp/AllIn/Components/BetCard.swift | 2 +- .../AllIn/Components/ChoiceCapsule.swift | 34 ++++++++++++++----- .../AllInApp/AllIn/Ressources/Config.swift | 2 +- .../Ressources/en.lproj/Localizable.strings | 1 + .../Ressources/fr.lproj/Localizable.strings | 1 + .../AllIn/ViewModels/BetViewModel.swift | 25 ++++++++++---- Sources/AllInApp/AllIn/Views/BetView.swift | 11 +++--- .../AllInApp/AllIn/Views/DetailsView.swift | 10 +++--- Sources/Api/Sources/Api/BetApiManager.swift | 14 ++++++-- Sources/Api/Sources/Api/UserApiManager.swift | 2 +- .../Model/Sources/Model/BetDataManager.swift | 2 +- Sources/Model/Sources/Model/Enums/File.swift | 16 +++++++++ Sources/Model/Sources/Model/Manager.swift | 4 +-- .../Sources/StubLib/BetStubManager.swift | 2 +- 14 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 Sources/Model/Sources/Model/Enums/File.swift diff --git a/Sources/AllInApp/AllIn/Components/BetCard.swift b/Sources/AllInApp/AllIn/Components/BetCard.swift index 2b7f637..2fb87a9 100644 --- a/Sources/AllInApp/AllIn/Components/BetCard.swift +++ b/Sources/AllInApp/AllIn/Components/BetCard.swift @@ -19,7 +19,7 @@ struct BetCard: View { VStack(alignment: .leading,spacing: 2){ HStack{ Spacer() - Text("bet_proposed_by_format \(bet.author.capitalized)") + Text("bet_proposed_by_format \(bet.author.capitalized)") .font(.system(size: 10)) .foregroundColor(AllInColors.grey800Color) diff --git a/Sources/AllInApp/AllIn/Components/ChoiceCapsule.swift b/Sources/AllInApp/AllIn/Components/ChoiceCapsule.swift index f540b1a..da3c1c9 100644 --- a/Sources/AllInApp/AllIn/Components/ChoiceCapsule.swift +++ b/Sources/AllInApp/AllIn/Components/ChoiceCapsule.swift @@ -6,22 +6,39 @@ // import SwiftUI +import Model struct ChoiceCapsule: View { - + let filter: BetFilter @State var pressed = false + @ObservedObject var viewModel: BetViewModel + + var label: String { + switch filter { + case .isPublic: + return String(localized: "bet_public") + case .isInvitation: + return String(localized: "bet_invitation") + case .inProgress: + return String(localized: "bet_current") + case .isFinished: + return String(localized: "bet_finished") + default: + return "NaN" + } + } var body: some View { Group { if(pressed) { - Text("bet_current") + Text(label) .textStyle(weight: .semibold, color: .white, size: 15) .padding([.leading,.trailing],13.8) .padding([.top,.bottom], 7) .background(AllInColors.lightPurpleColor) .clipShape(Capsule()) } else { - Text("bet_current") + Text(label) .textStyle(weight: .regular, color: AllInColors.grey800Color, size: 15) .padding([.leading,.trailing], 15) .padding([.top,.bottom], 7) @@ -34,14 +51,13 @@ struct ChoiceCapsule: View { } } .onTapGesture() { + if(!pressed) { + viewModel.filters.insert(filter) + } else { + viewModel.filters.remove(filter) + } pressed.toggle() } } } - -struct ChoiceCapsule_Previews: PreviewProvider { - static var previews: some View { - ChoiceCapsule() - } -} diff --git a/Sources/AllInApp/AllIn/Ressources/Config.swift b/Sources/AllInApp/AllIn/Ressources/Config.swift index d734b64..ab35db0 100644 --- a/Sources/AllInApp/AllIn/Ressources/Config.swift +++ b/Sources/AllInApp/AllIn/Ressources/Config.swift @@ -8,5 +8,5 @@ import Foundation struct Config { - static let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/" + static let allInApi = "http://localhost:8080/" } diff --git a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings index bdfcf8f..9939b51 100644 --- a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings @@ -132,6 +132,7 @@ "bet_ended" = "Ended on"; "bet_participate" = "Participate"; "bet_proposed_by_format %@" = "Proposed by %@"; +"bet_proposed_by_format" = "Proposed by"; "bet_players_waiting_format %@" = "%@ joueurs en attente"; "bet_players_format" = "players"; "bet_points_at_stake_format" = "points at stake"; diff --git a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings index f3e9b2d..4a22e95 100644 --- a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings @@ -132,6 +132,7 @@ "bet_ended" = "A pris fin le"; "bet_participate" = "Participer"; "bet_proposed_by_format %@" = "Proposé par %@"; +"bet_proposed_by_format" = "Proposé par"; "bet_players_waiting_format %@" = "%@ players waiting"; "bet_players_format" = "joueurs"; "bet_points_at_stake_format" = "points en jeu"; diff --git a/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift index 3f7e383..6b939b9 100644 --- a/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift @@ -17,13 +17,27 @@ class BetViewModel: ObservableObject { @Published private(set) var bets: [Bet] = [] @Published var betsOver: [BetDetail] = [] @Published var showingSheet: Bool = false - - init() { - getItems() - } + @Published var filters: Set<BetFilter> = [] { + didSet { + getItems() + } + } + + private var cancellables = Set<AnyCancellable>() + + init() { + getItems() + + // Observer for changes in filters + $filters + .sink { [weak self] _ in + self?.getItems() + } + .store(in: &cancellables) + } func getItems() { - manager.getBets(withIndex: 0, withCount: 20) { bets in + manager.getBets(withIndex: 0, withCount: 20, filters: Array(filters)) { bets in self.bets = bets } manager.getBetsOver() { bets in @@ -31,7 +45,6 @@ class BetViewModel: ObservableObject { if !self.betsOver.isEmpty { self.showingSheet = true } - print(bets) } } } diff --git a/Sources/AllInApp/AllIn/Views/BetView.swift b/Sources/AllInApp/AllIn/Views/BetView.swift index 2878d7a..80e8630 100644 --- a/Sources/AllInApp/AllIn/Views/BetView.swift +++ b/Sources/AllInApp/AllIn/Views/BetView.swift @@ -35,13 +35,10 @@ struct BetView: View { AllInColors.fadeInGradiantCard ScrollView(.horizontal,showsIndicators: false){ HStack{ - ChoiceCapsule() - ChoiceCapsule() - ChoiceCapsule() - ChoiceCapsule() - ChoiceCapsule() - ChoiceCapsule() - ChoiceCapsule() + ChoiceCapsule(filter: .isPublic, viewModel: viewModel) + ChoiceCapsule(filter: .isInvitation, viewModel: viewModel) + ChoiceCapsule(filter: .inProgress, viewModel: viewModel) + ChoiceCapsule(filter: .isFinished, viewModel: viewModel) } .padding(.leading,25) .padding([.top,.bottom],15) diff --git a/Sources/AllInApp/AllIn/Views/DetailsView.swift b/Sources/AllInApp/AllIn/Views/DetailsView.swift index f354f52..f32cbdd 100644 --- a/Sources/AllInApp/AllIn/Views/DetailsView.swift +++ b/Sources/AllInApp/AllIn/Views/DetailsView.swift @@ -16,16 +16,16 @@ struct DetailsView: View { if let betType = viewModel.betDetail?.bet.status { switch betType { case .inProgress: - return ("bet_status_in_progress", AllInColors.darkPurpleColor) + return (String(localized: "bet_status_in_progress"), AllInColors.darkPurpleColor) case .waiting, .closing: - return ("bet_status_waiting", AllInColors.pink100) + return (String(localized: "bet_status_waiting"), AllInColors.pink100) case .finished: - return ("bet_status_finished", AllInColors.grey100Color) + return (String(localized: "bet_status_finished"), AllInColors.grey100Color) case .cancelled: - return ("bet_status_cancelled", AllInColors.grey100Color) + return (String(localized: "bet_status_cancelled"), AllInColors.grey100Color) } } else { - return ("bet_status_unavailable", AllInColors.pink100) + return (String(localized: "bet_status_unavailable"), AllInColors.pink100) } } diff --git a/Sources/Api/Sources/Api/BetApiManager.swift b/Sources/Api/Sources/Api/BetApiManager.swift index d0e1f0e..ec7f5ee 100644 --- a/Sources/Api/Sources/Api/BetApiManager.swift +++ b/Sources/Api/Sources/Api/BetApiManager.swift @@ -16,7 +16,7 @@ public struct BetApiManager: BetDataManager { self.token = token } - public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { + public func getBets(withIndex index: Int, withCount count: Int, filters: [BetFilter] = [], completion: @escaping ([Bet]) -> Void) { let url = URL(string: allInApi + "bets/gets")! var request = URLRequest(url: url) @@ -24,13 +24,22 @@ public struct BetApiManager: BetDataManager { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - var bets: [Bet] = [] + let filterStrings = filters.map { $0.rawValue } + let jsonDictionary: [String: Any] = ["filters": filterStrings] + do { + let jsonData = try JSONSerialization.data(withJSONObject: jsonDictionary, options: []) + request.httpBody = jsonData + } catch { + print("Error creating JSON data: \(error)") + return + } URLSession.shared.dataTask(with: request) { data, response, error in if let data = data { print ("ALLIN : get bets") do { if let httpResponse = response as? HTTPURLResponse, let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] { + var bets: [Bet] = [] for json in jsonArray { if let bet = FactoryApiBet().toBet(from: json) { bets.append(bet) @@ -45,6 +54,7 @@ public struct BetApiManager: BetDataManager { } }.resume() } + public func getUsers(username: String) -> [User] { return [] diff --git a/Sources/Api/Sources/Api/UserApiManager.swift b/Sources/Api/Sources/Api/UserApiManager.swift index 267bf16..ef89393 100644 --- a/Sources/Api/Sources/Api/UserApiManager.swift +++ b/Sources/Api/Sources/Api/UserApiManager.swift @@ -8,7 +8,7 @@ import Foundation import Model -let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/" +let allInApi = "http://localhost:8080/" public struct UserApiManager: UserDataManager { diff --git a/Sources/Model/Sources/Model/BetDataManager.swift b/Sources/Model/Sources/Model/BetDataManager.swift index 6fede4a..200ccf4 100644 --- a/Sources/Model/Sources/Model/BetDataManager.swift +++ b/Sources/Model/Sources/Model/BetDataManager.swift @@ -8,7 +8,7 @@ import Foundation public protocol BetDataManager { - func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) + func getBets(withIndex index: Int, withCount count: Int, filters: [BetFilter], completion: @escaping ([Bet]) -> Void) func getUsers(username: String) -> [User] func getBet(withId id: String, completion: @escaping (BetDetail) -> Void) } diff --git a/Sources/Model/Sources/Model/Enums/File.swift b/Sources/Model/Sources/Model/Enums/File.swift new file mode 100644 index 0000000..f5b22f5 --- /dev/null +++ b/Sources/Model/Sources/Model/Enums/File.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by Lucas Delanier on 27/05/2024. +// + +import Foundation + +public enum BetFilter: String, Codable { + case inProgress = "IN_PROGRESS" + case isPublic = "PUBLIC" + case isInvitation = "INVITATION" + case isFinished = "FINISHED" +} + diff --git a/Sources/Model/Sources/Model/Manager.swift b/Sources/Model/Sources/Model/Manager.swift index 65f59aa..afed8f9 100644 --- a/Sources/Model/Sources/Model/Manager.swift +++ b/Sources/Model/Sources/Model/Manager.swift @@ -22,8 +22,8 @@ public struct Manager { } } - public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { - betDataManager.getBets(withIndex: index, withCount: count) { bets in + public func getBets(withIndex index: Int, withCount count: Int, filters: [BetFilter], completion: @escaping ([Bet]) -> Void) { + betDataManager.getBets(withIndex: index, withCount: count, filters: filters) { bets in completion(bets) } } diff --git a/Sources/StubLib/Sources/StubLib/BetStubManager.swift b/Sources/StubLib/Sources/StubLib/BetStubManager.swift index fa9e5bc..acd1460 100644 --- a/Sources/StubLib/Sources/StubLib/BetStubManager.swift +++ b/Sources/StubLib/Sources/StubLib/BetStubManager.swift @@ -12,7 +12,7 @@ public struct BetStubManager: BetDataManager { public init() {} - public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { + public func getBets(withIndex index: Int, withCount count: Int, filters: [BetFilter] = [], completion: @escaping ([Bet]) -> Void) { completion(Stub.shared.bets) } -- 2.36.3 From e7d89089703b2d815e38220d0a40228bdae613dc Mon Sep 17 00:00:00 2001 From: "emre.kartal" <emre.kartal@etu.uca.fr> Date: Wed, 29 May 2024 09:32:21 +0200 Subject: [PATCH 3/3] Adding friends and displaying the ranking --- .../AllIn/Components/AllcoinsCounter.swift | 6 +- .../AllInApp/AllIn/Components/Friend.swift | 45 ++-- .../AllIn/Components/ReviewCard.swift | 13 +- .../AllInApp/AllIn/Ressources/Config.swift | 2 +- .../Ressources/en.lproj/Localizable.strings | 3 +- .../Ressources/fr.lproj/Localizable.strings | 1 + .../AllIn/ViewModels/BetViewModel.swift | 12 +- .../ViewModels/CurrentBetViewModel.swift | 5 +- .../AllIn/ViewModels/FriendsViewModel.swift | 60 ++++- .../AllIn/ViewModels/RankingViewModel.swift | 22 +- .../AllInApp/AllIn/Views/FriendsView.swift | 42 +++- .../AllInApp/AllIn/Views/RankingView.swift | 208 +++++++++--------- Sources/Api/Sources/Api/UserApiManager.swift | 104 ++++++++- .../Enums/{File.swift => BetFilter.swift} | 2 +- .../Sources/Model/Enums/FriendStatus.swift | 14 ++ Sources/Model/Sources/Model/Manager.swift | 26 ++- Sources/Model/Sources/Model/MatchBet.swift | 1 + Sources/Model/Sources/Model/User.swift | 19 +- .../Model/Sources/Model/UserDataManager.swift | 7 +- 19 files changed, 412 insertions(+), 180 deletions(-) rename Sources/Model/Sources/Model/Enums/{File.swift => BetFilter.swift} (92%) create mode 100644 Sources/Model/Sources/Model/Enums/FriendStatus.swift diff --git a/Sources/AllInApp/AllIn/Components/AllcoinsCounter.swift b/Sources/AllInApp/AllIn/Components/AllcoinsCounter.swift index efd2e6b..6fd256f 100644 --- a/Sources/AllInApp/AllIn/Components/AllcoinsCounter.swift +++ b/Sources/AllInApp/AllIn/Components/AllcoinsCounter.swift @@ -15,13 +15,13 @@ struct AllcoinsCounter: View { var body: some View { HStack(alignment: .center) { - Image("allcoinIcon") - .resizable() - .frame(width: 17, height: 17, alignment: .leading) Text(String(appStateContainer.user?.nbCoins ?? 0)) .contentTransition(.numericText()) .fontWeight(.black) .foregroundColor(foregroundColor) + Image("allcoinIcon") + .resizable() + .frame(width: 17, height: 17, alignment: .leading) } .frame(width: 90, height: 40) .background(backgroundColor) diff --git a/Sources/AllInApp/AllIn/Components/Friend.swift b/Sources/AllInApp/AllIn/Components/Friend.swift index 2cf3ca5..3744a79 100644 --- a/Sources/AllInApp/AllIn/Components/Friend.swift +++ b/Sources/AllInApp/AllIn/Components/Friend.swift @@ -6,38 +6,49 @@ // import SwiftUI +import Model struct Friend: View { - var image: String - var pseudo: String + var user: User + @ObservedObject var viewModel: FriendsViewModel + + var StatusValues: (String, Color, Color) { + switch user.friendStatus { + case .friend: + return (String(localized: "generic_delete"), AllInColors.grey400Color, AllInColors.componentBackgroundColor) + case .notFriend: + return (String(localized: "generic_add"), .white, AllInColors.lightPurpleColor) + case .requested: + return (String(localized: "friends_request_sent"), AllInColors.grey400Color, AllInColors.componentBackgroundColor) + default: + return ("NaN", AllInColors.grey400Color, AllInColors.componentBackgroundColor) + } + } var body: some View { HStack{ - AsyncImage(url: URL(string: image)) + AsyncImage(url: URL(string: "https://picsum.photos/536/354")) .frame(width: 50, height: 50) .cornerRadius(180) .scaledToFit() - Text(pseudo) + Text(user.username) .fontWeight(.medium) .padding(.leading, 5) .font(.system(size: 18)) .lineLimit(1) Spacer() - Button("generic_delete") {} - .frame(width: 90, height: 30) - .foregroundColor(AllInColors.grey400Color) - .font(.system(size: 14)) - .background(AllInColors.componentBackgroundColor) - .cornerRadius(5) - + Button(StatusValues.0) { + viewModel.toggleFriendStatus(for: user) + } + .minimumScaleFactor(0.3) + .lineLimit(2) + .frame(width: 90, height: 30) + .foregroundColor(StatusValues.1) + .font(.system(size: 14)) + .background(StatusValues.2) + .cornerRadius(5) } .padding([.trailing,.leading], 25) } } - -struct Friend_Previews: PreviewProvider { - static var previews: some View { - Friend(image: "https://picsum.photos/536/354", pseudo: "Lucas") - } -} diff --git a/Sources/AllInApp/AllIn/Components/ReviewCard.swift b/Sources/AllInApp/AllIn/Components/ReviewCard.swift index 300e782..d42d73f 100644 --- a/Sources/AllInApp/AllIn/Components/ReviewCard.swift +++ b/Sources/AllInApp/AllIn/Components/ReviewCard.swift @@ -23,8 +23,8 @@ struct ReviewCard: View { HStack{ Spacer() Text("bet_proposed_by_format \(betDetail.bet.author)") - .font(.system(size: 10)) - .foregroundColor(AllInColors.grey800Color) + .font(.system(size: 10)) + .foregroundColor(AllInColors.grey800Color) } Text(betDetail.bet.theme).font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) @@ -50,7 +50,8 @@ struct ReviewCard: View { .font(.system(size: 25)) .fontWeight(.bold) } - else{Text(amountBetted.description) + else{ + Text(amountBetted.description) .foregroundColor(.white) .font(.system(size: 25)) .fontWeight(.bold) @@ -63,7 +64,7 @@ struct ReviewCard: View { .fontWeight(.bold) } Spacer() - + } .frame(width: .infinity) .padding(.all,10) @@ -74,9 +75,9 @@ struct ReviewCard: View { .background( isAWin || betDetail.bet.endBetDate < Date() ? AnyView(AllInColors.primaryGradient) : - AnyView(Color.black) + AnyView(Color.black) ) .cornerRadius(20, corners: [.bottomLeft,.bottomRight]) - .border(width: 1, edges: [.top], color: AllInColors.delimiterGrey) + .border(width: 1, edges: [.top], color: AllInColors.delimiterGrey) } .onTapGesture { showDetails.toggle() diff --git a/Sources/AllInApp/AllIn/Ressources/Config.swift b/Sources/AllInApp/AllIn/Ressources/Config.swift index ab35db0..d734b64 100644 --- a/Sources/AllInApp/AllIn/Ressources/Config.swift +++ b/Sources/AllInApp/AllIn/Ressources/Config.swift @@ -8,5 +8,5 @@ import Foundation struct Config { - static let allInApi = "http://localhost:8080/" + static let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/" } diff --git a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings index 9939b51..1a3e605 100644 --- a/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/en.lproj/Localizable.strings @@ -131,7 +131,7 @@ "bet_ends" = "Ends on"; "bet_ended" = "Ended on"; "bet_participate" = "Participate"; -"bet_proposed_by_format %@" = "Proposed by %@"; +"bet_proposed_by_format %@" = "Proposed by %@"; "bet_proposed_by_format" = "Proposed by"; "bet_players_waiting_format %@" = "%@ joueurs en attente"; "bet_players_format" = "players"; @@ -166,6 +166,7 @@ /// Friends "friends_title" = "Friends"; +"friends_request_sent" = "Request sent"; /// Daily reward diff --git a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings index 4a22e95..c8c7cb8 100644 --- a/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings +++ b/Sources/AllInApp/AllIn/Ressources/fr.lproj/Localizable.strings @@ -166,6 +166,7 @@ /// Friends "friends_title" = "Amis"; +"friends_request_sent" = "Requête envoyée"; /// Daily reward diff --git a/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift index 6b939b9..5d994bd 100644 --- a/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/BetViewModel.swift @@ -38,12 +38,16 @@ class BetViewModel: ObservableObject { func getItems() { manager.getBets(withIndex: 0, withCount: 20, filters: Array(filters)) { bets in - self.bets = bets + DispatchQueue.main.async { + self.bets = bets + } } manager.getBetsOver() { bets in - self.betsOver = bets - if !self.betsOver.isEmpty { - self.showingSheet = true + DispatchQueue.main.async { + self.betsOver = bets + if !self.betsOver.isEmpty { + self.showingSheet = true + } } } } diff --git a/Sources/AllInApp/AllIn/ViewModels/CurrentBetViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/CurrentBetViewModel.swift index 22a44ff..23b7428 100644 --- a/Sources/AllInApp/AllIn/ViewModels/CurrentBetViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/CurrentBetViewModel.swift @@ -21,10 +21,7 @@ class CurrentBetViewModel: ObservableObject { func getItems() { manager.getCurrentBets(withIndex: 0, withCount: 20) { bets in - for bet in bets { - let betDetail = BetDetail(bet: bet, answers: [AnswerDetail(response: "OUI", totalStakes: 120, totalParticipants: 2, highestStake: 200, odds: 1.2), AnswerDetail(response: "NON", totalStakes: 120, totalParticipants: 2, highestStake: 200, odds: 1.2)], participations: []) - self.bets.append(betDetail) - } + self.bets = bets } } diff --git a/Sources/AllInApp/AllIn/ViewModels/FriendsViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/FriendsViewModel.swift index 294ab23..7e52413 100644 --- a/Sources/AllInApp/AllIn/ViewModels/FriendsViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/FriendsViewModel.swift @@ -13,23 +13,69 @@ class FriendsViewModel: ObservableObject { @Inject var manager: Manager + @Published private(set) var users: [User] = [] + @Published var text: String = "" { + didSet { + if text.isEmpty { + getItems() + } else { + search() + } + } + } + init() { getItems() } - func getItems ( ) { - + func getItems() { + manager.getFriends() { friends in + DispatchQueue.main.async { + self.users = friends + } + } } - func deleteItem(indexSet: IndexSet) { - + func search() { + guard text.allSatisfy({ $0.isLetter || $0.isNumber }) else { + return + } + + manager.getUsers(withName: text) { users in + self.users = users + } } - func moveltem(from: IndexSet, to: Int) { + func toggleFriendStatus(for user: User) { + guard let index = users.firstIndex(where: { $0.username == user.username }) else { return } + var updatedUser = users[index] + switch updatedUser.friendStatus { + case .friend: + updatedUser.friendStatus = .notFriend + deleteItem(username: user.username) + case .notFriend: + updatedUser.friendStatus = .requested + addItem(username: user.username) + case .requested: + updatedUser.friendStatus = .notFriend + deleteItem(username: user.username) + default: + break + } + + users[index] = updatedUser } - func addItem(title: String) { - + func deleteItem(username: String) { + manager.removeFriend(withUsername: username) { statusCode in + + } + } + + func addItem(username: String) { + manager.addFriend(withUsername: username) { statusCode in + + } } } diff --git a/Sources/AllInApp/AllIn/ViewModels/RankingViewModel.swift b/Sources/AllInApp/AllIn/ViewModels/RankingViewModel.swift index e321f91..7bd1879 100644 --- a/Sources/AllInApp/AllIn/ViewModels/RankingViewModel.swift +++ b/Sources/AllInApp/AllIn/ViewModels/RankingViewModel.swift @@ -13,23 +13,17 @@ class RankingViewModel: ObservableObject { @Inject var manager: Manager + @Published private(set) var friends: [User] = [] + init() { getItems() } - func getItems ( ) { - - } - - func deleteItem(indexSet: IndexSet) { - - } - - func moveltem(from: IndexSet, to: Int) { - - } - - func addItem(title: String) { - + func getItems() { + manager.getFriends() { users in + var friends = users + friends.append(AppStateContainer.shared.user!) + self.friends = friends.sorted(by: { $0.nbCoins > $1.nbCoins }) + } } } diff --git a/Sources/AllInApp/AllIn/Views/FriendsView.swift b/Sources/AllInApp/AllIn/Views/FriendsView.swift index 7b1ccc1..3166e5f 100644 --- a/Sources/AllInApp/AllIn/Views/FriendsView.swift +++ b/Sources/AllInApp/AllIn/Views/FriendsView.swift @@ -9,6 +9,7 @@ import SwiftUI struct FriendsView: View { + @StateObject private var viewModel = FriendsViewModel() @Binding var showMenu: Bool var body: some View { @@ -18,12 +19,37 @@ struct FriendsView: View { .textStyle(weight: .bold, color: AllInColors.grey500Color, size: 25) .padding([.top,.bottom],15) + HStack { + TextField("Search...", text: $viewModel.text) + .padding(7) + .padding(.horizontal, 25) + .background(Color(.systemGray6)) + .cornerRadius(8) + .overlay( + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.gray) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(.leading, 8) + + if !viewModel.text.isEmpty { + Button(action: { + self.viewModel.text = "" + }) { + Image(systemName: "multiply.circle.fill") + .foregroundColor(.gray) + .padding(.trailing, 8) + } + } + } + ) + .padding(.horizontal, 10) + } + ScrollView(showsIndicators: false){ - Friend(image: "https://picsum.photos/536/354", pseudo: "Lucas") - Friend(image: "https://picsum.photos/536/354", pseudo: "Arthur") - Friend(image: "https://picsum.photos/536/354", pseudo: "Lucase") - Friend(image: "https://picsum.photos/536/354", pseudo: "Rayhan") - + ForEach(viewModel.users, id: \.self) { friend in + Friend(user: friend, viewModel: viewModel) + } } .padding(.top, 25) Spacer() @@ -32,9 +58,3 @@ struct FriendsView: View { .background(AllInColors.backgroundColor) } } - -struct FriendsView_Previews: PreviewProvider { - static var previews: some View { - FriendsView(showMenu: .constant(false)) - } -} diff --git a/Sources/AllInApp/AllIn/Views/RankingView.swift b/Sources/AllInApp/AllIn/Views/RankingView.swift index f1e08be..ea58507 100644 --- a/Sources/AllInApp/AllIn/Views/RankingView.swift +++ b/Sources/AllInApp/AllIn/Views/RankingView.swift @@ -10,6 +10,7 @@ import SwiftUI struct RankingView: View { @Binding var showMenu: Bool + @StateObject var viewModel = RankingViewModel() var body: some View { GeometryReader { geometry in @@ -17,121 +18,126 @@ struct RankingView: View { TopBar(showMenu: self.$showMenu) Text("ranking_title") .textStyle(weight: .bold, color: AllInColors.grey500Color, size: 25) - .padding([.top,.bottom],15) + .padding([.top,.bottom], 15) - HStack { - ZStack { - VStack(spacing: 0){ - Spacer() - Text("Pseudo") - .fontWeight(.bold) - .padding(.bottom, 4) - .font(.system(size: 16)) - .lineLimit(1) - Divider() - .background(AllInColors.lightGrey100Color) - HStack{ - Spacer() - Image("allcoinIcon") + if !viewModel.friends.isEmpty { + HStack { + if viewModel.friends.indices.contains(0) { + ZStack { + VStack(spacing: 0) { + Spacer() + Text(viewModel.friends[0].username) + .fontWeight(.bold) + .padding(.bottom, 4) + .font(.system(size: 16)) + .lineLimit(1) + Divider() + .background(AllInColors.lightGrey100Color) + HStack { + Spacer() + Image("allcoinIcon") + .resizable() + .frame(width: 18, height: 18, alignment: .leading) + .padding([.top, .bottom], 10) + Text(String(viewModel.friends[0].nbCoins)) + .textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16) + .padding(.trailing, 18) + Spacer() + } + .frame(width: geometry.size.width * 0.43) + .background(AllInColors.underComponentBackgroundColor) + } + .frame(width: geometry.size.width * 0.43, height: 120) + .background(AllInColors.componentBackgroundColor) + .cornerRadius(41.5, corners: .topLeft) + .cornerRadius(8, corners: .topRight) + .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) + + Image("defaultUserImage") .resizable() - .frame(width: 18, height: 18, alignment: .leading) - .padding([.top,.bottom],10) - Text(String("570")) - .textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16) - .padding(.trailing, 18) - Spacer() + .frame(width: 70, height: 70) + .scaledToFit() + .cornerRadius(180) + .offset(x: 0, y: -55) + + Text("1") + .frame(width: 28, height: 28) + .foregroundColor(.white) + .background(AllInColors.lightPurpleColor) + .cornerRadius(30) + .font(.system(size: 18)) + .fontWeight(.bold) + .offset(x: 0, y: -23) } - .frame(width: geometry.size.width * 0.43) - .background(AllInColors.underComponentBackgroundColor) } - .frame(width: geometry.size.width * 0.43, height: 120) - .background(AllInColors.componentBackgroundColor) - .cornerRadius(41.5, corners: .topLeft) - .cornerRadius(8, corners: .topRight) - .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) - - Image("defaultUserImage") - .resizable() - .frame(width: 70, height: 70) - .scaledToFit() - .cornerRadius(180) - .offset(x: 0, y: -55) - - Text("1") - .frame(width: 28, height: 28) - .foregroundColor(.white) - .background(AllInColors.lightPurpleColor) - .cornerRadius(30) - .font(.system(size: 18)) - .fontWeight(.bold) - .offset(x: 0, y: -23) - } - ZStack { - VStack(spacing: 0){ - Spacer() - Text("Pseudo") - .fontWeight(.bold) - .padding(.bottom, 4) - .font(.system(size: 15)) - .lineLimit(1) - Divider() - .background(AllInColors.lightGrey100Color) - HStack{ - Spacer() - Image("allcoinIcon") + if viewModel.friends.indices.contains(1) { + ZStack { + VStack(spacing: 0) { + Spacer() + Text(viewModel.friends[1].username) + .fontWeight(.bold) + .padding(.bottom, 4) + .font(.system(size: 15)) + .lineLimit(1) + Divider() + .background(AllInColors.lightGrey100Color) + HStack { + Spacer() + Image("allcoinIcon") + .resizable() + .frame(width: 18, height: 18, alignment: .leading) + .padding([.top, .bottom], 10) + Text(String(viewModel.friends[1].nbCoins)) + .textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16) + .padding(.trailing, 18) + Spacer() + } + .frame(width: geometry.size.width * 0.43) + .background(AllInColors.underComponentBackgroundColor) + } + .frame(width: geometry.size.width * 0.43, height: 90) + .background(AllInColors.componentBackgroundColor) + .cornerRadius(27.5, corners: .topRight) + .cornerRadius(8, corners: .topLeft) + .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) + + Image("defaultUserImage") .resizable() - .frame(width: 18, height: 18, alignment: .leading) - .padding([.top,.bottom],10) - Text(String("570")) - .textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16) - .padding(.trailing, 18) - Spacer() + .frame(width: 50, height: 50) + .scaledToFit() + .cornerRadius(180) + .offset(x: 0, y: -50) + + Text("2") + .frame(width: 20, height: 20) + .foregroundColor(.white) + .background(AllInColors.lightPurpleColor) + .cornerRadius(30) + .font(.system(size: 14)) + .fontWeight(.bold) + .offset(x: 0, y: -28) } - .frame(width: geometry.size.width * 0.43) - .background(AllInColors.underComponentBackgroundColor) + .padding(.top, 28) } - .frame(width: geometry.size.width * 0.43, height: 90) - .background(AllInColors.componentBackgroundColor) - .cornerRadius(27.5, corners: .topRight) - .cornerRadius(8, corners: .topLeft) - .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) - - Image("defaultUserImage") - .resizable() - .frame(width: 60, height: 60) - .scaledToFit() - .cornerRadius(180) - .offset(x: 0, y: -50) - - Text("2") - .frame(width: 23, height: 23) - .foregroundColor(.white) - .background(AllInColors.lightPurpleColor) - .cornerRadius(30) - .font(.system(size: 15)) - .fontWeight(.bold) - .offset(x: 0, y: -22) } - .padding(.top, 28) - + .padding([.leading, .trailing, .top], 20) } - .padding([.leading,.trailing,.top],20) - ScrollView(showsIndicators: false){ - VStack(spacing: 10) { - RankingRow(number: 3, image: "defaultUserImage", pseudo: "Lucas", allCoins: 541) - RankingRow(number: 4, image: "defaultUserImage", pseudo: "Arthur", allCoins: 542) + ScrollView(showsIndicators: false) { + ForEach(viewModel.friends.indices.dropFirst(2), id: \.self) { index in + let friend = viewModel.friends[index] + RankingRow( + number: index + 1, + image: "defaultUserImage", + pseudo: friend.username, + allCoins: friend.nbCoins + ) } - }.padding(.top, 10) + } + .padding(.top, 10) Spacer() } .edgesIgnoringSafeArea(.bottom).background(AllInColors.backgroundColor) } } } - -struct RankingView_Previews: PreviewProvider { - static var previews: some View { - RankingView(showMenu: .constant(false)) - } -} diff --git a/Sources/Api/Sources/Api/UserApiManager.swift b/Sources/Api/Sources/Api/UserApiManager.swift index ef89393..a956ead 100644 --- a/Sources/Api/Sources/Api/UserApiManager.swift +++ b/Sources/Api/Sources/Api/UserApiManager.swift @@ -8,7 +8,7 @@ import Foundation import Model -let allInApi = "http://localhost:8080/" +let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/" public struct UserApiManager: UserDataManager { @@ -73,8 +73,100 @@ public struct UserApiManager: UserDataManager { } } - public func getFriends() -> [User] { - fatalError("Not implemented yet") + public func addFriend(username: String, completion : @escaping (Int)-> ()) { + + let url = URL(string: allInApi + "friends/add")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + let json: [String: Any] = ["username": username] + + if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []){ + URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in + print ("ALLIN : Add friend") + if let httpResponse = response as? HTTPURLResponse { + print(httpResponse.statusCode) + completion(httpResponse.statusCode) + } + }.resume() + } + } + + public func removeFriend(username: String, completion : @escaping (Int)-> ()) { + + let url = URL(string: allInApi + "friends/delete")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + let json: [String: Any] = ["username": username] + + if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []){ + URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in + print ("ALLIN : Remove friend") + if let httpResponse = response as? HTTPURLResponse { + print(httpResponse.statusCode) + completion(httpResponse.statusCode) + } + }.resume() + } + } + + public func getFriends(completion: @escaping ([User]) -> Void) { + let url = URL(string: allInApi + "friends/gets")! + + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + var users: [User] = [] + + URLSession.shared.dataTask(with: request) { data, response, error in + if let data = data { + print ("ALLIN : get friends") + do { + if let httpResponse = response as? HTTPURLResponse, let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] { + print(jsonArray) + users = try JSONDecoder().decode([User].self, from: JSONSerialization.data(withJSONObject: jsonArray)) + print(httpResponse.statusCode) + completion(users) + } + } catch { + print("Error parsing JSON: \(error)") + } + } + }.resume() + } + + public func getUsers(withName name: String, completion: @escaping ([User]) -> Void) { + let url = URL(string: allInApi + "friends/search/" + name)! + + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + var users: [User] = [] + + URLSession.shared.dataTask(with: request) { data, response, error in + if let data = data { + print ("ALLIN : get friends by search") + do { + if let httpResponse = response as? HTTPURLResponse, let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] { + print(jsonArray) + users = try JSONDecoder().decode([User].self, from: JSONSerialization.data(withJSONObject: jsonArray)) + print(httpResponse.statusCode) + completion(users) + } + } catch { + print("Error parsing JSON: \(error)") + } + } + }.resume() } public func getGifts(completion : @escaping (Int, Int)-> ()) { @@ -106,7 +198,7 @@ public struct UserApiManager: UserDataManager { fatalError("Not implemented yet") } - public func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { + public func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([BetDetail]) -> Void) { let url = URL(string: allInApi + "bets/current")! var request = URLRequest(url: url) @@ -114,7 +206,7 @@ public struct UserApiManager: UserDataManager { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - var bets: [Bet] = [] + var bets: [BetDetail] = [] URLSession.shared.dataTask(with: request) { data, response, error in if let data = data { @@ -123,7 +215,7 @@ public struct UserApiManager: UserDataManager { if let httpResponse = response as? HTTPURLResponse, let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] { for json in jsonArray { print(json) - if let bet = FactoryApiBet().toBet(from: json) { + if let bet = FactoryApiBet().toBetDetail(from: json) { bets.append(bet) } } diff --git a/Sources/Model/Sources/Model/Enums/File.swift b/Sources/Model/Sources/Model/Enums/BetFilter.swift similarity index 92% rename from Sources/Model/Sources/Model/Enums/File.swift rename to Sources/Model/Sources/Model/Enums/BetFilter.swift index f5b22f5..4891632 100644 --- a/Sources/Model/Sources/Model/Enums/File.swift +++ b/Sources/Model/Sources/Model/Enums/BetFilter.swift @@ -1,5 +1,5 @@ // -// File.swift +// BetFilter.swift // // // Created by Lucas Delanier on 27/05/2024. diff --git a/Sources/Model/Sources/Model/Enums/FriendStatus.swift b/Sources/Model/Sources/Model/Enums/FriendStatus.swift new file mode 100644 index 0000000..b190d90 --- /dev/null +++ b/Sources/Model/Sources/Model/Enums/FriendStatus.swift @@ -0,0 +1,14 @@ +// +// FriendStatus.swift +// +// +// Created by Emre on 28/05/2024. +// + +import Foundation + +public enum FriendStatus: String, Codable { + case friend = "FRIEND" + case requested = "REQUESTED" + case notFriend = "NOT_FRIEND" +} diff --git a/Sources/Model/Sources/Model/Manager.swift b/Sources/Model/Sources/Model/Manager.swift index afed8f9..33dc10b 100644 --- a/Sources/Model/Sources/Model/Manager.swift +++ b/Sources/Model/Sources/Model/Manager.swift @@ -21,6 +21,18 @@ public struct Manager { completion(status) } } + + public func addFriend(withUsername username: String, completion : @escaping (Int)-> ()) { + userDataManager.addFriend(username: username) { status in + completion(status) + } + } + + public func removeFriend(withUsername username: String, completion : @escaping (Int)-> ()) { + userDataManager.removeFriend(username: username) { status in + completion(status) + } + } public func getBets(withIndex index: Int, withCount count: Int, filters: [BetFilter], completion: @escaping ([Bet]) -> Void) { betDataManager.getBets(withIndex: index, withCount: count, filters: filters) { bets in @@ -40,13 +52,25 @@ public struct Manager { } } + public func getFriends(completion: @escaping ([User]) -> Void) { + userDataManager.getFriends() { users in + completion(users) + } + } + + public func getUsers(withName name: String, completion: @escaping ([User]) -> Void) { + userDataManager.getUsers(withName: name) { users in + completion(users) + } + } + public func getHistoricBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { userDataManager.getOldBets(withIndex: index, withCount: count) { bets in completion(bets) } } - public func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) { + public func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([BetDetail]) -> Void) { userDataManager.getCurrentBets(withIndex: index, withCount: count) { bets in completion(bets) } diff --git a/Sources/Model/Sources/Model/MatchBet.swift b/Sources/Model/Sources/Model/MatchBet.swift index 8bea44a..d171562 100644 --- a/Sources/Model/Sources/Model/MatchBet.swift +++ b/Sources/Model/Sources/Model/MatchBet.swift @@ -9,6 +9,7 @@ import Foundation /// A subclass of Bet that represents a bet on a match between two teams. public class MatchBet: Bet { + /// The name of the first team involved in the match. public var nameTeam1: String diff --git a/Sources/Model/Sources/Model/User.swift b/Sources/Model/Sources/Model/User.swift index 9bb5e79..6dd0b5d 100644 --- a/Sources/Model/Sources/Model/User.swift +++ b/Sources/Model/Sources/Model/User.swift @@ -8,7 +8,7 @@ import Foundation /// A struct representing a user with details such as username, email, number of coins, and friends. -public struct User: Codable { +public struct User: Codable, Hashable { /// The username of the user. public private(set) var username: String @@ -19,6 +19,9 @@ public struct User: Codable { /// The number of coins associated with the user. public var nbCoins: Int + /// The friendship status with the main user. + public var friendStatus: FriendStatus? + /// Custom constructor to initialize a User instance. /// /// - Parameters: @@ -30,4 +33,18 @@ public struct User: Codable { self.email = email self.nbCoins = nbCoins } + + /// Custom constructor to initialize a User instance. + /// + /// - Parameters: + /// - username: The username of the user. + /// - email: The email address of the user. + /// - nbCoins: The number of coins associated with the user. + /// - status: The friendship status with the main user. + public init(username: String, email: String, nbCoins: Int, status: FriendStatus) { + self.username = username + self.email = email + self.nbCoins = nbCoins + self.friendStatus = status + } } diff --git a/Sources/Model/Sources/Model/UserDataManager.swift b/Sources/Model/Sources/Model/UserDataManager.swift index 2c974a9..4b76f5f 100644 --- a/Sources/Model/Sources/Model/UserDataManager.swift +++ b/Sources/Model/Sources/Model/UserDataManager.swift @@ -11,10 +11,13 @@ public protocol UserDataManager { func getBets(withIndex index: Int, withCount count: Int) -> [Bet] func getBetsOver(completion: @escaping ([BetDetail]) -> Void) func addBet(bet: Bet, completion : @escaping (Int)-> ()) - func getFriends() -> [User] + func addFriend(username: String, completion : @escaping (Int)-> ()) + func removeFriend(username: String, completion : @escaping (Int)-> ()) + func getFriends(completion: @escaping ([User]) -> Void) + func getUsers(withName name: String, completion: @escaping ([User]) -> Void) func getGifts(completion : @escaping (Int, Int)-> ()) func getOldBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) - func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) + func getCurrentBets(withIndex index: Int, withCount count: Int, completion: @escaping ([BetDetail]) -> Void) func addParticipation(withId id: String, withAnswer answer: String, andStake stake: Int, completion : @escaping (Int)-> ()) func addResponse(withIdBet id: String, andResponse responseBet: String) } -- 2.36.3