Merge with feature/display_bet_details 🔀

view/details-page
Emre KARTAL 1 year ago
commit 5b0c2248a4

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

@ -1,3 +1,55 @@
# Swift <div align="center">
Client IOS <img src="Documentation/Images/Banner-AllIn.png" />
---
&nbsp; ![Swift](https://img.shields.io/badge/Swift-F05138.svg?style=for-the-badge&logo=Swift&logoColor=white)
&nbsp; ![iOS](https://img.shields.io/badge/iOS-000000.svg?style=for-the-badge&logo=Apple&logoColor=white)
---
[Présentation](#apple---all-in) | [Répartition du dépôt](#répartition-du-gitlab) | [Structures](#structures) | [Technologies](#technologies) | [Outils](#outils) | [Wiki](https://codefirst.iut.uca.fr/git/AllDev/Gestion_de_projet/wiki)
</div>
### Apple - ALL IN!
**Contexte** : Application Swift et SwiftUI pour le projet universitaire de troisième année (B.U.T Informatique de Clermont-Ferrand) intitulé *All In*.
</br>
**Description** : Ce dépôt contient l'ensemble du code pour la partie client iOS de l'application *ALL IN*.
</br>
# Répartition du GitLab
[**Sources**](Sources) : **Code de l'application**
[**Documentation**](Documentation) : **Documentation de l'application**
# Structures
- MVVM
# Technologies
<img src="" />
Pour réaliser l'interface visuelle, nous avons opté pour **SwiftUI** du fait qu'elle permet de réaliser des interfaces utilisateurs complexes de manière élégante. Le framework est récent, mis à jour régulièrement, et facile à prendre en main pour le développement.
# Outils
Pour la partie API, nous avons utilisé plusieurs outils :
- UserDefaults
Pour stocker le token localement, nous utilisons l'outil fourni par SwiftUI qui est UserDefaults, afin de réaliser une authentification automatique lorsque le client ouvre l'application, en récupérant son token lors de la précédente connexion.
<div align="center">
© AllDev - Apple
</div>

@ -2,16 +2,13 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Api"> location = "group:StubLib">
</FileRef>
<FileRef
location = "group:AllInApp/AllInApp.xcodeproj">
</FileRef> </FileRef>
<FileRef <FileRef
location = "group:StubLib"> location = "group:Api">
</FileRef> </FileRef>
<FileRef <FileRef
location = "group:ViewModel"> location = "group:AllInApp/AllInApp.xcodeproj">
</FileRef> </FileRef>
<FileRef <FileRef
location = "group:DependencyInjection/DependencyInjection.xcodeproj"> location = "group:DependencyInjection/DependencyInjection.xcodeproj">

@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
import DependencyInjection import DependencyInjection
import Model import Model
import ViewModel
@main @main
struct AllInApp: App { struct AllInApp: App {

@ -8,8 +8,8 @@
import SwiftUI import SwiftUI
struct AllcoinsCounter: View { struct AllcoinsCounter: View {
var backgroundColor: Color = AllInColors.whiteColor var backgroundColor: Color = .white
var foreGroundColor: Color = AllInColors.primaryColor var foregroundColor: Color = AllInColors.primaryColor
var body: some View { var body: some View {
HStack(alignment: .center) { HStack(alignment: .center) {
Image("allcoinIcon") Image("allcoinIcon")
@ -17,7 +17,7 @@ struct AllcoinsCounter: View {
.frame(width: 17, height: 17, alignment: .leading) .frame(width: 17, height: 17, alignment: .leading)
Text(String(AppStateContainer.shared.user?.nbCoins ?? 0)) Text(String(AppStateContainer.shared.user?.nbCoins ?? 0))
.fontWeight(.black) .fontWeight(.black)
.foregroundColor(foreGroundColor) .foregroundColor(foregroundColor)
} }
.frame(width: 90, height: 40) .frame(width: 90, height: 40)
.background(backgroundColor) .background(backgroundColor)

@ -6,26 +6,34 @@
// //
import SwiftUI import SwiftUI
import Model
struct BetCard: View { struct BetCard: View {
var bet: Bet
@State var showDetails: Bool = false @State var showDetails: Bool = false
@State var showParticipate: Bool = false @State var showParticipate: Bool = false
var body: some View { var body: some View {
VStack(spacing: 0){ VStack(spacing: 0){
VStack(alignment: .leading,spacing: 2){ VStack(alignment: .leading,spacing: 2){
HStack{ HStack{
Spacer() Spacer()
Text("proposé par Lucas").font(.system(size: 10)).foregroundColor(AllInColors.grey800Color) Text("proposé par " + bet.author.username.capitalized)
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
} }
Text("Etudes").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text(bet.theme)
Text("Emre va réussir son TP de CI/CD mercredi?").font(.system(size: 20)).fontWeight(.bold) .font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
Text(bet.phrase)
.font(.system(size: 20))
.fontWeight(.bold)
HStack{ HStack{
Text("Commence le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text("Commence le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
TextCapsule() TextCapsule(date: bet.endRegisterDate)
TextCapsule()
Spacer() Spacer()
} }
} }
.frame(width: .infinity) .frame(width: .infinity)
@ -36,33 +44,41 @@ struct BetCard: View {
HStack{ HStack{
Spacer() Spacer()
UsersPreview() UsersPreview()
Text(" 4 joueurs en attente").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color).fontWeight(.medium) Text(String(bet.registered.count) + " joueurs en attente")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
.fontWeight(.medium)
Spacer() Spacer()
}.padding(0) }.padding(0)
ParticipateButton(isOpen: $showParticipate).padding(.top, 5) ParticipateButton(isOpen: $showParticipate).padding(.top, 5)
} }
.frame(width: .infinity) .frame(width: .infinity)
.padding(.all,8) .padding(.all,8)
.background(AllInColors.underComponentBackgroundColor) .background(AllInColors.underComponentBackgroundColor)
.cornerRadius(20, corners: [.bottomLeft,.bottomRight]) .cornerRadius(20, corners: [.bottomLeft,.bottomRight])
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey) .border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
}.onTapGesture { }
.onTapGesture {
showDetails.toggle() showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails)
} }
.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, id: bet.id)
}
} }
} }
struct BetCard_Previews: PreviewProvider { struct BetCard_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
BetCard() BetCard(bet: BinaryBet(theme: "Football - Finale de la Ligue des Champions",
phrase: "Le gagnant de la finale sera l'équipe avec le plus de tirs au but.",
endRegisterDate: Date().addingTimeInterval(86400),
endBetDate: Date().addingTimeInterval(172800),
totalStakes: 100,
isPublic: true,
invited: [],
author: User(username: "Imri", email: "emre.kartal@etu.uca.fr", nbCoins: 75, friends: []),
registered: []))
.preferredColorScheme(.dark) .preferredColorScheme(.dark)
} }
} }

@ -25,7 +25,7 @@ struct ParticipationModal: View {
HStack{ HStack{
Text("Faites vos paris").font(.system(size: 18)).foregroundColor(AllInColors.blackTitleColor).fontWeight(.semibold) Text("Faites vos paris").font(.system(size: 18)).foregroundColor(AllInColors.blackTitleColor).fontWeight(.semibold)
Spacer() Spacer()
AllcoinsCounter(backgroundColor: AllInColors.purpleAccentColor, foreGroundColor: AllInColors.whiteColor) AllcoinsCounter(backgroundColor: AllInColors.purpleAccentColor, foregroundColor: AllInColors.whiteColor)
} }
.padding(.leading, 15) .padding(.leading, 15)
VStack(alignment: .leading){ VStack(alignment: .leading){

@ -32,8 +32,7 @@ struct RecapBetCard: View {
Text("Fini le ") Text("Fini le ")
.font(.system(size: 15)) .font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color) .foregroundColor(AllInColors.grey800Color)
TextCapsule() TextCapsule(date: Date())
TextCapsule()
Spacer() Spacer()
} }
} }
@ -108,7 +107,7 @@ struct RecapBetCard: View {
.onTapGesture { .onTapGesture {
showDetails.toggle() showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) { }.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails) DetailsView(isModalPresented: $showDetails, id: "1")
} }
.gesture( .gesture(
LongPressGesture(minimumDuration: 0.5) LongPressGesture(minimumDuration: 0.5)

@ -24,8 +24,7 @@ struct ReviewCard: View {
Text("Emre va réussir son TP de CI/CD mercredi?").font(.system(size: 20)).fontWeight(.bold) Text("Emre va réussir son TP de CI/CD mercredi?").font(.system(size: 20)).fontWeight(.bold)
HStack{ HStack{
Text("Fini le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text("Fini le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
TextCapsule() TextCapsule(date: Date())
TextCapsule()
Spacer() Spacer()
} }
@ -38,14 +37,14 @@ struct ReviewCard: View {
HStack(){ HStack(){
Spacer() Spacer()
Text(amountBetted.description) Text(amountBetted.description)
.foregroundColor(AllInColors.whiteColor) .foregroundColor(.white)
.font(.system(size: 25)) .font(.system(size: 25))
.fontWeight(.bold) .fontWeight(.bold)
Image("allcoinWhiteIcon") Image("allcoinWhiteIcon")
.resizable() .resizable()
.frame(width: 20, height: 20, alignment: .bottom) .frame(width: 20, height: 20, alignment: .bottom)
Text(isAWin ? "Gagnés!" : "Perdus!") Text(isAWin ? "Gagnés!" : "Perdus!")
.foregroundColor(AllInColors.whiteColor) .foregroundColor(.white)
.font(.system(size: 25)) .font(.system(size: 25))
.fontWeight(.bold) .fontWeight(.bold)
Spacer() Spacer()
@ -71,7 +70,7 @@ struct ReviewCard: View {
.onTapGesture { .onTapGesture {
showDetails.toggle() showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) { }.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails) DetailsView(isModalPresented: $showDetails, id: "1")
} }
} }
} }

@ -8,21 +8,53 @@
import SwiftUI import SwiftUI
struct TextCapsule: View { struct TextCapsule: View {
var date: Date
private var formattedDate: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMM"
return dateFormatter.string(from: date)
}
private var formattedTime: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
return dateFormatter.string(from: date)
}
var body: some View { var body: some View {
Text("12 sept.").font(.system(size: 15)) HStack {
Text(formattedDate)
.font(.system(size: 15))
.foregroundColor(AllInColors.lightPurpleColor)
.fontWeight(.bold)
.padding([.leading, .trailing], 10)
.padding([.top, .bottom], 5)
.background(AllInColors.underComponentBackgroundColor)
.clipShape(Capsule())
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
Text(formattedTime)
.font(.system(size: 15))
.foregroundColor(AllInColors.lightPurpleColor) .foregroundColor(AllInColors.lightPurpleColor)
.fontWeight(.bold) .fontWeight(.bold)
.padding([.leading,.trailing],10) .padding([.leading, .trailing], 10)
.padding([.top,.bottom], 5).background(AllInColors.underComponentBackgroundColor) .padding([.top, .bottom], 5)
.background(AllInColors.underComponentBackgroundColor)
.clipShape(Capsule()) .clipShape(Capsule())
.overlay(RoundedRectangle(cornerRadius: 20) .overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(AllInColors.delimiterGrey, lineWidth: 1) .stroke(AllInColors.delimiterGrey, lineWidth: 1)
) )
} }
}
} }
struct TextCapsule_Previews: PreviewProvider { struct TextCapsule_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
TextCapsule() TextCapsule(date: Date())
} }
} }

@ -8,5 +8,5 @@
import Foundation import Foundation
struct Config { struct Config {
static let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api" static let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/"
} }

@ -7,14 +7,13 @@
import Foundation import Foundation
import Model import Model
import ViewModel
import DependencyInjection import DependencyInjection
import Api import Api
import StubLib import StubLib
class AuthService: IAuthService { class AuthService: IAuthService {
public func login(login: String, password: String, completion : @escaping (Int)-> ()) { public func login(login: String, password: String, completion: @escaping (Int) -> ()) {
let url = URL(string: Config.allInApi + "users/login")! let url = URL(string: Config.allInApi + "users/login")!
var request = URLRequest(url: url) var request = URLRequest(url: url)
@ -26,31 +25,35 @@ class AuthService: IAuthService {
"password": password, "password": password,
] ]
if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []){ if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []) {
URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in
print ("ALLIN : Process LOGIN") print("ALLIN: Process LOGIN")
if let httpResponse = response as? HTTPURLResponse { if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 { if httpResponse.statusCode == 200 {
if let data = data, if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let token = json["token"] as? String { let token = json["token"] as? String {
AppStateContainer.shared.authenticationRefresh = token; AppStateContainer.shared.authenticationRefresh = token
self.initializeUser(withToken: token) { status in self.initializeUser(withToken: token) { status in
print(status)
if status != 200 { if status != 200 {
completion(status) completion(status)
AppStateContainer.shared.authenticationRefresh = nil; AppStateContainer.shared.authenticationRefresh = nil
} else { } else {
self.initManagerVM(token: token) self.initManagerVM(token: token)
completion(httpResponse.statusCode)
} }
} }
} }
} } else {
completion(httpResponse.statusCode) completion(httpResponse.statusCode)
} }
}
}.resume() }.resume()
} }
} }
func register(username: String, email: String, password: String, completion : @escaping (Int)-> ()) { func register(username: String, email: String, password: String, completion : @escaping (Int)-> ()) {
let url = URL(string: Config.allInApi + "/users/register")! let url = URL(string: Config.allInApi + "/users/register")!
var request = URLRequest(url: url) var request = URLRequest(url: url)
@ -60,8 +63,7 @@ class AuthService: IAuthService {
let json = [ let json = [
"email": email.lowercased(), "email": email.lowercased(),
"username": username.lowercased(), "username": username.lowercased(),
"password": password, "password": password
"nbCoins": "0"
] ]
if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []){ if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []){
@ -79,12 +81,14 @@ class AuthService: IAuthService {
AppStateContainer.shared.authenticationRefresh = nil; AppStateContainer.shared.authenticationRefresh = nil;
} else { } else {
self.initManagerVM(token: token) self.initManagerVM(token: token)
completion(httpResponse.statusCode)
} }
} }
} }
} } else {
completion(httpResponse.statusCode) completion(httpResponse.statusCode)
} }
}
}.resume() }.resume()
} }
} }
@ -119,8 +123,10 @@ class AuthService: IAuthService {
} }
private func initializeUser(withToken token: String, completion: @escaping (Int) -> ()) { private func initializeUser(withToken token: String, completion: @escaping (Int) -> ()) {
let url = URL(string: Config.allInApi + "users/token")! let url = URL(string: Config.allInApi + "users/token")!
var request = URLRequest(url: url) var request = URLRequest(url: url)
request.httpMethod = "GET" request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
@ -144,7 +150,7 @@ class AuthService: IAuthService {
} }
private func initManagerVM(token: String) { private func initManagerVM(token: String) {
DependencyInjection.shared.addSingleton(ManagerVM.self, ManagerVM(withModel: Manager(withBetDataManager: BetStubManager(), withUserDataManager: UserApiManager(withUserToken: token)))) DependencyInjection.shared.addSingleton(Manager.self, Manager(withBetDataManager: BetStubManager(), withUserDataManager: UserApiManager(withUserToken: token)))
} }
} }

@ -7,29 +7,24 @@
import Foundation import Foundation
import DependencyInjection import DependencyInjection
import ViewModel import Model
import Combine
class BetViewModel: ObservableObject { class BetViewModel: ObservableObject {
@Inject var manager: ManagerVM @Inject var manager: Manager
@Published private(set) var bets: [Bet] = []
init() { init() {
getItems() getItems()
} }
func getItems() { func getItems() {
manager.getBets(withIndex: 0, withCount: 20) { bets in
self.bets = bets
} }
func deleteItem(indexSet: IndexSet) {
} }
func moveltem(from: IndexSet, to: Int) { private var cancellables: Set<AnyCancellable> = []
}
func addItem(title: String) {
}
} }

@ -8,11 +8,11 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import DependencyInjection import DependencyInjection
import ViewModel import Model
class CreationBetViewModel: ObservableObject { class CreationBetViewModel: ObservableObject {
@Inject var manager: ManagerVM @Inject var manager: Manager
@Published var theme: String = "" @Published var theme: String = ""
@Published var description: String = "" @Published var description: String = ""
@Published var isPublic = true @Published var isPublic = true

@ -0,0 +1,30 @@
//
// DetailsViewModel.swift
// AllIn
//
// Created by Emre on 16/01/2024.
//
import Foundation
import SwiftUI
import DependencyInjection
import Model
class DetailsViewModel: ObservableObject {
@Inject var manager: Manager
var id: String
@Published var betDetail: BetDetail?
init(id: String) {
self.id = id
getItem(withId: id)
}
func getItem(withId id: String) {
manager.getBet(withId: id) { bet in
self.betDetail = bet
}
}
}

@ -7,11 +7,11 @@
import Foundation import Foundation
import DependencyInjection import DependencyInjection
import ViewModel import Model
class FriendsViewModel: ObservableObject { class FriendsViewModel: ObservableObject {
@Inject var manager: ManagerVM @Inject var manager: Manager
init() { init() {
getItems() getItems()

@ -0,0 +1,24 @@
//
// HistoricBetViewModel.swift
// AllIn
//
// Created by Emre on 16/01/2024.
//
import Foundation
import SwiftUI
import DependencyInjection
import Model
class HistoricBetViewModel: ObservableObject {
@Inject var manager: Manager
init() {
getItems()
}
func getItems() {
}
}

@ -23,10 +23,6 @@ class LoginViewModel: ObservableObject {
func login() { func login() {
#if DEBUG
self.onLoginSuccess()
#endif
guard checkAndSetError(forLogin: true, forPassword: true) else { guard checkAndSetError(forLogin: true, forPassword: true) else {
return return
} }

@ -7,11 +7,11 @@
import Foundation import Foundation
import DependencyInjection import DependencyInjection
import ViewModel import Model
class RankingViewModel: ObservableObject { class RankingViewModel: ObservableObject {
@Inject var manager: ManagerVM @Inject var manager: Manager
init() { init() {
getItems() getItems()

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import Model
struct BetView: View { struct BetView: View {
@ -14,11 +15,8 @@ struct BetView: View {
@State var showingSheet: Bool = false @State var showingSheet: Bool = false
var body: some View { var body: some View {
ZStack(){
VStack(alignment: .center, spacing: 0) { VStack(alignment: .center, spacing: 0) {
TopBar(showMenu: self.$showMenu) TopBar(showMenu: self.$showMenu)
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) { LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
@ -27,11 +25,12 @@ struct BetView: View {
Section { Section {
VStack(spacing: 20){ VStack(spacing: 20){
BetCard() ForEach(viewModel.bets, id: \.id) { (bet: Bet) in
BetCard(bet: bet)
}
Button("Show Sheet") { Button("Show Sheet") {
showingSheet.toggle() showingSheet.toggle()
} }
} }
.padding([.leading,.trailing],25) .padding([.leading,.trailing],25)
@ -55,6 +54,9 @@ struct BetView: View {
} }
} }
} }
.refreshable {
viewModel.getItems()
}
.sheet(isPresented: $showingSheet) { .sheet(isPresented: $showingSheet) {
WinModal() WinModal()
} }
@ -64,9 +66,9 @@ struct BetView: View {
.background(AllInColors.backgroundColor) .background(AllInColors.backgroundColor)
} }
}
} }
struct BetView_Previews: PreviewProvider { struct BetView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
BetView(showMenu: .constant(false)) BetView(showMenu: .constant(false))

@ -1,9 +1,20 @@
import SwiftUI import SwiftUI
struct DetailsView: View { struct DetailsView: View {
@Binding var isModalPresented: Bool @Binding var isModalPresented: Bool
@State var isModalParticipated: Bool = false @State var isModalParticipated: Bool = false
@State var progressValue: Float = 0.2 @State var progressValue: Float = 0.2
var id: String
@StateObject private var viewModel: DetailsViewModel
init(isModalPresented: Binding<Bool>, id: String) {
self._isModalPresented = isModalPresented
self.id = id
self._viewModel = StateObject(wrappedValue: DetailsViewModel(id: id))
}
var body: some View { var body: some View {
GeometryReader { geometry in GeometryReader { geometry in
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
@ -11,7 +22,7 @@ struct DetailsView: View {
HStack{ HStack{
Text("Terminé!").font(.system(size: 25)).fontWeight(.bold).padding(.bottom, 10).foregroundStyle(AllInColors.blackTitleColor).opacity(0.7) Text("Terminé!").font(.system(size: 25)).fontWeight(.bold).padding(.bottom, 10).foregroundStyle(AllInColors.blackTitleColor).opacity(0.7)
Spacer() Spacer()
Image("CloseiconRounded") Image("closeIcon")
.resizable() .resizable()
.frame(maxWidth: 25, maxHeight: 25) .frame(maxWidth: 25, maxHeight: 25)
.onTapGesture { .onTapGesture {
@ -27,38 +38,48 @@ struct DetailsView: View {
VStack(alignment: .leading,spacing: 5){ VStack(alignment: .leading,spacing: 5){
HStack{ HStack{
Spacer() Spacer()
Text("proposé par Lucas").font(.system(size: 10)).foregroundColor(AllInColors.grey800Color) Text("proposé par " + (viewModel.betDetail?.bet.author.username ?? "Unknown").capitalized)
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
} }
Text("Etudes").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text(viewModel.betDetail?.bet.theme ?? "Not loaded")
Text("Emre va réussir son TP de CI/CD mercredi?").font(.system(size: 20)).fontWeight(.bold).padding(.bottom, 10) .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{
Text("Commence le").frame(maxWidth: 100).font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text("Commence le")
TextCapsule() .frame(maxWidth: 100)
TextCapsule() .font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: viewModel.betDetail?.bet.endRegisterDate ?? Date())
Spacer() Spacer()
}.padding(.bottom, 10) }.padding(.bottom, 10)
HStack{ HStack{
Text("Fini le").frame(maxWidth: 100).font(.system(size: 15)).foregroundColor(AllInColors.grey800Color) Text("Fini le")
TextCapsule() .frame(maxWidth: 100)
TextCapsule() .font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: viewModel.betDetail?.bet.endBetDate ?? Date())
Spacer() Spacer()
} }
} }
.frame(width: .infinity) .frame(width: .infinity)
.padding(.all,15).padding(.vertical, 10) .padding(.all,15).padding(.vertical, 10)
.background(AllInColors.whiteColor).cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0) .background(AllInColors.componentBackgroundColor)
.cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0)
ResultBanner() ResultBanner()
VStack(alignment: .leading,spacing: 15){ VStack(alignment: .leading, spacing: 15) {
BetLineLoading(value: $progressValue).padding(.vertical, 15) BetLineLoading(value: $progressValue).padding(.vertical, 15)
Spacer() Spacer()
} }
.frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight : .infinity) .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: .infinity)
.padding([.bottom,.trailing,.leading],15) .padding([.bottom,.trailing,.leading], 15)
.background(AllInColors.underComponentBackgroundColor) .background(AllInColors.underComponentBackgroundColor)
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey) .border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
Spacer() Spacer()
@ -67,7 +88,7 @@ struct DetailsView: View {
} }
.frame(maxWidth: .infinity, maxHeight: geometry.size.height*0.98) .frame(maxWidth: .infinity, maxHeight: geometry.size.height*0.98)
.background(Color.white) .background(AllInColors.componentBackgroundColor)
.cornerRadius(15) .cornerRadius(15)
ParticipateButton(isOpen: $isModalParticipated).padding(10) ParticipateButton(isOpen: $isModalParticipated).padding(10)
@ -81,9 +102,3 @@ struct DetailsView: View {
} }
} }
} }
struct DetailsView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

@ -19,14 +19,15 @@
EC01937A2B25C12B005D81E6 /* BetCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0193792B25C12B005D81E6 /* BetCard.swift */; }; EC01937A2B25C12B005D81E6 /* BetCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0193792B25C12B005D81E6 /* BetCard.swift */; };
EC01937C2B25C2A8005D81E6 /* AllcoinsCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01937B2B25C2A8005D81E6 /* AllcoinsCounter.swift */; }; EC01937C2B25C2A8005D81E6 /* AllcoinsCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01937B2B25C2A8005D81E6 /* AllcoinsCounter.swift */; };
EC01937E2B25C52E005D81E6 /* TopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01937D2B25C52E005D81E6 /* TopBar.swift */; }; EC01937E2B25C52E005D81E6 /* TopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01937D2B25C52E005D81E6 /* TopBar.swift */; };
EC01FCC32B56650400BB2390 /* DetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01FCC22B56650400BB2390 /* DetailsViewModel.swift */; };
EC01FCC52B56791B00BB2390 /* HistoricBetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC01FCC42B56791B00BB2390 /* HistoricBetViewModel.swift */; };
EC3077072B24CB840060E34D /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3077062B24CB840060E34D /* SplashView.swift */; }; EC3077072B24CB840060E34D /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3077062B24CB840060E34D /* SplashView.swift */; };
EC3077092B24CF7F0060E34D /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3077082B24CF7F0060E34D /* Colors.swift */; }; EC3077092B24CF7F0060E34D /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3077082B24CF7F0060E34D /* Colors.swift */; };
EC30770B2B24D9160060E34D /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770A2B24D9160060E34D /* WelcomeView.swift */; }; EC30770B2B24D9160060E34D /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770A2B24D9160060E34D /* WelcomeView.swift */; };
EC30770D2B24DB7A0060E34D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770C2B24DB7A0060E34D /* Extensions.swift */; }; EC30770D2B24DB7A0060E34D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770C2B24DB7A0060E34D /* Extensions.swift */; };
EC30770F2B24FCB00060E34D /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770E2B24FCB00060E34D /* RegisterView.swift */; }; EC30770F2B24FCB00060E34D /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC30770E2B24FCB00060E34D /* RegisterView.swift */; };
EC4F0D2C2B4EBF5600853949 /* Model in Frameworks */ = {isa = PBXBuildFile; productRef = ECB357342B3E13A400045D41 /* Model */; }; EC4F0D2C2B4EBF5600853949 /* Model in Frameworks */ = {isa = PBXBuildFile; productRef = ECB357342B3E13A400045D41 /* Model */; };
EC4F0D2E2B4EC04B00853949 /* ViewModel in Frameworks */ = {isa = PBXBuildFile; productRef = EC4F0D2D2B4EC04B00853949 /* ViewModel */; }; EC60C5682B5A83FB00FFD6EF /* StubLib in Frameworks */ = {isa = PBXBuildFile; productRef = EC60C5672B5A83FB00FFD6EF /* StubLib */; };
EC4F0D302B4EC05D00853949 /* StubLib in Frameworks */ = {isa = PBXBuildFile; productRef = EC4F0D2F2B4EC05D00853949 /* StubLib */; };
EC650A422B25C817003AFCAD /* Friend.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A412B25C817003AFCAD /* Friend.swift */; }; EC650A422B25C817003AFCAD /* Friend.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A412B25C817003AFCAD /* Friend.swift */; };
EC650A442B25CDF3003AFCAD /* ParameterMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A432B25CDF3003AFCAD /* ParameterMenu.swift */; }; EC650A442B25CDF3003AFCAD /* ParameterMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A432B25CDF3003AFCAD /* ParameterMenu.swift */; };
EC650A462B25D686003AFCAD /* RankingRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A452B25D686003AFCAD /* RankingRow.swift */; }; EC650A462B25D686003AFCAD /* RankingRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC650A452B25D686003AFCAD /* RankingRow.swift */; };
@ -106,7 +107,6 @@
120919192B56DC6C00D0FA29 /* DropDownAnswerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownAnswerMenu.swift; sourceTree = "<group>"; }; 120919192B56DC6C00D0FA29 /* DropDownAnswerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownAnswerMenu.swift; sourceTree = "<group>"; };
122278B72B4BDE1100E632AA /* DependencyInjection.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DependencyInjection.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 122278B92B4BDE9500E632AA /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Model/Package.swift; sourceTree = "<group>"; };
122278BB2B4BDEC300E632AA /* Sources */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Sources; path = ../StubLib/Sources; sourceTree = "<group>"; };
123590B32B51792000F7AEBD /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = "<group>"; }; 123590B32B51792000F7AEBD /* DetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsView.swift; sourceTree = "<group>"; };
123590B52B5537E200F7AEBD /* ResultBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultBanner.swift; sourceTree = "<group>"; }; 123590B52B5537E200F7AEBD /* ResultBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultBanner.swift; sourceTree = "<group>"; };
123590B72B5541BA00F7AEBD /* ParticipateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateButton.swift; sourceTree = "<group>"; }; 123590B72B5541BA00F7AEBD /* ParticipateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateButton.swift; sourceTree = "<group>"; };
@ -117,6 +117,8 @@
EC0193792B25C12B005D81E6 /* BetCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetCard.swift; sourceTree = "<group>"; }; EC0193792B25C12B005D81E6 /* BetCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetCard.swift; sourceTree = "<group>"; };
EC01937B2B25C2A8005D81E6 /* AllcoinsCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllcoinsCounter.swift; sourceTree = "<group>"; }; EC01937B2B25C2A8005D81E6 /* AllcoinsCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllcoinsCounter.swift; sourceTree = "<group>"; };
EC01937D2B25C52E005D81E6 /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; }; EC01937D2B25C52E005D81E6 /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; };
EC01FCC22B56650400BB2390 /* DetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsViewModel.swift; sourceTree = "<group>"; };
EC01FCC42B56791B00BB2390 /* HistoricBetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoricBetViewModel.swift; sourceTree = "<group>"; };
EC3077062B24CB840060E34D /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; }; EC3077062B24CB840060E34D /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
EC3077082B24CF7F0060E34D /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; }; EC3077082B24CF7F0060E34D /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
EC30770A2B24D9160060E34D /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; }; EC30770A2B24D9160060E34D /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
@ -173,10 +175,9 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EC4F0D302B4EC05D00853949 /* StubLib in Frameworks */, EC60C5682B5A83FB00FFD6EF /* StubLib in Frameworks */,
EC4F0D2C2B4EBF5600853949 /* Model in Frameworks */, EC4F0D2C2B4EBF5600853949 /* Model in Frameworks */,
ECCD244A2B4DE8010071FA9E /* Api in Frameworks */, ECCD244A2B4DE8010071FA9E /* Api in Frameworks */,
EC4F0D2E2B4EC04B00853949 /* ViewModel in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -341,7 +342,6 @@
ECB3572D2B3CA3BD00045D41 /* Frameworks */ = { ECB3572D2B3CA3BD00045D41 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
122278BB2B4BDEC300E632AA /* Sources */,
122278B92B4BDE9500E632AA /* Package.swift */, 122278B92B4BDE9500E632AA /* Package.swift */,
122278B72B4BDE1100E632AA /* DependencyInjection.framework */, 122278B72B4BDE1100E632AA /* DependencyInjection.framework */,
ECB357302B3CA69300045D41 /* DependencyInjection.framework */, ECB357302B3CA69300045D41 /* DependencyInjection.framework */,
@ -359,6 +359,8 @@
ECB26A162B4073F100FE06B3 /* CreationBetViewModel.swift */, ECB26A162B4073F100FE06B3 /* CreationBetViewModel.swift */,
ECB26A182B40744F00FE06B3 /* RankingViewModel.swift */, ECB26A182B40744F00FE06B3 /* RankingViewModel.swift */,
ECB26A1A2B40746C00FE06B3 /* FriendsViewModel.swift */, ECB26A1A2B40746C00FE06B3 /* FriendsViewModel.swift */,
EC01FCC22B56650400BB2390 /* DetailsViewModel.swift */,
EC01FCC42B56791B00BB2390 /* HistoricBetViewModel.swift */,
); );
path = ViewModels; path = ViewModels;
sourceTree = "<group>"; sourceTree = "<group>";
@ -383,8 +385,7 @@
packageProductDependencies = ( packageProductDependencies = (
ECB357342B3E13A400045D41 /* Model */, ECB357342B3E13A400045D41 /* Model */,
ECCD24492B4DE8010071FA9E /* Api */, ECCD24492B4DE8010071FA9E /* Api */,
EC4F0D2D2B4EC04B00853949 /* ViewModel */, EC60C5672B5A83FB00FFD6EF /* StubLib */,
EC4F0D2F2B4EC05D00853949 /* StubLib */,
); );
productName = AllIn; productName = AllIn;
productReference = EC6B96982B24B4CC00FC1C58 /* AllIn.app */; productReference = EC6B96982B24B4CC00FC1C58 /* AllIn.app */;
@ -539,6 +540,7 @@
EC01937E2B25C52E005D81E6 /* TopBar.swift in Sources */, EC01937E2B25C52E005D81E6 /* TopBar.swift in Sources */,
ECA9D1CB2B2DA2320076E0EC /* DropDownFriends.swift in Sources */, ECA9D1CB2B2DA2320076E0EC /* DropDownFriends.swift in Sources */,
123590B82B5541BA00F7AEBD /* ParticipateButton.swift in Sources */, 123590B82B5541BA00F7AEBD /* ParticipateButton.swift in Sources */,
EC01FCC32B56650400BB2390 /* DetailsViewModel.swift in Sources */,
ECB26A1B2B40746C00FE06B3 /* FriendsViewModel.swift in Sources */, ECB26A1B2B40746C00FE06B3 /* FriendsViewModel.swift in Sources */,
ECB7BC682B2F1ADF002A6654 /* LoginViewModel.swift in Sources */, ECB7BC682B2F1ADF002A6654 /* LoginViewModel.swift in Sources */,
1244EF622B4EC67000374ABF /* ReviewCard.swift in Sources */, 1244EF622B4EC67000374ABF /* ReviewCard.swift in Sources */,
@ -546,6 +548,7 @@
EC650A562B279D68003AFCAD /* WinModal.swift in Sources */, EC650A562B279D68003AFCAD /* WinModal.swift in Sources */,
EC6B96D12B24BAE800FC1C58 /* AuthService.swift in Sources */, EC6B96D12B24BAE800FC1C58 /* AuthService.swift in Sources */,
123590B62B5537E200F7AEBD /* ResultBanner.swift in Sources */, 123590B62B5537E200F7AEBD /* ResultBanner.swift in Sources */,
EC01FCC52B56791B00BB2390 /* HistoricBetViewModel.swift in Sources */,
EC01937C2B25C2A8005D81E6 /* AllcoinsCounter.swift in Sources */, EC01937C2B25C2A8005D81E6 /* AllcoinsCounter.swift in Sources */,
12C370482B5A5EE500CD9F0F /* BetLineLoading.swift in Sources */, 12C370482B5A5EE500CD9F0F /* BetLineLoading.swift in Sources */,
EC650A542B279545003AFCAD /* ChoiceCapsule.swift in Sources */, EC650A542B279545003AFCAD /* ChoiceCapsule.swift in Sources */,
@ -881,11 +884,7 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
EC4F0D2D2B4EC04B00853949 /* ViewModel */ = { EC60C5672B5A83FB00FFD6EF /* StubLib */ = {
isa = XCSwiftPackageProductDependency;
productName = ViewModel;
};
EC4F0D2F2B4EC05D00853949 /* StubLib */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = StubLib; productName = StubLib;
}; };

@ -0,0 +1,53 @@
//
// BetApiManager.swift
//
//
// Created by Emre on 12/01/2024.
//
import Foundation
import Model
public struct BetApiManager: BetDataManager {
public init() { }
public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) {
let url = URL(string: allInApi + "bets/gets")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var bets: [Bet] = []
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]] {
for json in jsonArray {
if let bet = FactoryApiBet().toModel(from: json) {
bets.append(bet)
print(bet.theme)
}
}
print(httpResponse.statusCode)
completion(bets)
}
} catch {
print("Error parsing JSON: \(error)")
}
}
}.resume()
}
public func getUsers(username: String) -> [User] {
return []
}
public func getBet(withId id: String, completion: @escaping (BetDetail) -> Void) {
}
}

@ -11,13 +11,13 @@ import Model
public class FactoryApiBet: FactoryBet { public class FactoryApiBet: FactoryBet {
public func toResponse(bet: Bet) -> [String: Any] { public func toResponse(bet: Bet) -> [String: Any] {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let json: [String: Any] = [ let json: [String: Any] = [
"id": "1",
"theme": bet.theme, "theme": bet.theme,
"sentenceBet": bet.phrase, "sentenceBet": bet.phrase,
"endRegistration": dateFormatter.string(from: bet.endRegisterDate), "endRegistration": bet.endRegisterDate.timeIntervalSince1970,
"endBet": dateFormatter.string(from: bet.endBetDate), "endBet": bet.endBetDate.timeIntervalSince1970,
"isPrivate": String(bet.isPublic), "isPrivate": String(bet.isPublic),
"response": [], "response": [],
] ]
@ -26,13 +26,13 @@ public class FactoryApiBet: FactoryBet {
} }
public func toModel(from json: [String: Any]) -> Bet? { public func toModel(from json: [String: Any]) -> Bet? {
guard let theme = json["theme"] as? String, guard let id = json["id"] as? String,
let theme = json["theme"] as? String,
let phrase = json["sentenceBet"] as? String, let phrase = json["sentenceBet"] as? String,
let endRegisterDateString = json["endRegistration"] as? String, let endRegisterDateString = json["endRegistration"] as? String,
let endBetDateString = json["endBet"] as? String, let endBetDateString = json["endBet"] as? String,
let isPublicString = json["isPrivate"] as? String, let isPublic = json["isPrivate"] as? Bool,
let createdBy = json["createdBy"] as? User, // Assuming User object can be parsed from JSON let createdBy = json["createdBy"] as? String else {
let type = json["type"] as? Int else {
return nil return nil
} }
@ -44,21 +44,19 @@ public class FactoryApiBet: FactoryBet {
return nil return nil
} }
let isPublic = (isPublicString.lowercased() == "true") return toModel(id: id, theme: theme, description: phrase, endRegister: endRegisterDate, endBet: endBetDate, isPublic: isPublic, creator: User(username: createdBy, email: createdBy, nbCoins: 0, friends: []), type: 0)
return toModel(theme: theme, description: phrase, endRegister: endRegisterDate, endBet: endBetDate, isPublic: isPublic, creator: createdBy, type: type)
} }
public func toModel(theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User, type: Int) -> Bet { public func toModel(id: String, theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User, type: Int) -> Bet {
switch type { switch type {
case 0: case 0:
return BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: []) return BinaryBet(id: id, theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: [])
case 1: case 1:
return MatchBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: [], nameTeam1: "", nameTeam2: "") return MatchBet(id: id, theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: [], nameTeam1: "", nameTeam2: "")
case 2: case 2:
return CustomBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: []) return CustomBet(id: id, theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: [])
default: default:
return BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: []) return BinaryBet(id: id, theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: [])
} }
} }

@ -8,7 +8,7 @@
import Foundation import Foundation
import Model import Model
let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api" let allInApi = "https://codefirst.iut.uca.fr/containers/AllDev-api/"
public struct UserApiManager: UserDataManager { public struct UserApiManager: UserDataManager {
@ -37,9 +37,6 @@ public struct UserApiManager: UserDataManager {
URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in
print ("ALLIN : Add BET") print ("ALLIN : Add BET")
if let httpResponse = response as? HTTPURLResponse { if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 201 {
}
print(httpResponse.statusCode) print(httpResponse.statusCode)
} }
}.resume() }.resume()
@ -49,4 +46,8 @@ public struct UserApiManager: UserDataManager {
public func getFriends() -> [User] { public func getFriends() -> [User] {
fatalError("Not implemented yet") fatalError("Not implemented yet")
} }
public func getOldBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) {
fatalError("Not implemented yet")
}
} }

@ -8,7 +8,11 @@
import Foundation import Foundation
/// A class representing a betting entity, including details about the bet theme, participants, and deadlines. /// A class representing a betting entity, including details about the bet theme, participants, and deadlines.
public class Bet: ObservableObject { public class Bet: ObservableObject, Identifiable {
/// The id for the bet.
public private(set) var id: String
/// The theme or topic of the bet. /// The theme or topic of the bet.
public private(set) var theme: String public private(set) var theme: String
@ -39,6 +43,32 @@ public class Bet: ObservableObject {
/// Custom Constructor /// Custom Constructor
/// ///
/// - Parameters: /// - Parameters:
/// - id: The id for the bet.
/// - theme: The theme or topic of the bet.
/// - phrase: The specific phrase or question related to the bet.
/// - endRegisterDate: The deadline for users to register for the bet.
/// - endBetDate: The deadline for the actual betting to take place.
/// - totalStakes: The total stakes or amount involved in the bet.
/// - isPublic: Indicates whether the bet is public or private.
/// - invited: List of users who are invited to participate in the bet.
/// - author: The user who created the bet.
/// - registered: List of users who have registered for the bet.
public init(id: String, theme: String, phrase: String, endRegisterDate: Date, endBetDate: Date, totalStakes: Int, isPublic: Bool, invited: [User], author: User, registered: [User]) {
self.id = id
self.theme = theme
self.phrase = phrase
self.endRegisterDate = endRegisterDate
self.endBetDate = endBetDate
self.totalStakes = totalStakes
self.isPublic = isPublic
self.invited = invited
self.author = author
self.registered = registered
}
/// Custom Constructor without Id
///
/// - Parameters:
/// - theme: The theme or topic of the bet. /// - theme: The theme or topic of the bet.
/// - phrase: The specific phrase or question related to the bet. /// - phrase: The specific phrase or question related to the bet.
/// - endRegisterDate: The deadline for users to register for the bet. /// - endRegisterDate: The deadline for users to register for the bet.
@ -49,6 +79,7 @@ public class Bet: ObservableObject {
/// - author: The user who created the bet. /// - author: The user who created the bet.
/// - registered: List of users who have registered for the bet. /// - registered: List of users who have registered for the bet.
public init(theme: String, phrase: String, endRegisterDate: Date, endBetDate: Date, totalStakes: Int, isPublic: Bool, invited: [User], author: User, registered: [User]) { public init(theme: String, phrase: String, endRegisterDate: Date, endBetDate: Date, totalStakes: Int, isPublic: Bool, invited: [User], author: User, registered: [User]) {
self.id = UUID().uuidString
self.theme = theme self.theme = theme
self.phrase = phrase self.phrase = phrase
self.endRegisterDate = endRegisterDate self.endRegisterDate = endRegisterDate

@ -0,0 +1,43 @@
//
// BetAnswerDetail.swift
//
//
// Created by Emre on 19/01/2024.
//
import Foundation
/// A class representing detailed information about a specific answer option for a bet.
public class BetAnswerDetail: ObservableObject {
/// The response or outcome associated with this answer.
public private(set) var response: String
/// The total amount of stakes placed on this answer.
public private(set) var totalStakes: Int
/// The total number of participants who selected this answer.
public private(set) var totalParticipants: Int
/// The highest stake placed on this answer.
public private(set) var highestStake: Int
/// The odds associated with this answer.
public private(set) var odds: Float
/// Custom Constructor
///
/// - Parameters:
/// - response: The response or outcome associated with this answer.
/// - totalStakes: The total amount of stakes placed on this answer.
/// - totalParticipants: The total number of participants who selected this answer.
/// - highestStake: The highest stake placed on this answer.
/// - odds: The odds associated with this answer.
public init(response: String, totalStakes: Int, totalParticipants: Int, highestStake: Int, odds: Float) {
self.response = response
self.totalStakes = totalStakes
self.totalParticipants = totalParticipants
self.highestStake = highestStake
self.odds = odds
}
}

@ -8,6 +8,7 @@
import Foundation import Foundation
public protocol BetDataManager { public protocol BetDataManager {
func getBets(withIndex index: Int, withCount count: Int) -> [Bet] func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void)
func getUsers(username: String) -> [User] func getUsers(username: String) -> [User]
func getBet(withId id: String, completion: @escaping (BetDetail) -> Void)
} }

@ -0,0 +1,38 @@
//
// BetDetail.swift
//
//
// Created by Emre on 19/01/2024.
//
import Foundation
/// A class representing detailed information about a specific bet, including answers and user participations.
public class BetDetail: ObservableObject {
/// The main bet information.
public private(set) var bet: Bet
/// Details about the answers available for the bet.
public private(set) var answers: [BetAnswerDetail]
/// List of user participations in the bet.
public private(set) var participations: [Participation]
/// The user's own participation in the bet.
public private(set) var userParticipation: Participation
/// Custom Constructor
///
/// - Parameters:
/// - bet: The main bet information.
/// - answers: Details about the answers available for the bet.
/// - participations: List of user participations in the bet.
/// - userParticipation: The user's own participation in the bet.
public init(bet: Bet, answers: [BetAnswerDetail], participations: [Participation], userParticipation: Participation) {
self.bet = bet
self.answers = answers
self.participations = participations
self.userParticipation = userParticipation
}
}

@ -13,6 +13,23 @@ public class BinaryBet: Bet {
// Custom Constructor // Custom Constructor
/// ///
/// - Parameters: /// - Parameters:
/// - id: The id for the bet.
/// - theme: The theme or topic of the binary bet.
/// - phrase: The specific phrase or question related to the binary bet.
/// - endRegisterDate: The deadline for users to register for the binary bet.
/// - endBetDate: The deadline for the actual betting to take place for the binary bet.
/// - totalStakes: The total stakes or amount involved in the binary bet.
/// - isPublic: Indicates whether the binary bet is public or private.
/// - invited: List of users who are invited to participate in the binary bet.
/// - author: The user who created the binary bet.
/// - registered: List of users who have registered for the binary bet.
public override init(id: String, theme: String, phrase: String, endRegisterDate: Date, endBetDate: Date, totalStakes: Int, isPublic: Bool, invited: [User], author: User, registered: [User]) {
super.init(id: id, theme: theme, phrase: phrase, endRegisterDate: endRegisterDate, endBetDate: endBetDate, totalStakes: totalStakes, isPublic: isPublic, invited: invited, author: author, registered: registered)
}
// Custom Constructor without Id
///
/// - Parameters:
/// - theme: The theme or topic of the binary bet. /// - theme: The theme or topic of the binary bet.
/// - phrase: The specific phrase or question related to the binary bet. /// - phrase: The specific phrase or question related to the binary bet.
/// - endRegisterDate: The deadline for users to register for the binary bet. /// - endRegisterDate: The deadline for users to register for the binary bet.

@ -1,33 +0,0 @@
//
// BinaryParticipation.swift
//
//
// Created by Emre on 28/12/2023.
//
import Foundation
/// Enum to represent the possible answers for a binary participation.
public enum YesNo {
case yes
case no
}
/// A subclass of Participation that represents a binary participation (yes/no) in a bet.
public class BinaryParticipation: Participation {
/// The answer for the binary participation (yes or no).
public var answer: YesNo
/// Custom Constructor
///
/// - Parameters:
/// - coinAmount: The amount of coins involved in the binary participation.
/// - date: The date and time when the binary participation occurred.
/// - user: The user who participated in the binary bet.
/// - bet: The binary bet in which the user participated.
/// - answer: The answer for the binary participation (yes or no).
public init(coinAmount: Int, date: Date, user: User, bet: Bet, answer: YesNo) {
self.answer = answer
super.init(coinAmount: coinAmount, date: date, user: user, bet: bet)
}
}

@ -1,27 +0,0 @@
//
// CustomParticipation.swift
//
//
// Created by Emre on 28/12/2023.
//
import Foundation
/// A subclass of Participation that represents a custom participation in a bet.
public class CustomParticipation: Participation {
/// The user's response to the custom bet.
public var answer: CustomBetResponse
/// Custom Constructor
///
/// - Parameters:
/// - coinAmount: The amount of coins involved in the custom participation.
/// - date: The date and time when the custom participation occurred.
/// - user: The user who participated in the custom bet.
/// - bet: The custom bet in which the user participated.
/// - answer: The user's response to the custom bet.
public init(coinAmount: Int, date: Date, user: User, bet: Bet, answer: CustomBetResponse) {
self.answer = answer
super.init(coinAmount: coinAmount, date: date, user: user, bet: bet)
}
}

@ -10,5 +10,5 @@ import Foundation
public protocol FactoryBet { public protocol FactoryBet {
func toResponse(bet: Bet) -> [String: Any] func toResponse(bet: Bet) -> [String: Any]
func toModel(from json: [String: Any]) -> Bet? func toModel(from json: [String: Any]) -> Bet?
func toModel(theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User, type: Int) -> Bet func toModel(id: String, theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User, type: Int) -> Bet
} }

@ -16,7 +16,25 @@ public struct Manager {
self.userDataManager = userDataManager self.userDataManager = userDataManager
} }
public func addBet(bet: Bet) { public func addBet(theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User) {
userDataManager.addBet(bet: bet) userDataManager.addBet(bet: BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: []))
}
public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) {
betDataManager.getBets(withIndex: index, withCount: count) { bets in
completion(bets)
}
}
public func getBet(withId id: String, completion: @escaping (BetDetail) -> Void) {
betDataManager.getBet(withId: id) { bet in
completion(bet)
}
}
public func getHistoricBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) {
userDataManager.getOldBets(withIndex: index, withCount: count) { bets in
completion(bets)
}
} }
} }

@ -18,6 +18,27 @@ public class MatchBet: Bet {
/// Custom Constructor /// Custom Constructor
/// ///
/// - Parameters: /// - Parameters:
/// - id: The id for the bet.
/// - theme: The theme or topic of the match bet.
/// - phrase: The specific phrase or question related to the match bet.
/// - endRegisterDate: The deadline for users to register for the match bet.
/// - endBetDate: The deadline for the actual betting to take place for the match bet.
/// - totalStakes: The total stakes or amount involved in the match bet.
/// - isPublic: Indicates whether the match bet is public or private.
/// - invited: List of users who are invited to participate in the match bet.
/// - author: The user who created the match bet.
/// - registered: List of users who have registered for the match bet.
/// - nameTeam1: The name of the first team involved in the match.
/// - nameTeam2: The name of the second team involved in the match.
public init(id: String, theme: String, phrase: String, endRegisterDate: Date, endBetDate: Date, totalStakes: Int, isPublic: Bool, invited: [User], author: User, registered: [User], nameTeam1: String, nameTeam2: String) {
self.nameTeam1 = nameTeam1
self.nameTeam2 = nameTeam2
super.init(id: id, theme: theme, phrase: phrase, endRegisterDate: endRegisterDate, endBetDate: endBetDate, totalStakes: totalStakes, isPublic: isPublic, invited: invited, author: author, registered: registered)
}
/// Custom Constructor without Id
///
/// - Parameters:
/// - theme: The theme or topic of the match bet. /// - theme: The theme or topic of the match bet.
/// - phrase: The specific phrase or question related to the match bet. /// - phrase: The specific phrase or question related to the match bet.
/// - endRegisterDate: The deadline for users to register for the match bet. /// - endRegisterDate: The deadline for users to register for the match bet.

@ -1,32 +0,0 @@
//
// MatchParticipation.swift
//
//
// Created by Emre on 28/12/2023.
//
import Foundation
/// A subclass of Participation that represents a user's participation in a match bet.
public class MatchParticipation: Participation {
/// The points earned by the user for the first team in the match.
public var pointsTeam1: Int
/// The points earned by the user for the second team in the match.
public var pointsTeam2: Int
/// Custom Constructor
///
/// - Parameters:
/// - coinAmount: The amount of coins involved in the match participation.
/// - date: The date and time when the match participation occurred.
/// - user: The user who participated in the match bet.
/// - bet: The match bet in which the user participated.
/// - pointsTeam1: The points earned by the user for the first team in the match.
/// - pointsTeam2: The points earned by the user for the second team in the match.
public init(coinAmount: Int, date: Date, user: User, bet: Bet, pointsTeam1: Int, pointsTeam2: Int) {
self.pointsTeam1 = pointsTeam1
self.pointsTeam2 = pointsTeam2
super.init(coinAmount: coinAmount, date: date, user: user, bet: bet)
}
}

@ -7,31 +7,37 @@
import Foundation import Foundation
/// A class representing a user's participation in a bet, including the amount of coins, date, user, and the associated bet. /// A class representing a user's participation in a bet.
public class Participation: ObservableObject { public class Participation: ObservableObject {
/// The amount of coins involved in the participation.
var coinAmount: Int /// The amount of stake in the bet.
public private(set) var stake: Int
/// The date and time when the participation occurred. /// The date and time when the participation occurred.
var date: Date public private(set) var date: Date
/// The response or outcome of the participation.
public private(set) var response: String
/// The user who participated in the bet. /// The user who participated in the bet.
var user: User public private(set) var user: User
/// The bet in which the user participated. /// The unique identifier of the bet.
var bet: Bet let betId: String
/// Custom Constructor /// Custom Constructor
/// ///
/// - Parameters: /// - Parameters:
/// - coinAmount: The amount of coins involved in the participation. /// - stake: The amount of stake in the bet.
/// - date: The date and time when the participation occurred. /// - date: The date and time when the participation occurred.
/// - response: The response or outcome of the participation.
/// - user: The user who participated in the bet. /// - user: The user who participated in the bet.
/// - bet: The bet in which the user participated. /// - betId: The unique identifier of the bet.
init(coinAmount: Int, date: Date, user: User, bet: Bet) { public init(stake: Int, date: Date, response: String, user: User, betId: String) {
self.coinAmount = coinAmount self.stake = stake
self.date = date self.date = date
self.response = response
self.user = user self.user = user
self.bet = bet self.betId = betId
} }
} }

@ -11,4 +11,5 @@ public protocol UserDataManager {
func getBets(withIndex index: Int, withCount count: Int) -> [Bet] func getBets(withIndex index: Int, withCount count: Int) -> [Bet]
func addBet(bet: Bet) func addBet(bet: Bet)
func getFriends() -> [User] func getFriends() -> [User]
func getOldBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void)
} }

@ -12,14 +12,20 @@ public struct BetStubManager: BetDataManager {
public init() {} public init() {}
public func getBets(withIndex index: Int, withCount count: Int) -> [Bet] { public func getBets(withIndex index: Int, withCount count: Int, completion: @escaping ([Bet]) -> Void) {
return Stub.shared.bets completion(Stub.shared.bets)
} }
public func getUsers(username: String) -> [User] { public func getUsers(username: String) -> [User] {
return Stub.shared.users return []
.filter { user in }
user.username.contains(username)
public func getBet(withId id: String, completion: @escaping (BetDetail) -> Void) {
if let betDetail = Stub.shared.betsDetail.first(where: { $0.bet.id == id }) {
completion(betDetail)
} else {
print("BetDetail with ID \(id) not found.")
} }
} }

@ -12,6 +12,7 @@ struct Stub {
static var shared = Stub() static var shared = Stub()
public var bets: [Bet] = [] public var bets: [Bet] = []
public var betsDetail: [BetDetail] = []
public var users: [User] = [] public var users: [User] = []
public init() { public init() {
@ -83,9 +84,14 @@ struct Stub {
) )
self.bets.append(bet4) self.bets.append(bet4)
for bet in bets {
let betDetail = BetDetail(bet: bet, answers: [], participations: [], userParticipation: Participation(stake: 0, date: Date(), response: "", user: user1, betId: ""))
self.betsDetail.append(betDetail)
}
} }
public mutating func add(bet: Bet) { public mutating func add(bet: Bet) {
self.bets.append(bet) let newBetDetail = BetDetail(bet: bet, answers: [], participations: [], userParticipation: Participation(stake: 0, date: Date(), response: "", user: users[1], betId: ""))
self.betsDetail.append(newBetDetail)
} }
} }

@ -1,38 +0,0 @@
//
// UserStubManager.swift
//
//
// Created by Emre on 31/12/2023.
//
import Foundation
import Model
public struct UserStubManager: UserDataManager {
private var username: String
public init(username: String) {
self.username = username
}
public func getBets(withIndex index: Int, withCount count: Int) -> [Bet] {
return Stub.shared.bets.filter { bet in
bet.registered.contains { user in
user.username == self.username
}
}
}
public func addBet(bet: Bet) {
Stub.shared.add(bet: bet)
}
public func getFriends() -> [User] {
return Stub.shared.users.filter { user in
user.friends.contains { friend in
friend.username == self.username
}
}
}
}

@ -1,29 +0,0 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "ViewModel",
platforms: [
.iOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ViewModel",
targets: ["ViewModel"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(name: "Model", path: "../Model")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ViewModel",
dependencies: ["Model"]),
]
)

@ -1,3 +0,0 @@
# ViewModel
A description of this package.

@ -1,25 +0,0 @@
//
// ManagerVM.swift
//
//
// Created by Emre on 30/12/2023.
//
import Foundation
import Model
public class ManagerVM: ObservableObject {
@Published var model: Manager
public init(withModel model: Manager) {
self.model = model
}
public func getPublicBets() {
}
public func addBet(theme: String, description: String, endRegister: Date, endBet: Date, isPublic: Bool, creator: User) {
model.addBet(bet: BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, totalStakes: 0, isPublic: isPublic, invited: [], author: creator, registered: []))
}
}
Loading…
Cancel
Save