Compare commits

...

80 Commits
V3 ... master

Author SHA1 Message Date
Emre KARTAL c640fc0acb fix icon
8 months ago
Emre KARTAL ee3f0f0c9b fix icon
8 months ago
Emre KARTAL 1b8db51116 Merge pull request 'fix/multiples-fix' (#40) from fix/multiples-fix into master
8 months ago
Emre KARTAL a4f149b81c fix little errors 🐛
8 months ago
Emre KARTAL 2bb46d3b6f Add a semaphore to avoid the empty page in the DetailsView and get the user infos when the menu is open 🐛
8 months ago
Lucas DELANIER d5e50e4168 fix multiple issues with floats, responsive etc
8 months ago
Emre KARTAL b97254d000 Merge pull request 'feature/bet-custom' (#39) from feature/bet-custom into master
8 months ago
Lucas DELANIER f668388088 fix error when loading
8 months ago
Lucas DELANIER 77e6790f59 fix order
8 months ago
Lucas DELANIER bb2a2baac3 make the review for the custom bets
8 months ago
Emre KARTAL b7efad2eed get custom answers and add the creation of bet custom
8 months ago
Lucas DELANIER f4e3edbf4a fix winning and loosing status (#38)
8 months ago
Emre KARTAL b878759b3d Merge pull request 'feature/winnings' (#37) from feature/winnings into master
8 months ago
Lucas DELANIER 65940cb3bc delete submodule
8 months ago
Lucas DELANIER e116fc9dca fix some bugs with current and finished list
8 months ago
Emre KARTAL b542ff1bf2 Bind winModal with betResult
8 months ago
Lucas DELANIER ef1f130ec5 bind images and fix ranking row (#36)
8 months ago
Emre KARTAL 68345572ef Get bets won
8 months ago
Lucas DELANIER 5d1ea8492a feature/in-progress-card (#35)
8 months ago
Emre KARTAL d82c8e7d79 Merge pull request 'Add private friends invitation' (#34) from feature/user-invitation into master
8 months ago
Emre KARTAL ab4aa3313a Add private friends invitation
8 months ago
Lucas DELANIER 32480e0cb1 fix mistake ispublic (#33)
8 months ago
Lucas DELANIER 5ce52cff75 limit the minimal date (#32)
8 months ago
Emre KARTAL 0c30fa1571 Mise à jour de 'README.md'
8 months ago
Emre KARTAL 0a9d0c0508 Transférer les fichiers vers 'Documentation/Images'
8 months ago
Lucas DELANIER b27cd37a39 feature/user-picture (#31)
8 months ago
Emre KARTAL 71e5c12992 Merge pull request 'Fix errors' (#30) from fix/participation into master
8 months ago
Emre KARTAL eed6fcca99 Fix errors
8 months ago
Arthur VALIN 7cbd2c1336 Mise à jour de 'README.md'
8 months ago
Lucas DELANIER e1c8d85dd1 i18n and empty view (#29)
8 months ago
avalin 28b0f19619 Fix bet confirmation
8 months ago
Emre KARTAL 030e3fb2f0 Merge pull request 'fix/api-calls' (#28) from fix/api-calls into master
8 months ago
Emre KARTAL b700938539 Add popular bet
8 months ago
Emre KARTAL 68e8c4d41c Get old bets
8 months ago
Lucas DELANIER 1146eeeaa8 add friend request (#27)
8 months ago
Lucas DELANIER e32d032d3c empty informations (#26)
8 months ago
Emre KARTAL c9fb2ca2ed Fix filtering
8 months ago
Emre KARTAL d3dc2536eb Merge pull request 'fix/api-calls' (#25) from fix/api-calls into master
8 months ago
Emre KARTAL e7d8908970 Adding friends and displaying the ranking
8 months ago
Lucas DELANIER 431c95e84c nice
8 months ago
Emre KARTAL 80b5f9ba51 Fix get bets
8 months ago
Emre KARTAL 155e893561 Merge pull request 'Add profile page' (#24) from feature/profile_page into master
8 months ago
Emre KARTAL 7d73e8e23a Add profile page
8 months ago
Emre KARTAL ed084dbc1b Merge pull request 'feature/localize' (#23) from feature/localize into master
8 months ago
Emre KARTAL 10c2ba3922 Add localization to error messages
8 months ago
Emre KARTAL 75c966c1ec Add localization
8 months ago
Emre KARTAL 18a01ab349 Added codeable protocol to classes to deserialize API json objects 🔨
10 months ago
Emre KARTAL de12721408 Adding a widget to view all coins
11 months ago
Emre KARTAL 58b315e818 Add notification when bet is over
11 months ago
Emre KARTAL d53ea32ce7 Add local notifications service
11 months ago
Emre KARTAL 6e26a99b3d Add quick actions
11 months ago
Emre KARTAL dbdd27b14b get answers
12 months ago
Emre KARTAL 1e5e1269e4 Bind endBet with API
12 months ago
Emre KARTAL ff9e9f6ed2 Added and used the bet status enum instead of dates, and set the size of the betLineLoading
12 months ago
Emre KARTAL 65c49e1b40 Fix adding a bet with API
12 months ago
Emre KARTAL 059b3fec74 Add getGift
12 months ago
Lucas DELANIER 2be8faebd0 Merge pull request 'animation for the valid button' (#22) from fix/answer-animation into master
1 year ago
Lucas DELANIER 12709c8e00 animation for the valid button
1 year ago
Lucas DELANIER 98270138fc little dark mod fix
1 year ago
Emre KARTAL 5a4a27df9a Merge pull request 'feature/current_bets' (#21) from feature/current_bets into master
1 year ago
Emre KARTAL 623535e2b4 Merge with master
1 year ago
Emre KARTAL 28250f7b00 Merge pull request 'feature/bet-ending' (#19) from feature/bet-ending into master
1 year ago
Emre KARTAL 2f25e47d49 Merge pull request 'view/daily-gift' (#18) from view/daily-gift into master
1 year ago
Emre KARTAL f1c482caa8 Add more animations
1 year ago
Lucas DELANIER fa7ef95648 finish validation bet answer
1 year ago
Emre KARTAL 07d2353230 replace opacite to scale 🔨
1 year ago
Emre KARTAL ff67f2d1d0 Add animation in each step
1 year ago
Emre KARTAL 58047e8f48 Begging the daily gift page 🔨
1 year ago
Emre KARTAL dfe0ec2665 Merge branch 'feature/current_bets' of https://codefirst.iut.uca.fr/git/AllDev/Apple into feature/current_bets
1 year ago
Emre KARTAL 4238358cb6 Correction of the visual and refresh the Allcoins of a user 🔨
1 year ago
Lucas DELANIER 1e7f0aa521 fix marquee
1 year ago
Lucas DELANIER 32202d8860 progress validation answer
1 year ago
Emre KARTAL f2ddcf9f08 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
1 year ago
Emre KARTAL 27afb5718e Mise à jour de '.drone.yml'
continuous-integration/drone/push Build encountered an error Details
1 year ago
Emre KARTAL 86b6e0c765 Add View CurrentBet
1 year ago
Lucas DELANIER ef15072a22 fix modal marquee integration
1 year ago
Lucas DELANIER fb6c8b58c6 Merge pull request 'fix/Model_Details' (#17) from fix/Model_Details into master
1 year ago
Emre KARTAL 74d032c915 Editing images and managing stakes in a bet 🔨
1 year ago
Emre KARTAL f6df5faf03 Changed images and colors for the details page, and refreshed information after adding a participation
1 year ago
Emre KARTAL 542c8a6b3a Update comments and fix refresh betPage after i add a Bet
1 year ago

@ -12,3 +12,9 @@ steps:
commands:
- cd Sources/Model
- swift build
- name: test
image: swift:latest
commands:
- cd Sources/Model
- swift test

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

@ -21,7 +21,7 @@
**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
# Répartition du dépot
[**Sources**](Sources) : **Code de l'application**
@ -32,7 +32,43 @@
- MVVM
<div align = center>
<img src="https://codefirst.iut.uca.fr/git/AllDev/Gestion_de_projet/raw/branch/master/Documentation/Diagrammes/AllInMVVM.png" width="600" />
</div>
# Fonctionnement
- ### Comment lancer le projet ?
:information_source: *Assurez-vous d'avoir un Mac à disposition*
Tout d'abord si ce n'est pas fait cloner le dépôt de la branche **master/main**, pour cela copier le lien URL du dépôt git :
<div align = center>
![Comment cloner](Documentation/Images/HowToClone.png)
</div>
Sur votre Mac, ouvrez l'IDE **Xcode** (disponible via l'App Store), puis cloner le dépôt en utilisant l'URL copiée précédemment :
<div align = center>
<img src="Documentation/Images/WelcomeToXcode.png" width="500" >
</div>
Vous serez alors redirigé par l'IDE et pourrez lancer l'application sur l'appareil de votre choix :
<div align = center>
<img src="Documentation/Images/LaunchApp.png" width="900" >
</div>
*Si vous souhaitez lancer l'application sur votre appareil personnel, il sera nécessaire de renseigner votre compte iCloud dans l'IDE !*
# Technologies

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array/>
</dict>
</plist>

@ -13,6 +13,7 @@ import Model
struct AllInApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var phase
let DI = DependencyInjection.shared
init() {
@ -22,6 +23,17 @@ struct AllInApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
AppStateContainer.shared.notificationState.scheduleNotifications()
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .background, .inactive:
UIApplication.shared.shortcutItems = QuickAction.allShortcutItems
default:
break
}
}
}
}
}

@ -1,41 +0,0 @@
//
// AppDelegate.swift
// AllIn
//
// Created by Emre on 17/12/2023.
//
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

@ -12,9 +12,10 @@ class AppStateContainer: ObservableObject {
static let shared = AppStateContainer()
let loggedState: LoggedState = LoggedState()
var onlineStatus: OnlineStatus = OnlineStatus()
var user: User?
var notificationState: NotificationService = NotificationService()
@Published var user: User?
@AppStorage("authenticationRefresh") var authenticationRefresh: String?
@AppStorage("authenticationRefresh", store: UserDefaults(suiteName: "group.alldev.AllIn")!) var authenticationRefresh: String?
}
class LoggedState: ObservableObject {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 B

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x49",
"green" : "0x49",
"red" : "0x49"
"blue" : "0x24",
"green" : "0x24",
"red" : "0x24"
}
},
"idiom" : "universal"
@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x49",
"green" : "0x49",
"red" : "0x49"
"blue" : "0x24",
"green" : "0x24",
"red" : "0x24"
}
},
"idiom" : "universal"

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x3C",
"green" : "0x3C",
"red" : "0x3C"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Group 107 (1).png",
"filename" : "allCoinBlack.png",
"idiom" : "universal",
"scale" : "1x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Vector (1).png",
"filename" : "blueFlame.png",
"idiom" : "universal",
"scale" : "1x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "GiftEarn.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "Gift.png",
"idiom" : "universal",
"scale" : "1x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "globe.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Mask group (3).png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "PinkAllCoin.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "PinkBadge.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "pinkFlame.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "UserPink.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Trophy.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

@ -8,11 +8,12 @@
import SwiftUI
struct AllcoinsCapsule: View {
var gains: Int
var body: some View {
Text("Vous remportez")
.foregroundColor(.white)
HStack{
Text("2340")
Text(gains.description)
.textStyle(weight: .bold, color: .white, size: 60)
Image("allcoinWhiteIcon")
.resizable()
@ -30,6 +31,6 @@ struct AllcoinsCapsule: View {
struct AllcoinsCapsule_Previews: PreviewProvider {
static var previews: some View {
AllcoinsCapsule()
AllcoinsCapsule(gains: 100)
}
}

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

@ -13,16 +13,15 @@ struct BetCard: View {
var bet: Bet
@State var showDetails: Bool = false
@State var showParticipate: Bool = false
var body: some View {
VStack(spacing: 0){
VStack(spacing: 0) {
VStack(alignment: .leading,spacing: 2){
HStack{
Spacer()
Text("proposé par " + bet.author.username.capitalized)
Text("bet_proposed_by_format \(bet.author.capitalized)")
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
}
Text(bet.theme)
.font(.system(size: 15))
@ -31,7 +30,9 @@ struct BetCard: View {
.font(.system(size: 20))
.fontWeight(.bold)
HStack{
Text("Commence le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
Text("bet_starting")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: bet.endRegisterDate)
Spacer()
}
@ -43,14 +44,15 @@ struct BetCard: View {
VStack(alignment: .leading,spacing: 2){
HStack{
Spacer()
UsersPreview()
Text(String(bet.registered.count) + " joueurs en attente")
UsersPreview(users: [])
Text("bet_players_waiting_format \(bet.invited.count.description)")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
.fontWeight(.medium)
Spacer()
}.padding(0)
}
.padding(0)
ParticipateButton(isOpen: $showDetails, isParticapatedOpen: $showParticipate, bet: bet)
.padding(.top, 5)
}
@ -64,7 +66,7 @@ struct BetCard: View {
showDetails.toggle()
}
.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showParticipate,id: bet.id)
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showParticipate, id: bet.id)
}
}
}
@ -75,11 +77,10 @@ struct BetCard_Previews: PreviewProvider {
phrase: "Le gagnant de la finale sera l'équipe avec le plus de tirs au but.",
endRegisterDate: Date().addingTimeInterval(86400),
endBetDate: Date().addingTimeInterval(172800),
isPublic: true,
status: .FINISHED,
isPrivate: false,
status: .inProgress,
invited: [],
author: User(username: "Imri", email: "emre.kartal@etu.uca.fr", nbCoins: 75, friends: []),
registered: []))
author: "Imri"))
.preferredColorScheme(.dark)
}
}

@ -10,12 +10,13 @@ import Model
struct BetLineLoading: View {
var participations: [Participation]
@State var showInfos: Bool = false
var bet: BetDetail
var value: CGFloat {
let totalParticipations = participations.count
let numberOfYes = participations.filter { $0.response.uppercased() == "YES" }.count
let numberOfNo = participations.filter { $0.response.uppercased() == "NO" }.count
let totalParticipations = bet.participations.count
let numberOfYes = bet.participations.filter { $0.answer.uppercased() == "YES" }.count
let numberOfNo = bet.participations.filter { $0.answer.uppercased() == "NO" }.count
if(numberOfNo == 0 && numberOfYes == 0){
return 0.5
}
@ -23,78 +24,23 @@ struct BetLineLoading: View {
return totalParticipations > 0 ? CGFloat(numberOfYes) / CGFloat(totalParticipations) : 0.0
}
var yesParticipations: [Participation] {
return participations.filter { $0.response.uppercased() == "YES" }
bet.participations.filter { $0.answer.uppercased() == "YES" }
}
var noParticipations: [Participation] {
return participations.filter { $0.response.uppercased() == "NO" }
bet.participations.filter { $0.answer.uppercased() == "NO" }
}
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading,spacing: 0){
HStack(spacing: 5){
Text("OUI").font(.system(size: 25)).fontWeight(.bold).foregroundColor(AllInColors.bleue200)
Spacer()
Text("NON").font(.system(size: 25)).fontWeight(.bold).foregroundColor(AllInColors.pink100)
}
ZStack(alignment: .leading) {
HStack{
Spacer()
Rectangle().frame(width: min(CGFloat(1-self.value)*geometry.size.width, geometry.size.width), height: 17)
.foregroundStyle(AllInColors.PinkBetGradiant).cornerRadius(999)
}
HStack(spacing: 0){
Rectangle().frame(width: min(CGFloat(self.value)*geometry.size.width, geometry.size.width), height: 17)
.foregroundStyle(AllInColors.BlueBetGradiant).cornerRadius(999)
.animation(.linear)
Image("LoadingHeart").resizable().frame(width: 29, height: 32).padding(.leading, -10)
}
}
VStack(spacing: 1){
HStack(spacing: 5){
Image("BlueAllCoinIcon").resizable().frame(width:12, height: 12)
Text(yesParticipations.reduce(0, {x,y in x + y.stake}).description).font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.bleue200)
Spacer()
Text(noParticipations.reduce(0, {x,y in x + y.stake}).description).font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.pink100)
Image("PinkBadge").resizable().frame(width:10, height: 14)
}
HStack(spacing: 5){
Image("BleuePersonIcon").resizable().frame(width:14, height: 12)
Text(yesParticipations.count.description).font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.bleue200)
Spacer()
Text(noParticipations.count.description).font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.pink100)
Image("PinkBadge").resizable().frame(width:10, height: 14)
}
HStack(spacing: 5){
Image("BleueBadge").resizable().frame(width:10, height: 14)
Text(yesParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0").font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.bleue200)
Spacer()
Text(noParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0").font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.pink100)
Image("PinkBadge").resizable().frame(width:10, height: 14)
}
HStack(spacing: 5){
Image("BleueTrophyIcon").resizable().frame(width:14, height: 13)
Text("1.2").font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.bleue200)
Spacer()
Text("1.2").font(.system(size: 15)).fontWeight(.bold).foregroundColor(AllInColors.pink100)
Image("PinkBadge").resizable().frame(width:10, height: 14)
}
}
switch bet.bet {
case is BinaryBet:
BinaryBetLine(bet: bet)
case is CustomBet:
CustomBetLine(bet: bet)
default:
BinaryBetLine(bet: bet)
}
}.frame(height: 140)
}
}

@ -0,0 +1,168 @@
//
// BinaryBetLine.swift
// AllIn
//
// Created by Lucas Delanier on 10/06/2024.
//
import SwiftUI
import Model
struct BinaryBetLine: View {
@State var showInfos: Bool = false
var bet: BetDetail
var value: CGFloat {
let totalParticipations = bet.participations.count
let numberOfYes = bet.participations.filter { $0.answer.uppercased() == "YES" }.count
let numberOfNo = bet.participations.filter { $0.answer.uppercased() == "NO" }.count
if(numberOfNo == 0 && numberOfYes == 0){
return 0.5
}
return totalParticipations > 0 ? CGFloat(numberOfYes) / CGFloat(totalParticipations) : 0.0
}
var yesParticipations: [Participation] {
bet.participations.filter { $0.answer.uppercased() == "YES" }
}
var noParticipations: [Participation] {
bet.participations.filter { $0.answer.uppercased() == "NO" }
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 5) {
Text("OUI")
.font(.system(size: 25))
.fontWeight(.bold)
.foregroundColor(AllInColors.blue200)
Spacer()
Text("NON")
.font(.system(size: 25))
.fontWeight(.bold)
.foregroundColor(AllInColors.pink100)
}
GeometryReader { geometry in
ZStack(alignment: .leading) {
HStack{
Spacer()
Rectangle()
.frame(width: min(CGFloat(1-self.value) * geometry.size.width, geometry.size.width-20), height: 17)
.foregroundStyle(AllInColors.PinkBetGradiant).cornerRadius(999)
}
HStack(spacing: 0) {
Rectangle()
.frame(width: min(CGFloat(self.value) * geometry.size.width, geometry.size.width-20), height: 17)
.foregroundStyle(AllInColors.BlueBetGradiant)
.cornerRadius(999)
Image("loadingHeartIcon")
.resizable()
.frame(width: 29, height: 32)
.padding(.leading, -10)
}
}
.padding(.bottom, 5)
}
.frame(height: 40)
HStack {
Spacer()
Text("bet_status_details_drawer")
.textStyle(weight: .medium, color: AllInColors.primaryTextColor, size: 10)
Image(showInfos ? "chevronUpIcon" : "chevronDownIcon")
.resizable()
.frame(width: 10, height: 7)
.scaledToFill()
}
.onTapGesture {
withAnimation {
showInfos.toggle()
}
}
.padding(.bottom, 5)
.padding(.trailing, 5)
if showInfos {
VStack(spacing: 1) {
HStack(spacing: 5) {
Image("blueAllCoinIcon")
.resizable()
.frame(width:12, height: 12)
Text(yesParticipations.reduce(0, {x,y in x + y.stake}).description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.blue200)
Spacer()
Text(noParticipations.reduce(0, {x,y in x + y.stake}).description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.pink100)
Image("pinkAllCoinIcon")
.resizable()
.frame(width: 12, height: 12)
}
HStack(spacing: 5){
Image("bluePersonIcon")
.resizable()
.frame(width: 12, height: 12)
Text(yesParticipations.count.description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.blue200)
Spacer()
Text(noParticipations.count.description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.pink100)
Image("pinkPersonIcon")
.resizable()
.frame(width: 12, height: 12)
}
HStack(spacing: 5){
Image("blueBadgeIcon")
.resizable()
.frame(width: 12, height: 12)
Text(yesParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.blue200)
Spacer()
Text(noParticipations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.pink100)
Image("pinkBadgeIcon")
.resizable()
.frame(width: 12, height: 12)
}
HStack(spacing: 5){
Image("blueTrophyIcon")
.resizable()
.frame(width: 12, height: 12)
Text("1.2")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.blue200)
Spacer()
Text("1.2")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(AllInColors.pink100)
Image("pinkTrophyIcon")
.resizable()
.frame(width:12, height: 12)
}
}
}
}
}
}

@ -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("En cours")
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("En cours")
Text(label)
.textStyle(weight: .regular, color: AllInColors.grey800Color, size: 15)
.padding([.leading,.trailing], 15)
.padding([.top,.bottom], 7)
@ -35,13 +52,12 @@ struct ChoiceCapsule: View {
}
.onTapGesture() {
pressed.toggle()
if(pressed) {
viewModel.filters.insert(filter)
} else {
viewModel.filters.remove(filter)
}
}
}
}
struct ChoiceCapsule_Previews: PreviewProvider {
static var previews: some View {
ChoiceCapsule()
}
}

@ -0,0 +1,43 @@
//
// ChoiceFinalAnswerCell.swift
// AllIn
//
// Created by Lucas Delanier on 29/01/2024.
//
import Foundation
import SwiftUI
import Model
struct ChoiceFinalAnswerCell: View {
var selected = false
var answer: AnswerDetail
var rawColor = AllInColors.blueAccentColor
var body: some View {
ZStack {
HStack {
Spacer()
Text(answer.response)
.textStyle(weight: .bold, color: selected ? .white : rawColor, size: 40)
.padding(.vertical, 10)
Spacer()
}
HStack {
Spacer()
OddCapsule(
backgroundColor: selected ? .white : AllInColors.purpleAccentColor,
foregroundColor: selected ? AllInColors.purpleAccentColor : .white,
odd: answer.odds
)
.padding(.trailing, 20)
.scaleEffect(0.9)
}
}
.background(selected ? AllInColors.purpleAccentColor : .white)
.cornerRadius(17)
.scaleEffect(selected ? 1.02 : 1)
.animation(.easeInOut(duration: 0.3), value: selected)
}
}

@ -0,0 +1,141 @@
//
// CustomBetLine.swift
// AllIn
//
// Created by Lucas Delanier on 10/06/2024.
//
import SwiftUI
import Model
struct CustomBetLine: View {
@State var showInfos: [String: Bool] = [:]
var bet: BetDetail
var participationsForAnswer: [String: [Participation]] {
Dictionary(grouping: bet.participations, by: { $0.answer.uppercased() })
}
var sortedAnswers: [AnswerDetail] {
bet.answers.sorted { $0.totalParticipants > $1.totalParticipants }
}
func getTextStyle(for answer: AnswerDetail) -> Font.Weight {
return answer == sortedAnswers.first ? .bold : .light
}
func getColor(for answer: AnswerDetail) -> Color {
return answer == sortedAnswers.first ? AllInColors.pinkAccentColor : AllInColors.blueAccentColor
}
func getGradiant(for answer: AnswerDetail) -> LinearGradient {
return answer == sortedAnswers.first ? AllInColors.PinkBetGradiant : AllInColors.BlueBetLineGradiant
}
func getFlameImage(for answer: AnswerDetail) -> String {
return answer == sortedAnswers.first ? "pinkFlameIcon" : "blueFlameIcon"
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(sortedAnswers, id: \.response) { answer in
let participations = participationsForAnswer[answer.response.uppercased()] ?? []
let totalParticipations = bet.participations.count
let percentage = totalParticipations > 0 ? CGFloat(participations.count) / CGFloat(totalParticipations) : 0.0
VStack {
HStack {
Text(answer.response)
.font(.system(size: 15))
.fontWeight(getTextStyle(for: answer))
.foregroundColor(getColor(for: answer))
Spacer()
}
GeometryReader { geometry in
ZStack(alignment: .leading) {
HStack(spacing: 0) {
Rectangle()
.frame(width: min(percentage * geometry.size.width, geometry.size.width-20), height: 17)
.foregroundStyle(getGradiant(for: answer))
.cornerRadius(999, corners: [.topLeft, .bottomLeft])
Image(getFlameImage(for: answer))
.resizable()
.frame(width: 29, height: 32)
.padding(.leading, -6)
Text("\(Int(percentage * 100))%")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundStyle(getColor(for: answer))
.padding(.leading, 2)
}
}
.padding(.bottom, 5)
}
.frame(height: 40)
HStack {
Spacer()
Text("bet_status_details_drawer")
.textStyle(weight: .medium, color: AllInColors.primaryTextColor, size: 10)
Image(showInfos[answer.response] ?? false ? "chevronUpIcon" : "chevronDownIcon")
.resizable()
.frame(width: 10, height: 7)
.scaledToFill()
}
.onTapGesture {
withAnimation {
showInfos[answer.response, default: false].toggle()
}
}
.padding(.trailing, 5)
if showInfos[answer.response] ?? false {
HStack{
VStack(alignment: .leading,spacing: 1) {
HStack(spacing: 5) {
Image(answer == sortedAnswers.first ? "pinkAllCoinIcon" : "blueAllCoinIcon")
.resizable()
.frame(width: 12, height: 12)
Text(participations.reduce(0, { x, y in x + y.stake }).description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(getColor(for: answer))
}
HStack(spacing: 5) {
Image(answer == sortedAnswers.first ? "pinkPersonIcon" : "bluePersonIcon")
.resizable()
.frame(width: 12, height: 12)
Text(participations.count.description)
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(getColor(for: answer))
}
HStack(spacing: 5) {
Image(answer == sortedAnswers.first ? "pinkBadgeIcon" : "blueBadgeIcon")
.resizable()
.frame(width: 12, height: 12)
Text(participations.max(by: { $0.stake < $1.stake })?.stake.description ?? "0")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(getColor(for: answer))
}
HStack(spacing: 5) {
Image(answer == sortedAnswers.first ? "pinkTrophyIcon" : "blueTrophyIcon")
.resizable()
.frame(width: 12, height: 12)
Text(String(format: "%.2f", answer.odds))
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(getColor(for: answer))
}
}
Spacer()
}
}
}
}
}
}
}

@ -1,12 +1,3 @@
//
// DropDownAnswerMenu.swift
// AllIn
//
// Created by Lucas Delanier on 16/01/2024.
//
import SwiftUI
//
// DropDownMenu.swift
// AllIn
@ -15,20 +6,21 @@ import SwiftUI
//
import SwiftUI
import Model
struct DropDownAnswerMenu: View {
@State var expand = false
@Binding var selectedOption: Int
var options: [(Int, String, Float)]
@Binding var selectedAnswer: AnswerDetail
var answers: [AnswerDetail]
var body: some View {
VStack(spacing: 0, content: {
Button(action: { self.expand.toggle() }) {
Button(action: { withTransaction(Transaction(animation: nil)) { self.expand.toggle() } }) {
HStack{
Text(options[selectedOption].1.description)
Text(selectedAnswer.response)
.textStyle(weight: .bold, color: AllInColors.blueAccentColor, size: 20)
Text(options[selectedOption].2.description)
Text("\(selectedAnswer.odds, specifier: "%.2f")")
.textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 10)
Spacer()
@ -43,39 +35,32 @@ struct DropDownAnswerMenu: View {
.foregroundColor(AllInColors.delimiterGrey)
.padding(.bottom, 18)
VStack(spacing: 0) {
ForEach(0..<options.count, id: \.self) { index in
if options[index].0 != selectedOption {
Button(action: {self.selectedOption = options[index].0
self.expand.toggle()}) {
HStack{
Text(options[index].1.description)
.textStyle(weight: .bold, color: AllInColors.blueAccentColor, size: 20)
Text(options[index].2.description)
.textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 10)
Spacer()
}
ForEach(answers) { (answer: AnswerDetail) in
if answer != selectedAnswer {
Button(action: {
self.selectedAnswer = answer
self.expand.toggle()}
) {
HStack{
Text(answer.response)
.textStyle(weight: .bold, color: AllInColors.blueAccentColor, size: 20)
Text("\(answer.odds, specifier: "%.2f")")
.textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 10)
Spacer()
}
.padding(.bottom, 15)
}
.padding(.bottom, 15)
}
}
}
.padding([.leading, .trailing], 15)
}
})
.frame(width: .infinity)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(10)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.stroke(AllInColors.veryLightPurpleColor, lineWidth: 0.4)
)
}
}
struct DropDownAnswerMenu_Previews: PreviewProvider {
static var previews: some View {
DropDownAnswerMenu(selectedOption: .constant(0), options: [
(0, "questionMarkIcon", 1.2),
(1, "footballIcon", 2.2),
(2, "paintbrushIcon", 3.3)
])
.preferredColorScheme(.dark)
}
}

@ -6,27 +6,21 @@
//
import SwiftUI
import Model
struct DropDownFriends: View {
@State private var selectedItems: Set<Int> = []
@Binding var selectedItems: Set<String>
@State var expand = false
let friends: [(Int, Int, String, String)] = [
(0, 541, "David", "defaultUserImage"),
(1, 541, "David", "defaultUserImage"),
(2, 541, "David", "defaultUserImage"),
(3, 541, "David", "defaultUserImage"),
(4, 541, "David", "defaultUserImage"),
(5, 541, "David", "defaultUserImage")
]
var friends: [User]
var body: some View {
VStack(spacing: 0, content: {
Button(action: { self.expand.toggle() }) {
HStack(spacing: 3){
Text("41")
Text(friends.count.description)
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 15)
Text("amis disponibles")
Text("bet_creation_friends_available_format")
.textStyle(weight: .regular, color: AllInColors.grey800Color, size: 15)
Spacer()
@ -42,33 +36,33 @@ struct DropDownFriends: View {
.foregroundColor(AllInColors.delimiterGrey)
ScrollView(.vertical) {
VStack(spacing: 0) {
ForEach(0..<friends.count, id: \.self) { item in
ForEach(friends, id: \.self) { (friend: User) in
HStack {
Circle()
.fill(selectedItems.contains(friends[item].0) ? AllInColors.lightPurpleColor : Color.clear)
.fill(selectedItems.contains(friend.id) ? AllInColors.lightPurpleColor : Color.clear)
.overlay(
Circle()
.stroke(selectedItems.contains(friends[item].0) ? Color.clear : AllInColors.skyBlueColor, lineWidth: 1)
.stroke(selectedItems.contains(friend.id) ? Color.clear : AllInColors.skyBlueColor, lineWidth: 1)
)
.frame(width: 15, height: 15)
.padding(.trailing, 5)
UserInfo()
UserInfo(username: friend.username, value: friend.nbCoins)
.contentShape(Rectangle())
}
.padding([.leading, .trailing], 15)
.padding([.top, .bottom], 5)
.overlay(
selectedItems.contains(friends[item].0) ?
selectedItems.contains(friend.id) ?
Rectangle()
.fill(AllInColors.lightPurpleColor.opacity(0.13))
: nil
)
.opacity(1.0)
.onTapGesture {
if selectedItems.contains(friends[item].0) {
selectedItems.remove(friends[item].0)
if selectedItems.contains(friend.id) {
selectedItems.remove(friend.id)
} else {
selectedItems.insert(friends[item].0)
selectedItems.insert(friend.id)
}
}
Rectangle()
@ -90,9 +84,3 @@ struct DropDownFriends: View {
)
}
}
struct DropDownFriends_Previews: PreviewProvider {
static var previews: some View {
DropDownFriends()
}
}

@ -0,0 +1,27 @@
//
// EmptyInfo.swift
// AllIn
//
// Created by Lucas Delanier on 29/05/2024.
//
import SwiftUI
struct EmptyInfo: View {
let emoji: String
let title: String
let explain: String
var body: some View {
VStack{
Text(emoji).font(.system(size: 120))
Text(title).textStyle(weight: .bold, color: AllInColors.primaryTextColor , size: 15)
explain.isEmpty ? nil : Text(explain).textStyle(weight: .light, color: .gray, size: 12)
}.opacity(0.55).padding(.horizontal, 20).multilineTextAlignment(.center)
}
}
struct EmptyInfo_Previews: PreviewProvider {
static var previews: some View {
EmptyInfo(emoji:"👥", title: "Vous navez pas encore damis", explain: "Ajoutez les depuis cet écran")
}
}

@ -6,38 +6,54 @@
//
import SwiftUI
import Model
struct Friend: View {
var image: String
var pseudo: String
var user: User
let isRequest: Bool
@ObservedObject var viewModel: FriendsViewModel
var StatusValues: (String, Color, Color) {
switch user.friendStatus {
case .friend:
return isRequest ? (String(localized: "generic_decline"), AllInColors.grey400Color, AllInColors.componentBackgroundColor) : (String(localized: "generic_delete"), AllInColors.grey400Color, AllInColors.componentBackgroundColor)
case .notFriend:
return isRequest ? (String(localized: "generic_acccept"), .white, AllInColors.lightPurpleColor) : (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))
.frame(width: 50, height: 50)
.cornerRadius(180)
.scaledToFit()
Text(pseudo)
UserPicture(picture: user.image,username: user.username, size: 45)
Text(user.username)
.fontWeight(.medium)
.padding(.leading, 5)
.font(.system(size: 18))
.lineLimit(1)
Spacer()
Button("Supprimer") {}
.frame(width: 90, height: 30)
.foregroundColor(AllInColors.grey400Color)
.font(.system(size: 14))
.background(AllInColors.componentBackgroundColor)
.cornerRadius(5)
Button(StatusValues.0) {
viewModel.toggleFriendStatus(for: user, isRequest: isRequest)
}
.minimumScaleFactor(0.3)
.lineLimit(2)
.frame(width: 90, height: 30)
.foregroundColor(StatusValues.1)
.font(.system(size: 14))
.background(StatusValues.2)
.cornerRadius(5)
if(isRequest){
Button{
viewModel.declineRequest(username: user.username)
}label: {
Image(systemName: "xmark").foregroundColor(.gray)
}.padding([.leading], 25)
}
}
.padding([.trailing,.leading], 25)
}
}
struct Friend_Previews: PreviewProvider {
static var previews: some View {
Friend(image: "https://picsum.photos/536/354", pseudo: "Lucas")
}
}

@ -12,17 +12,21 @@ struct Menu: View {
@Inject var authService: IAuthService
let parameters: [(String, String, String, String)] = [
("videoGameImage", String(localized: "drawer_create_a_bet"), String(localized: "drawer_create_a_bet_subtitle"), "CreationBet"),
("globeImage", String(localized: "drawer_public_bets"), String(localized: "drawer_public_bets_subtitle"), "Bet"),
("moneyImage", String(localized: "drawer_current_bets"), String(localized: "drawer_current_bets_subtitle"), "Current"),
("eyesImage", String(localized: "drawer_bet_history"), String(localized: "drawer_bet_history_subtitle"),"Historic"),
("friendsImage", String(localized: "drawer_friends"), String(localized: "drawer_friends_subtitle"), "Friends"),
("rankingImage", String(localized: "drawer_ranking"), String(localized: "drawer_ranking_subtitle"), "Ranking"),
]
var body: some View {
VStack(alignment: .leading, spacing: 10) {
HStack() {
Spacer()
VStack(){
Image("defaultUserImage")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.cornerRadius(180)
UserPicture(picture: AppStateContainer.shared.user?.image, username: (AppStateContainer.shared.user?.username)!,size: 100)
Text(AppStateContainer.shared.user?.username.capitalized ?? "")
.fontWeight(.medium)
.font(.system(size: 17))
@ -35,31 +39,31 @@ struct Menu: View {
HStack(spacing: 30) {
Spacer()
VStack(){
Text("114")
Text(AppStateContainer.shared.user?.bestWin ?? 0, format: .number)
.fontWeight(.heavy)
.font(.system(size: 15))
.foregroundColor(.white)
Text("Bets")
Text(String(localized: "drawer_bets"))
.fontWeight(.regular)
.font(.system(size: 12))
.foregroundColor(AllInColors.grey600Color)
}
VStack(){
Text("343")
Text(AppStateContainer.shared.user!.bestWin, format: .number)
.fontWeight(.heavy)
.font(.system(size: 15))
.foregroundColor(.white)
Text("Meilleur gain")
Text(String(localized: "drawer_best_win"))
.fontWeight(.regular)
.font(.system(size: 12))
.foregroundColor(AllInColors.grey600Color)
}
VStack(){
Text("5")
Text(AppStateContainer.shared.user!.nbFriends, format: .number)
.fontWeight(.heavy)
.font(.system(size: 15))
.foregroundColor(.white)
Text("Amis")
Text(String(localized: "drawer_nb_friends"))
.fontWeight(.regular)
.font(.system(size: 12))
.foregroundColor(AllInColors.grey600Color)
@ -68,33 +72,11 @@ struct Menu: View {
}
.padding(.bottom, 15)
NavigationLink(destination: MainView(page: "CreationBet").navigationBarBackButtonHidden(true))
{
ParameterMenu(image: "videoGameImage", title: "CREER UN BET", description: "Créez un nouveau BET et faites participer vos amis.")
.padding([.leading,.trailing], 13)
}
NavigationLink(destination: MainView(page: "Historic").navigationBarBackButtonHidden(true))
{
ParameterMenu(image: "eyesImage", title: "HISTORIQUE DES BETS", description: "Consultez vos paris en cours et terminés.")
.padding([.leading,.trailing], 13)
}
NavigationLink(destination: MainView(page: "Friends").navigationBarBackButtonHidden(true))
{
ParameterMenu(image: "friendsImage", title: "AMIS", description: "Défiez vos porches en les ajoutant en amis.")
.padding([.leading,.trailing], 13)
}
NavigationLink(destination: MainView(page: "Bet").navigationBarBackButtonHidden(true))
{
ParameterMenu(image: "moneyImage", title: "BET EN COURS", description: "Gérez vos bets et récompensez les gagnants.")
.padding([.leading,.trailing], 13)
}
NavigationLink(destination: MainView(page: "Ranking").navigationBarBackButtonHidden(true))
{
ParameterMenu(image: "rankingImage", title: "CLASSEMENT", description: "Consultez votre classement parmis vos amis.")
.padding([.leading,.trailing], 13)
ForEach(0..<parameters.count, id: \.self) { index in
NavigationLink(destination: MainView(page: parameters[index].3).navigationBarBackButtonHidden(true)) {
ParameterMenu(image: parameters[index].0, title: parameters[index].1, description: parameters[index].2)
.padding([.leading, .trailing], 13)
}
}
HStack {
@ -102,21 +84,28 @@ struct Menu: View {
Button {
authService.logout()
} label: {
Text("Deconnexion")
Text("generic_logout")
.foregroundColor(.white)
}
Spacer()
}
Spacer()
Image("gearIcon")
.resizable()
.frame(width: 30, height: 30)
.padding([.leading,.bottom], 20)
NavigationLink(destination: MainView(page: "Profile")
.navigationBarBackButtonHidden(true))
{
Image("gearIcon")
.resizable()
.frame(width: 30, height: 30)
.padding([.leading,.bottom], 20)
}
}
.frame(maxWidth: .infinity,alignment: .leading)
.background(AllInColors.primaryColor)
.edgesIgnoringSafeArea(.bottom)
.onAppear {
authService.refreshAuthentication()
}
}
}

@ -0,0 +1,33 @@
//
// OddCapsule.swift
// AllIn
//
// Created by Lucas Delanier on 03/02/2024.
//
import Foundation
import SwiftUI
struct OddCapsule: View {
var backgroundColor: Color = AllInColors.purpleAccentColor
var foregroundColor: Color = AllInColors.whiteColor
var odd: Float = 0.0
var body: some View {
HStack(alignment: .center) {
Text("x\(odd, specifier: "%.2f")")
.fontWeight(.bold)
.foregroundColor(foregroundColor)
}
.padding(.horizontal, 10)
.padding(.vertical,5)
.background(backgroundColor)
.cornerRadius(9999)
}
}
struct OddCapsule_Previews: PreviewProvider {
static var previews: some View {
OddCapsule()
}
}

@ -1,29 +0,0 @@
//
// ParticiationCell.swift
// AllIn
//
// Created by Lucas Delanier on 21/01/2024.
//
import SwiftUI
import Model
struct ParticiationCell: View {
@State var participation: Participation?
var body: some View {
HStack(alignment: .center, spacing: 0){
Circle().frame(width: 30, height: 30).foregroundColor(AllInColors.grey700Color).padding(.trailing, 5)
Text(participation?.user.username ?? "Unknown")
.font(.system(size: 15))
.foregroundStyle(AllInColors.grey100Color)
.fontWeight(.semibold)
Spacer()
Text(participation?.stake.description ?? "NaN")
.font(.system(size: 18))
.foregroundStyle(AllInColors.lightPurpleColor)
.fontWeight(.bold).padding(.trailing, 5)
Image("PurpleAllCoin").resizable().frame(width: 11, height: 12)
}
}
}

@ -15,16 +15,16 @@ struct ParticipateButton: View {
var bet: Bet?
var isDisabled: Bool {
guard let endRegisterDate = bet?.endRegisterDate else {
return true
}
let currentDate = Date()
switch currentDate.compare(endRegisterDate) {
case .orderedAscending:
return false
case .orderedDescending, .orderedSame:
guard bet?.author != AppStateContainer.shared.user?.username else { return true }
if let betType = bet?.status {
switch betType {
case .inProgress:
return false
default:
return true
}
} else {
return true
}
}
@ -34,7 +34,7 @@ struct ParticipateButton: View {
isOpen = true
isParticapatedOpen = true
} label: {
Text("Participer")
Text("bet_participate")
.font(.system(size: 27))
.fontWeight(.semibold)
.frame(maxWidth: .infinity).padding(10)
@ -44,7 +44,7 @@ struct ParticipateButton: View {
case true:
AllInColors.grey700Color.frame(width: 170)
.mask(
Text("Participer")
Text("bet_participate")
.font(.system(size: 27))
.fontWeight(.semibold)
.frame(maxWidth: .infinity).padding(10)
@ -52,7 +52,7 @@ struct ParticipateButton: View {
case false:
AllInColors.primaryGradient.frame(width: 170)
.mask(
Text("Participer")
Text("bet_participate")
.font(.system(size: 27))
.fontWeight(.semibold)
.frame(maxWidth: .infinity).padding(10)
@ -66,6 +66,7 @@ struct ParticipateButton: View {
.overlay(
RoundedRectangle(cornerRadius: 12).stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
}.disabled(isDisabled)
}
.disabled(isDisabled)
}
}

@ -6,107 +6,115 @@
//
import SwiftUI
import Model
struct ParticipationModal: View {
@Binding private var selectedOption: Int
@Binding private var mise: String
private var description: String
var participationAddedCallback: (() -> Void)?
@Binding var selectedAnswer: AnswerDetail
@Binding var mise: String
var phrase: String
var answers: [AnswerDetail]
var participationAddedCallback: () -> Void
var checkAndSetError: () -> Bool
init(answer: Binding<Int>, mise: Binding<String>, description: String, participationAddedCallback: (() -> Void)? = nil) {
self._selectedOption = answer
self._mise = mise
self.description = description
self.participationAddedCallback = participationAddedCallback
var possibleGain: Int {
if let stake = Float(mise) {
return Int(round(stake * selectedAnswer.odds))
} else {
return 0
}
}
let options: [(Int, String, Float)] = [
(0, "OUI", 1.2),
(1, "NON", 3.3),
]
var body: some View {
VStack(alignment: .leading){
HStack{
Spacer()
Rectangle()
.foregroundStyle(AllInColors.grey800Color)
.frame(maxWidth: 80, maxHeight: 5)
.cornerRadius(999)
Spacer()
}.padding(10)
HStack{
Text("Faites vos paris")
.font(.system(size: 18))
.foregroundColor(AllInColors.blackTitleColor)
.fontWeight(.semibold)
Spacer()
AllcoinsCounter(backgroundColor: AllInColors.purpleAccentColor, foregroundColor: AllInColors.whiteColor)
}
.padding(.leading, 15)
GeometryReader { geometry in
VStack(alignment: .leading){
Text(description)
.font(.system(size: 13))
.foregroundColor(AllInColors.grey100Color)
.fontWeight(.light)
DropDownAnswerMenu(selectedOption: $selectedOption, options: options)
TextField("",text: $mise, prompt: Text("Mise")
.foregroundColor(AllInColors.lightGrey300Color)
.font(.system(size: 14))
.fontWeight(.bold))
.padding()
.keyboardType(.numberPad)
.background(
RoundedRectangle(cornerRadius: 9)
.fill(AllInColors.lightGrey200Color)
.frame(height: 40)
)
.frame(width: .infinity, height: 40)
.foregroundColor(AllInColors.primaryTextColor)
.overlay(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
.padding(.bottom, 5)
}
.padding(15)
Spacer()
VStack{
HStack{
Text("Gains possibles")
.font(.system(size: 13))
.foregroundColor(AllInColors.blackTitleColor)
.fontWeight(.regular)
Spacer()
Text("231")
Rectangle()
.foregroundStyle(AllInColors.lightGrey300Color)
.frame(maxWidth: 80, maxHeight: 5)
.cornerRadius(999)
Spacer()
}
.padding(10)
HStack{
Text("bet_status_place_your_bets")
.font(.system(size: 18))
.foregroundColor(AllInColors.primaryTextColor)
.fontWeight(.semibold)
Spacer()
AllcoinsCounter(backgroundColor: AllInColors.lightPurpleColor, foregroundColor: .white)
}
.padding(.leading, 15)
VStack(alignment: .leading){
Text(phrase)
.font(.system(size: 13))
.foregroundColor(AllInColors.blackTitleColor)
.foregroundColor(AllInColors.primaryTextColor)
.fontWeight(.light)
DropDownAnswerMenu(selectedAnswer: $selectedAnswer, answers: answers)
TextField("", text: $mise, prompt: Text("generic_stake")
.foregroundColor(AllInColors.lightGrey300Color)
.font(.system(size: 16))
.fontWeight(.bold))
.padding()
.keyboardType(.numberPad)
.background(
RoundedRectangle(cornerRadius: 9)
.fill(AllInColors.lightGrey200Color)
.frame(height: 40)
)
.frame(height: 40)
.foregroundColor(.black)
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
.padding(.bottom, 5)
}
.padding(.top, 10).padding(.bottom, 0)
Button {
participationAddedCallback?()
} label: {
Text("Miser")
.font(.system(size: 23))
.foregroundColor(AllInColors.whiteColor)
.fontWeight(.bold)
.frame(maxWidth: .infinity)
.padding(.vertical, 3)
.padding(15)
Spacer()
VStack{
HStack{
Text("participation_possible_winnings")
.font(.system(size: 13))
.foregroundColor(AllInColors.primaryTextColor)
.fontWeight(.medium)
Spacer()
Text(possibleGain.description)
.font(.system(size: 13))
.foregroundColor(AllInColors.primaryTextColor)
.fontWeight(.bold)
Image("allcoinIcon")
.resizable()
.frame(width: 11, height: 12)
}
.padding(.top, 10)
.padding(.bottom, 0)
Button {
participationAddedCallback()
} label: {
Text("Miser")
.font(.system(size: 23))
.foregroundColor(AllInColors.whiteColor)
.fontWeight(.bold)
.frame(maxWidth: .infinity)
.padding(.vertical, 3)
}
.buttonStyle(.borderedProminent)
.tint(AllInColors.lightPurpleColor)
.disabled(checkAndSetError())
.padding(.bottom, 5)
}
.buttonStyle(.borderedProminent)
.tint(AllInColors.purpleAccentColor)
.padding(.horizontal, 10)
.background(AllInColors.whiteColor)
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
}
.padding(.horizontal, 10)
.background(AllInColors.whiteColor)
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
.background(AllInColors.underComponentBackgroundColor)
}
.background(AllInColors.underComponentBackgroundColor)
}
}

@ -10,37 +10,20 @@ import SwiftUI
struct RankingRow: View {
var number: Int
var image: String
var image: String?
var pseudo: String
var allCoins: Int
var body: some View {
HStack(){
Text(String(number))
Text(number.description)
.textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 18)
.padding(.leading, 15)
Image(image)
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.cornerRadius(180)
.padding([.bottom,.top], 10)
Text(pseudo)
.fontWeight(.medium)
.font(.system(size: 16))
.lineLimit(1)
Spacer()
Image("allcoinIcon")
.resizable()
.frame(width: 17, height: 17, alignment: .leading)
Text(String(allCoins))
.textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16)
.padding(.trailing, 15)
UserInfo(username: pseudo, picture: image, value: allCoins)
}
.padding(10)
.padding(.horizontal, 5)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(8)
.padding([.leading,.trailing],20)
.frame(maxWidth: 750)
}
}

@ -6,34 +6,37 @@
//
import SwiftUI
import Model
struct RecapBetCard: View {
var betResult: BetResultDetail
@GestureState private var longPressTap = false
@State private var isPressed = false
@State var showDetails: Bool = false
@State var showPartipated: Bool = false
var body: some View {
VStack(spacing: 0){
VStack(alignment: .leading,spacing: 2){
HStack{
Spacer()
Text("proposé par Lucas")
Text("bet_proposed_by_format \(betResult.bet.author)")
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
}
Text("Etudes")
Text(betResult.bet.theme)
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
Text("Emre va réussir son TP de CI/CD mercredi?")
Text(betResult.bet.phrase)
.font(.system(size: 20))
.fontWeight(.bold)
HStack{
Text("Fini le ")
Text("bet_ends")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: Date())
TextCapsule(date: betResult.bet.endBetDate)
Spacer()
}
}
@ -47,7 +50,7 @@ struct RecapBetCard: View {
Text("Mise")
.textStyle(weight: .regular, color: AllInColors.grey800Color, size: 15)
Spacer()
Text("1630")
Text(betResult.participation.stake.description)
.textStyle(weight: .regular, color: AllInColors.grey800Color, size: 15)
Image("Allcoins")
.resizable()
@ -64,13 +67,13 @@ struct RecapBetCard: View {
Text("Gains")
.textStyle(weight: .medium, color: AllInColors.lightPurpleColor, size: 15)
Spacer()
Text("1630")
Text(betResult.amount.description)
.font(.system(size: 15))
.fontWeight(.medium)
.overlay {
AllInColors.primaryGradient.frame(width: 50)
.mask(
Text("1630").font(.system(size: 15)).fontWeight(.medium)
Text(betResult.amount.description).font(.system(size: 15)).fontWeight(.medium)
)
}
.padding(0)
@ -86,42 +89,35 @@ struct RecapBetCard: View {
HStack{
Text("Côte totale").font(.system(size: 15)).fontWeight(.medium)
Spacer()
Text("3,46")
// TODO bind le odd
Text("1,0")
.textStyle(weight: .bold, color: .white, size: 18)
.padding([.leading,.trailing],10)
.padding([.top,.bottom],5)
.background(AllInColors.primaryGradient)
.cornerRadius(8, corners: .allCorners)
}.padding([.bottom],10)
}.frame(width: .infinity)
.padding([.top,.bottom],8)
.padding([.leading,.trailing],15)
.background(AllInColors.underComponentBackgroundColor)
.cornerRadius(20, corners: [.bottomLeft,.bottomRight])
.padding(.bottom,0).border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
}.scaleEffect(longPressTap ? 0.97 : 1.0)
.animation(.easeInOut, value: longPressTap)
.onTapGesture {
showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showPartipated,id: "1")
}
.padding([.bottom],10)
}
.gesture(
LongPressGesture(minimumDuration: 0.5)
.updating($longPressTap) { value, state, _ in
state = value
}
)
}
}
struct RecapBetCard_Previews: PreviewProvider {
static var previews: some View {
RecapBetCard()
.preferredColorScheme(.dark)
.frame(width: .infinity)
.padding([.top,.bottom],8)
.padding([.leading,.trailing],15)
.background(AllInColors.underComponentBackgroundColor)
.cornerRadius(20, corners: [.bottomLeft,.bottomRight])
.padding(.bottom,0).border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
}
.scaleEffect(longPressTap ? 0.97 : 1.0)
.animation(.easeInOut, value: longPressTap)
.onTapGesture {
showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showPartipated, id: betResult.bet.id)
}
.gesture(
LongPressGesture(minimumDuration: 0.5)
.updating($longPressTap) { value, state, _ in
state = value
}
)
}
}

@ -6,26 +6,29 @@
//
import SwiftUI
import Model
struct ResultBanner: View {
var finalAnswer: Participation
var odds: Float
var body: some View {
VStack{
HStack{
Image("BleueTrophyIcon").resizable().frame(maxWidth: 70, maxHeight: 60)
Text("OUI").font(.system(size: 70)).fontWeight(.bold).foregroundStyle(AllInColors.blueGrey800Color)
Image(systemName: "trophy.fill").resizable().frame(maxWidth: 70, maxHeight: 60).foregroundColor(AllInColors.blueGrey800Color)
Text(finalAnswer.answer).font(.system(size: 70)).fontWeight(.bold).foregroundStyle(AllInColors.blueGrey800Color)
}.frame(height: 80)
HStack(spacing: 20){
HStack{
Image("BlueAllCoinIcon").resizable().frame(maxWidth: 12, maxHeight: 12)
Text("460").font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
Image("blueAllCoinIcon").resizable().frame(maxWidth: 12, maxHeight: 12)
Text(finalAnswer.stake.description).font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
}
HStack{
Image("BleuePersonIcon").resizable().frame(maxWidth: 15, maxHeight: 12)
Text("ImriDu43").font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
Image("bluePersonIcon").resizable().frame(maxWidth: 15, maxHeight: 12)
Text(finalAnswer.username).font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
}
HStack{
Image("BleueTrophyIcon").resizable().frame(maxWidth: 15, maxHeight: 12)
Text("x1.2").font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
Image("blueTrophyIcon").resizable().frame(maxWidth: 15, maxHeight: 12)
Text("\(odds, specifier: "%.2f")").font(.system(size: 16)).fontWeight(.semibold).foregroundStyle(AllInColors.blueGrey800Color)
}
}
}

@ -6,72 +6,108 @@
//
import SwiftUI
import Model
struct ReviewCard: View {
@State var showDetails: Bool = false
@State var showPartipated: Bool = false
var bet: Bet
var amount: Int
var isWin: Bool
var amountBetted: Int
var isAWin: Bool
var body: some View {
VStack(spacing: 0){
VStack(alignment: .leading,spacing: 2){
HStack{
Spacer()
Text("proposé par Lucas").font(.system(size: 10)).foregroundColor(AllInColors.grey800Color)
Text("bet_proposed_by_format \(bet.author)")
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
}
Text("Etudes").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
Text("Emre va réussir son TP de CI/CD mercredi?").font(.system(size: 20)).fontWeight(.bold)
Text(bet.theme).font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
Text(bet.phrase).font(.system(size: 20)).fontWeight(.bold)
HStack{
Text("Fini le").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
TextCapsule(date: Date())
Text("bet_ends").font(.system(size: 15)).foregroundColor(AllInColors.grey800Color)
TextCapsule(date: bet.endBetDate)
Spacer()
}
}
.frame(width: .infinity)
.padding(.all,15)
.background(AllInColors.componentBackgroundColor).cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0)
VStack(alignment: .center,spacing:0){
HStack(){
Spacer()
Text(amountBetted.description)
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
Image("allcoinWhiteIcon")
.resizable()
.frame(width: 20, height: 20, alignment: .bottom)
Text(isAWin ? "Gagnés!" : "Perdus!")
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
if bet.status == .finished {
Text(amount.description)
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
Image("allcoinWhiteIcon")
.resizable()
.frame(width: 18, height: 20)
}
switch bet.status {
case .waiting, .inProgress:
Text("bet_status_stake")
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
case .closing:
Text("bet_status_finished")
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
case .finished:
Text(isWin ? "Gagnés!" : "Perdus!")
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
case .cancelled:
Text("cancelled")
.foregroundColor(.white)
.font(.system(size: 25))
.fontWeight(.bold)
}
Spacer()
}
.frame(width: .infinity)
.padding(.all,10)
}
.frame(width: .infinity)
.padding(.all,2)
.background(
isAWin ?
AnyView(AllInColors.primaryGradient) :
AnyView(Color.black)
) .cornerRadius(20, corners: [.bottomLeft,.bottomRight])
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
.background(backgroundColor())
.cornerRadius(20, corners: [.bottomLeft,.bottomRight])
}
.onTapGesture {
showDetails.toggle()
}.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showPartipated, id: "1")
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showPartipated, id: bet.id)
}
}
private func backgroundColor() -> some View {
Group {
if bet.status == .finished && isWin {
AllInColors.primaryGradient
} else {
switch bet.status {
case .inProgress, .waiting, .closing:
AllInColors.grey50Color
case .finished:
Color.black
case .cancelled:
Color.red
}
}
}
}
}

@ -36,7 +36,7 @@ struct TextCapsule: View {
RoundedRectangle(cornerRadius: 20)
.stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
Text(formattedTime)
.font(.system(size: 15))
.foregroundColor(AllInColors.lightPurpleColor)
@ -49,6 +49,9 @@ struct TextCapsule: View {
RoundedRectangle(cornerRadius: 20)
.stroke(AllInColors.delimiterGrey, lineWidth: 1)
)
Spacer()
}
}
}

@ -6,8 +6,14 @@
//
import SwiftUI
import Model
struct TrendingBetCard: View {
var bet: Bet
@State var showDetails: Bool = false
@State var showParticipate: Bool = false
var body: some View {
VStack(alignment: .leading) {
HStack {
@ -15,12 +21,12 @@ struct TrendingBetCard: View {
.resizable()
.frame(width: 15, height: 15, alignment: .leading)
Text("Populaire")
Text("bet_popular")
.textStyle(weight: .medium, color: AllInColors.pinkAccentColor, size: 17)
}
.padding([.leading, .top], 10)
Text("Emre va réussir son TP de CI/CD mercredi?")
Text(bet.theme)
.textStyle(weight: .heavy, color: .white, size: 17)
.frame(height: 47)
.multilineTextAlignment(.leading)
@ -33,7 +39,7 @@ struct TrendingBetCard: View {
Text("12")
.textStyle(weight: .bold, color: AllInColors.pinkAccentColor, size: 14)
Text("joueurs")
Text("bet_players_format")
.textStyle(weight: .regular, color: .white, size: 14)
.padding([.leading], 2)
@ -41,7 +47,7 @@ struct TrendingBetCard: View {
.textStyle(weight: .bold, color: AllInColors.pinkAccentColor, size: 14)
.padding([.leading], 10)
Text("points misés")
Text("bet_points_at_stake_format")
.textStyle(weight: .regular, color: .white, size: 14)
.padding([.leading], 2)
}
@ -56,11 +62,24 @@ struct TrendingBetCard: View {
.stroke(AllInColors.primaryGradient, lineWidth: 5)
)
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.onTapGesture {
showDetails.toggle()
}
.fullScreenCover(isPresented: $showDetails) {
DetailsView(isModalPresented: $showDetails, isModalParticipated: $showParticipate, id: bet.id)
}
}
}
struct TrendingBetCard_Previews: PreviewProvider {
static var previews: some View {
TrendingBetCard()
TrendingBetCard(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),
isPrivate: true,
status: .inProgress,
invited: [],
author: "Imri"))
}
}

@ -6,27 +6,29 @@
//
import SwiftUI
import Model
struct UserInfo: View {
var username: String
var picture: String?
var value: Int
var body: some View {
HStack {
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
Text("David")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 13)
UserPicture(picture: picture, username: username, size: 35)
.padding(.trailing, 7)
Text(username)
.font(.system(size: 15))
.foregroundStyle(AllInColors.primaryTextColor)
.fontWeight(.semibold)
Spacer()
Image("allcoinIcon")
Text(value.description)
.font(.system(size: 18))
.foregroundStyle(AllInColors.lightPurpleColor)
.fontWeight(.bold)
.padding(.trailing, 4)
Image("PurpleAllCoin")
.resizable()
.frame(width: 17, height: 17, alignment: .leading)
Text("541")
.textStyle(weight: .black, color: AllInColors.lightPurpleColor, size: 16)
.frame(width: 15, height: 16)
}
}
}
struct UserInfo_Previews: PreviewProvider {
static var previews: some View {
UserInfo()
}
}

@ -6,22 +6,19 @@
//
import SwiftUI
import Model
struct UsersPreview: View {
var users: [User]?
var body: some View {
HStack(spacing: -20){
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
if users != nil {
HStack(spacing: -20){
ForEach(users!.prefix(4)) { user in
Image("defaultUserImage")
.resizable()
.frame(width: 35, height: 35)
}
}
}
}
}

@ -6,9 +6,11 @@
//
import SwiftUI
import Model
struct WinModal: View {
@Environment(\.dismiss) var dismiss
var betResult: BetResultDetail
@State var xOffset: CGFloat = 0
var body: some View {
@ -16,136 +18,9 @@ struct WinModal: View {
GeometryReader { geometry in
let size = geometry.size.width / 20
InfiniteScroller(contentWidth: size * 20) {
VStack(spacing: 20) {
VStack(spacing: 20) {
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing:20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
}
VStack(spacing: 20) {
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing:20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
HStack(spacing: 20){
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110,height:110)
Image("allcoinWhiteIcon").resizable().frame(width: 110, height:110)
}
}
}
.padding(10)
InfiniteScroller(contentWidth: geometry.size.width) {
Image("marquee").resizable().scaledToFit()
}
VStack {
ZStack(alignment: .topLeading){
@ -162,15 +37,15 @@ struct WinModal: View {
}
HStack{
Text("FÉLICITATIONS").font(.system(size: 20)).foregroundColor(.white).fontWeight(.semibold).italic()
Text("PSEUDO!").padding(.top,9).font(.system(size: 33)).fontWeight(.heavy).foregroundColor(.white)
Text(AppStateContainer.shared.user?.username ?? "").padding(.top,9).font(.system(size: 33)).fontWeight(.heavy).foregroundColor(.white)
}
.rotationEffect(.degrees(-4))
.padding(.top,40)
Spacer()
AllcoinsCapsule()
AllcoinsCapsule(gains: betResult.amount)
Spacer()
RecapBetCard()
RecapBetCard(betResult: betResult)
Spacer()
}
.padding([.all],20)
@ -185,12 +60,6 @@ struct WinModal: View {
}
}
struct WinModal_Previews: PreviewProvider {
static var previews: some View {
WinModal()
}
}
struct InfiniteScroller<Content: View>: View {
var contentWidth: CGFloat
var content: (() -> Content)
@ -204,13 +73,8 @@ struct InfiniteScroller<Content: View>: View {
content()
content()
content()
content()
content()
content()
content()
content()
}
.offset(x: xOffset, y: 0)
.offset(x: xOffset, y: 0).opacity(0.3)
}
.disabled(true)
.onAppear {
@ -221,8 +85,9 @@ struct InfiniteScroller<Content: View>: View {
xOffset = +contentWidth
}
}
.frame(width: 1200)
.rotationEffect(.degrees(-30))
.opacity(0.04)
.frame(width: 1300, height: 1300)
.padding(.leading, -400)
.rotationEffect(.degrees(30))
.opacity(0.1)
}
}

@ -0,0 +1,58 @@
//
// userPicture.swift
// AllIn
//
// Created by Lucas Delanier on 04/06/2024.
//
import SwiftUI
struct UserPicture: View {
var picture: String?
var username: String
var size: CGFloat
var body: some View {
ZStack {
if let pictureURL = picture {
userImage(url: pictureURL)
} else {
placeholderImage
}
}
}
@MainActor private func userImage(url: String) -> some View {
AsyncCachedImage(url: URL(string: url)) { image in
image
.resizable()
.frame(width: size, height: size)
.clipShape(Circle())
} placeholder: {
placeholderImage
}
}
private var placeholderImage: some View {
Circle()
.foregroundColor(.gray)
.frame(width: size, height: size)
.overlay(
Text(username.prefix(2).uppercased())
.fontWeight(.medium)
.foregroundStyle(.white)
.font(.system(size: fontSize(for: size)))
)
}
private func fontSize(for diameter: CGFloat) -> CGFloat {
let fontScaleFactor: CGFloat = 0.45
return diameter * fontScaleFactor
}
}
struct UserPicture_Previews: PreviewProvider {
static var previews: some View {
UserPicture(picture: nil, username: "Lucas", size: 100)
}
}

@ -7,17 +7,25 @@
import SwiftUI
import DependencyInjection
import Model
struct ContentView: View {
@State private var show = false
@State private var gain = 0
@Inject var authService: IAuthService
@Inject var manager: Manager
@ObservedObject var loggedState = AppStateContainer.shared.loggedState
var body: some View {
VStack {
if loggedState.connectedUser {
NavigationView {
MainView(page: "Bet")
if let name = QuickAction.selectedAction?.userInfo?["name"] as? String {
MainView(page: name)
} else {
MainView(page: "Bet")
}
}
.navigationViewStyle(StackNavigationViewStyle())
} else {
@ -30,6 +38,26 @@ struct ContentView: View {
.onAppear {
authService.refreshAuthentication()
}
.onChange(of: loggedState.connectedUser) { _ in
if loggedState.connectedUser {
manager.getTodayGifts() { status, gained in
if status == 200 {
withAnimation {
show = true
gain = gained
}
}
}
}
}
.overlay(
Group {
if show {
DailyGiftPage(show: $show, gain: $gain)
.transition(.opacity)
}
}
)
}
}

@ -83,42 +83,3 @@ extension View {
}
}
}
struct SlideInFromBottomTransition: ViewModifier {
var yOffset: CGFloat
func body(content: Content) -> some View {
content
.offset(y: yOffset)
.animation(.easeInOut(duration: 0.3))
}
}
extension AnyTransition {
static func slideInFromBottom(yOffset: CGFloat) -> AnyTransition {
AnyTransition.modifier(
active: SlideInFromBottomTransition(yOffset: yOffset),
identity: SlideInFromBottomTransition(yOffset: 0)
)
}
}
struct SlideOutToBottomTransition: ViewModifier {
var yOffset: CGFloat
func body(content: Content) -> some View {
content
.offset(y: yOffset)
.opacity(yOffset == 0 ? 1 : 0)
.animation(.easeInOut(duration: 0.3))
}
}
extension AnyTransition {
static func slideOutToBottom(yOffset: CGFloat) -> AnyTransition {
AnyTransition.modifier(
active: SlideOutToBottomTransition(yOffset: yOffset),
identity: SlideOutToBottomTransition(yOffset: 0)
)
}
}

@ -0,0 +1,25 @@
//
// Delegates.swift
// AllIn
//
// Created by Emre on 22/02/2024.
//
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if let selectedAction = options.shortcutItem {
QuickAction.selectedAction = selectedAction
}
let sceneConfiguration = UISceneConfiguration(name: "Quick Action Scene", sessionRole: connectingSceneSession.role)
sceneConfiguration.delegateClass = QuickActionSceneDelegate.self
return sceneConfiguration
}
}
class QuickActionSceneDelegate: UIResponder, UIWindowSceneDelegate {
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
QuickAction.selectedAction = shortcutItem
}
}

@ -0,0 +1,44 @@
//
// QuickAction.swift
// AllIn
//
// Created by Emre on 22/02/2024.
//
import UIKit
enum QuickAction {
static var selectedAction: UIApplicationShortcutItem?
static var homeuserInfo: [String : NSSecureCoding] {
["name" : "Bet" as NSSecureCoding]
}
static var createuserInfo: [String : NSSecureCoding] {
["name" : "CreationBet" as NSSecureCoding]
}
static var frienduserInfo: [String : NSSecureCoding] {
["name" : "Friends" as NSSecureCoding]
}
static var rankuserInfo: [String : NSSecureCoding] {
["name" : "Ranking" as NSSecureCoding]
}
static var allShortcutItems: [UIApplicationShortcutItem] = {
var shortcuts: [UIApplicationShortcutItem] = []
if AppStateContainer.shared.loggedState.connectedUser {
shortcuts.append(UIApplicationShortcutItem(type: "home", localizedTitle: "Coins : " + String(AppStateContainer.shared.user?.nbCoins ?? 0), localizedSubtitle: "", icon: UIApplicationShortcutIcon(templateImageName: "allCoinBlackIcon"), userInfo: homeuserInfo))
}
shortcuts.append(contentsOf: [
UIApplicationShortcutItem(type: "create", localizedTitle: "Créer un pari", localizedSubtitle: "", icon: UIApplicationShortcutIcon(systemImageName: "pencil.circle"), userInfo: createuserInfo),
UIApplicationShortcutItem(type: "friend", localizedTitle: "Voir mes amis", localizedSubtitle: "", icon: UIApplicationShortcutIcon(systemImageName: "person.2.square.stack"), userInfo: frienduserInfo),
UIApplicationShortcutItem(type: "ranking", localizedTitle: "Classement", localizedSubtitle: "Où en suis-je ?", icon: UIApplicationShortcutIcon(systemImageName: "rosette"), userInfo: rankuserInfo)
])
return shortcuts
}()
}

@ -29,6 +29,15 @@ struct AllInColors {
static let loginPurpleColor = Color("LoginPurpleColor")
static let whiteColor = Color("WhiteColor")
// Details
static let blue200 = Color("Blue200")
static let purple200 = Color("Purple200")
static let pink200 = Color("Pink200")
static let pink100 = Color("Pink100")
// Profile
static let secondaryTextColor = Color("SecondaryTextColor")
// Others
static let backgroundColor = Color("BackgroundColor")
static let darkBlueColor = Color("DarkBlueColor")
@ -48,11 +57,8 @@ struct AllInColors {
static let componentBackgroundColor = Color("ComponentBackgroundColor")
static let underComponentBackgroundColor = Color("UnderComponentBackgroundColor")
static let winBannerBackground = Color("WinBannerBackground")
static let blackTitleColor = Color("BlackTitleColor")
static let bleue200 = Color("Bleue200")
static let purple200 = Color("Purple200")
static let pink200 = Color("Pink200")
static let pink100 = Color("Pink100")
static let greyDarkColor = Color("GreyDarkColor")
// Gradients
static let primaryGradient = LinearGradient(
gradient: Gradient(colors: [AllInColors.pinkAccentColor, AllInColors.blueAccentColor]),
@ -66,7 +72,13 @@ struct AllInColors {
)
static let BlueBetGradiant = LinearGradient(
gradient: Gradient(colors: [AllInColors.bleue200, AllInColors.purple200]),
gradient: Gradient(colors: [AllInColors.blue200, AllInColors.purple200]),
startPoint: .leading,
endPoint: .trailing
)
static let BlueBetLineGradiant = LinearGradient(
gradient: Gradient(colors: [AllInColors.blue200, AllInColors.blueAccentColor]),
startPoint: .leading,
endPoint: .trailing
)

@ -0,0 +1,194 @@
/*
Localizable.strings
AllInApp
Created by Emre on 15/05/2024.
*/
/// Core
"generic_username" = "Username";
"generic_email" = "Email";
"generic_password" = "Password";
"generic_login" = "Login";
"generic_logout" = "Logout";
"generic_already_have_account" = "Already have an account ?";
"generic_register" = "Register";
"generic_validate" = "Validate";
"generic_ok" = "OK";
"generic_error" = "Error";
"generic_in_waiting" = "In waiting";
"generic_search" = "Search";
"generic_add" = "Add";
"generic_acccept"="Accept";
"generic_decline"="Decline";
"generic_delete" = "Delete";
"generic_stake" = "Stake";
"network_error_text" = "Make sure to be properly connected to the network then try again.";
/// Bet type
"bet_type_binary" = "Yes / No";
"bet_type_match" = "Sport match";
"bet_type_custom" = "Custom answers";
/// Field error
"field_error_mandatory" = "This field is mandatory.";
"field_error_not_identical" = "The fields are not identical.";
"field_error_bad_email" = "Please enter a valid email.";
/// Drawer
"drawer_bets" = "Bets";
"drawer_best_win" = "Best win";
"drawer_nb_friends" = "Friends";
"drawer_friends" = "FRIENDS";
"drawer_friends_subtitle" = "Challenge your folks by adding them as friends.";
"drawer_public_bets" = "PARTICIPATE";
"drawer_public_bets_subtitle" = "Browse the most popular bets of the moment.";
"drawer_create_a_bet" = "NEW BET";
"drawer_create_a_bet_subtitle" = "Create a net bet and get your friends participating.";
"drawer_bet_history" = "HISTORY";
"drawer_bet_history_subtitle" = "View your current and finished bets.";
"drawer_current_bets" = "MY PARTICIPATIONS";
"drawer_current_bets_subtitle" = "Manage your bets and reward the winners.";
"drawer_ranking" = "RANKING";
"drawer_ranking_subtitle" = "Check your ranking among your friends.";
/// Welcome Page
"welcome_title" = "Welcome to,";
"welcome_subtitle" = "Collect your Allcoins and come bet with your friend to prove who\'s best.";
"welcome_join" = "Join";
/// Register Page
"register_hello" = "Hello,";
"register_hello %@" = "Hello %@,";
"register_title" = "We need this!";
"register_subtitle" = "Don\'t worry it\'s fast.";
"register_error_title" = "Error saving";
"register_error_content" = "Registration failed. Try Again.";
"register_confirm_password" = "Confirm password";
"register_already_used" = "Email or nickname already used.";
/// Login Page
"login_title" = "Welcome back !";
"login_subtitle" = "We missed you.";
"login_error_title" = "Connection error";
"login_error_content" = "Failed to login. Please try again.";
"login_no_account" = "Don't have an account ?";
"login_forgot_password" = "Forgot password ?";
"login_bad_credentials" = "Incorrect login or password.";
/// Bet Creation Page
"bet_creation_theme_tooltip" = "Usually a common name describing the overall theme of the bet for reference..";
"bet_creation_phrase_tooltip" = "Generally the question the participants will answer to.";
"bet_creation_register_tooltip" = "After this date, nobody will be able to register anymore.";
"bet_creation_bet_end_tooltip" = "After this date, the result will be announced.";
"bet_creation_privacy_tooltip" = "Determines who will be able to see the bet.";
"bet_creation_question" = "Question";
"bet_creation_answer" = "Answer";
"bet_creation_publish" = "Publish the bet";
"bet_creation_theme" = "Theme";
"bet_creation_theme_placeholder" = "Studies, sports, party…";
"bet_creation_bet_phrase" = "Bet phrase";
"bet_creation_bet_phrase_placeholder" = "Will David be missing this Monday in class ?";
"bet_creation_end_registration_date" = "Registration end date";
"bet_creation_end_bet_date" = "Bet end date";
"bet_creation_bet_privacy" = "Bet privacy";
"bet_creation_friends_available_format" = "friends available";
"bet_creation_public_bottom_text_1" = "Your BET will be visible by all the users.";
"bet_creation_public_bottom_text_2" = "Everyone will be able to join the BET.";
"bet_creation_private_bottom_text_1" = "Your BET will only be visible by your friends.";
"bet_creation_private_bottom_text_2" = "Only your friends will be able to join the BET.";
"bet_creation_bottom_text_3" = "You can invite friends at any moment during the registration period.";
"bet_creation_yes_no_bottom_text_1" = "The participants will have to respond with either YES or NO.";
"bet_creation_yes_no_bottom_text_2" = "No other answer will be accepted.";
"bet_creation_custom_bottom_text_1" = "You are going to fill in the different answers available in this bet.";
"bet_creation_custom_bottom_text_2" = "Be careful to be clear and avoid any uncertainties";
"bet_creation_error" = "Error while creating the bet.";
"bet_creation_success_message" = "Bet created !";
"bet_creation_response_title" = "Response title";
"bet_creation_max_answers %lld" = "%lld more max.";
"bet_creation_error_past_date" = "The registration end date must be later than the current date.";
"bet_creation_error_date_order" = "The betting end date must be later than the registration end date.";
/// Bet page
"bet_popular" = "Popular";
"bet_public" = "Public";
"bet_private" = "Private";
"bet_invitation" = "Invitation";
"bet_current" = "Current";
"bet_finished" = "Finished";
"bet_starting" = "Starting on";
"bet_started" = "Started on";
"bet_ends" = "Ends on";
"bet_ended" = "Ended on";
"bet_participate" = "Participate";
"bet_proposed_by_format %@" = "Proposed by %@";
"bet_proposed_by_format" = "Proposed by";
"bet_players_waiting_format %@" = "%@ players waiting";
"bet_players_format" = "players";
"bet_points_at_stake_format" = "points at stake";
/// Bet status
"bet_status_finished" = "Finished !";
"bet_status_in_progress" = "In progress…";
"bet_status_waiting" = "Waiting…";
"bet_status_unavailable" = "Status unavailable";
"bet_status_cancelled" = "Cancelled";
"bet_status_place_your_bets" = "Place your bets";
"bet_status_participants_list" = "Participants";
"bet_status_details_drawer" = "Details";
"participation_possible_winnings" = "Possible winnings";
"bet_status_stake" = "Gambled";
/// Bet history
"bet_history_current_title" = "Current";
"bet_history_title" = "History";
/// Bet confirmation
"bet_confirmation_text" = "This bet has now ended. You may now distribute the winnings by selecting the right answer.";
"bet_confirmation_choose_response" = "Please select the final answer :";
/// Ranking
"ranking_title" = "Ranking";
/// Friends
"friends_title" = "Friends";
"friends_request_sent" = "Request sent";
"friends_request"= "Friends Request";
/// Daily reward
"daily_reward_title" = "Daily reward";
"daily_reward_subtitle" = "Your daily reward is unlocked every day at 00:00 UTC and allows you to get between 10 and 150 Allcoins.";
/// Notification
"notification_title_end_bet_date" = "Who will be the winners?";
"notification_subtitle_end_bet_date %@" = "The %@ bet has reached its deadline. Go to the app to enter the winning answer.";
/// Empty Views
"empty_ranking_title" = "It's a bit empty around here";
"empty_ranking_explain" = "Add some friends to display them in the leaderboard";
"empty_bets_title"= "No bet matches your search";
"empty_friends_title" = "You don't have any friends yet";
"empty_friends_explain" = "Add them from this screen";
/// Error Messages
"error_title" = "Error: Failed to Load Content.";
"error_message" = "The content you are trying to load could not be retrieved. Please check your internet connection and try again";

@ -0,0 +1,194 @@
/*
Localizable.strings
AllInApp
Created by Emre on 15/05/2024.
*/
/// Core
"generic_username" = "Pseudo";
"generic_email" = "Email";
"generic_password" = "Mot de passe";
"generic_login" = "Se connecter";
"generic_logout" = "Déconnexion";
"generic_already_have_account" = "Tu as déjà un compte ?";
"generic_register" = "S'inscrire";
"generic_validate" = "Valider";
"generic_ok" = "OK";
"generic_error" = "Erreur";
"generic_in_waiting" = "En attente";
"generic_search" = "Rechercher";
"generic_add" = "Ajouter";
"generic_acccept"="Accepter";
"generic_decline"="Decliner";
"generic_delete" = "Supprimer";
"generic_stake" = "Mise";
"network_error_text" = "Assurez-vous d\'être bien connecté au réseau puis réessayez.";
/// Bet type
"bet_type_binary" = "Oui / Non";
"bet_type_match" = "Match sportif";
"bet_type_custom" = "Réponses personnalisées";
/// Field error
"field_error_mandatory" = "Ce champ est obligatoire.";
"field_error_not_identical" = "Les champs ne sont pas identiques.";
"field_error_bad_email" = "Veuillez saisir un email valide.";
/// Drawer
"drawer_bets" = "Bets";
"drawer_best_win" = "Meilleur gain";
"drawer_nb_friends" = "Amis";
"drawer_friends" = "AMIS";
"drawer_friends_subtitle" = "Défiez vos proches en les ajoutant en amis.";
"drawer_public_bets" = "PARTICIPER";
"drawer_public_bets_subtitle" = "Parcourez les bets les plus populaires du moment.";
"drawer_create_a_bet" = "NOUVEAU BET";
"drawer_create_a_bet_subtitle" = "Créez un nouveau bet et faites participer vos amis.";
"drawer_bet_history" = "HISTORIQUE";
"drawer_bet_history_subtitle" = "Consultez vos paris en cours et terminés.";
"drawer_current_bets" = "MES PARTICIPATIONS";
"drawer_current_bets_subtitle" = "Gérez vos bets et récompensez les gagnants.";
"drawer_ranking" = "CLASSEMENT";
"drawer_ranking_subtitle" = "Consultez votre classement parmis vos amis.";
/// Welcome Page
"welcome_title" = "Bienvenue sur,";
"welcome_subtitle" = "Récupère tes Allcoins et viens parier avec tes amis pour prouver qui est le meilleur.";
"welcome_join" = "Rejoindre";
/// Register Page
"register_hello" = "Bonjour";
"register_hello %@" = "Bonjour %@,";
"register_title" = "On a besoins de ça!";
"register_subtitle" = "Promis c\'est rapide.";
"register_error_title" = "Erreur lors de l'enregistrement";
"register_error_content" = "L'enregistrement a échoué. Veuillez réessayer.";
"register_confirm_password" = "Confirmation du mot de passe";
"register_already_used" = "Email ou pseudo déjà utilisé.";
/// Login Page
"login_title" = "Te revoilà !";
"login_subtitle" = "Bon retour parmis nous tu nous as manqué !";
"login_error_title" = "Erreur de connexion";
"login_error_content" = "La connexion a échoué. Veuillez réessayer.";
"login_no_account" = "Pas encore inscrit ?";
"login_forgot_password" = "Mot de passe oublié ?";
"login_bad_credentials" = "Login ou mot de passe incorrects.";
/// Bet Creation Page
"bet_creation_theme_tooltip" = "Généralement un nom commun décrivant le thème global du pari pour servir de référence.";
"bet_creation_phrase_tooltip" = "Généralement la question à laquelle les participants devront répondre.";
"bet_creation_register_tooltip" = "Après cette date, plus personne ne pourra s\'inscrire.";
"bet_creation_bet_end_tooltip" = "Après cette date, les résultats seront annoncés.";
"bet_creation_privacy_tooltip" = "Détermine qui pourra voir le bet.";
"bet_creation_question" = "Question";
"bet_creation_answer" = "Réponse";
"bet_creation_publish" = "Publier le bet";
"bet_creation_theme" = "Thème";
"bet_creation_theme_placeholder" = "Études, sport, soirée…";
"bet_creation_bet_phrase" = "Phrase du BET";
"bet_creation_bet_phrase_placeholder" = "David sera-il absent lundi matin en cours ?";
"bet_creation_end_registration_date" = "Date de fin des inscriptions";
"bet_creation_end_bet_date" = "Date de fin du BET";
"bet_creation_bet_privacy" = "Confidentialité du bet";
"bet_creation_friends_available_format" = "amis disponibles";
"bet_creation_public_bottom_text_1" = "Votre BET sera visible par tous les utilisateurs.";
"bet_creation_public_bottom_text_2" = "Tout le monde pourra rejoindre le BET.";
"bet_creation_private_bottom_text_1" = "Votre BET sera visible uniquement par vos amis.";
"bet_creation_private_bottom_text_2" = "Seulement vos amis pourront rejoindre le BET.";
"bet_creation_bottom_text_3" = "Vous pourrez inviter des amis à tout moment pendant la période dinscription.";
"bet_creation_yes_no_bottom_text_1" = "Les utilisateurs devront répondre au pari avec OUI ou NON.";
"bet_creation_yes_no_bottom_text_2" = "Aucune autre réponse ne sera acceptée.";
"bet_creation_custom_bottom_text_1" = "Vous allez renseigner les différentes réponses disponibles dans ce pari.";
"bet_creation_custom_bottom_text_2" = "Faites attention a etre claire et éviter toutes incertitudes";
"bet_creation_error" = "Erreur lors de la création du bet.";
"bet_creation_success_message" = "Bet créé !";
"bet_creation_response_title" = "Intitulé de réponse";
"bet_creation_max_answers %lld" = "encore %lld max.";
"bet_creation_error_past_date" = "La date de fin des inscriptions doit être ultérieure à la date actuelle.";
"bet_creation_error_date_order" = "La date de fin des paris doit être ultérieure à la date de fin des inscriptions.";
/// Bet page
"bet_popular" = "Populaire";
"bet_public" = "Public";
"bet_private" = "Privé";
"bet_invitation" = "Invitation";
"bet_current" = "En cours";
"bet_finished" = "Terminés";
"bet_starting" = "Commence le";
"bet_started" = "A commencé le";
"bet_ends" = "Prend fin le";
"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 %@" = "%@ joueurs en attente";
"bet_players_format" = "joueurs";
"bet_points_at_stake_format" = "points en jeu";
/// Bet status
"bet_status_finished" = "Terminé !";
"bet_status_in_progress" = "En cours…";
"bet_status_waiting" = "En attente…";
"bet_status_unavailable" = "Statut indisponible";
"bet_status_cancelled" = "Annulé";
"bet_status_place_your_bets" = "Faites vos paris";
"bet_status_participants_list" = "Liste des participants";
"bet_status_details_drawer" = "Détails";
"participation_possible_winnings" = "Gains possibles";
"bet_status_stake" = "Pariés";
/// Bet history
"bet_history_current_title" = "En cours";
"bet_history_title" = "Historique";
/// Bet confirmation
"bet_confirmation_text" = "Ce bet est arrivé à la date de fin. Vous devez à présent distribuer les gains en validant le pari gagnant.";
"bet_confirmation_choose_response" = "Veuillez choisir la réponse finale :";
/// Ranking
"ranking_title" = "Classement";
/// Friends
"friends_title" = "Amis";
"friends_request_sent" = "Requête envoyée";
"friends_request"= "Requêtes d'amis";
/// Daily reward
"daily_reward_title" = "Récompense quotidienne";
"daily_reward_subtitle" = "Votre récompense quotidienne est débloquée tous les jours à 00:00 UTC et vous permets dobtenir entre 10 et 150 Allcoins.";
/// Notification
"notification_title_end_bet_date" = "Qui seront les vainqueurs ?";
"notification_subtitle_end_bet_date %@" = "Le pari %@ a atteint sa date limite. Rendez-vous dans l'application pour renseigner la réponse gagnante.";
/// Empty Views
"empty_ranking_title" = "Cest un peu vide par ici";
"empty_ranking_explain" = "Ajoutez des amis pour les afficher dans le classement";
"empty_bets_title"= "Aucun Bet ne correspond à votre recherche";
"empty_friends_title" = "Vous navez pas encore damis";
"empty_friends_explain" = "Ajoutez les depuis cet écran";
/// Error Messages
"error_title" = "Erreur : Échec du chargement du contenu.";
"error_message" = "Le contenu que vous essayez de charger n'a pas pu être récupéré. Veuillez vérifier votre connexion internet et réessayer.";

@ -10,6 +10,7 @@ import Model
import DependencyInjection
import Api
import StubLib
import WidgetKit
class AuthService: IAuthService {
@ -19,12 +20,12 @@ class AuthService: IAuthService {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let json = [
"login": login.lowercased(),
"password": password,
]
if let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []) {
URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in
print("ALLIN: Process LOGIN")
@ -52,10 +53,10 @@ class AuthService: IAuthService {
}.resume()
}
}
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)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
@ -70,6 +71,7 @@ class AuthService: IAuthService {
URLSession.shared.uploadTask(with: request, from: jsonData) { data, response, error in
print ("ALLIN : Process REGISTER")
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
if httpResponse.statusCode == 201 {
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
@ -98,7 +100,7 @@ class AuthService: IAuthService {
guard let token = AppStateContainer.shared.authenticationRefresh else {
return
}
let url = URL(string: Config.allInApi + "users/token")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
@ -108,13 +110,11 @@ class AuthService: IAuthService {
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
if let userJson = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let user = User.mapUser(from: userJson) {
AppStateContainer.shared.user = user
AppStateContainer.shared.loggedState.connectedUser = true
self.initManagerVM(token: token)
}
if httpResponse.statusCode == 200, let user = try? JSONDecoder().decode(User.self, from: data) {
AppStateContainer.shared.user = user
AppStateContainer.shared.loggedState.connectedUser = true
WidgetCenter.shared.reloadAllTimelines()
self.initManagerVM(token: token)
} else {
AppStateContainer.shared.authenticationRefresh = nil
}
@ -130,16 +130,14 @@ class AuthService: IAuthService {
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
if let userJson = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let user = User.mapUser(from: userJson) {
completion(httpResponse.statusCode)
AppStateContainer.shared.user = user
}
if httpResponse.statusCode == 200, let user = try? JSONDecoder().decode(User.self, from: data) {
AppStateContainer.shared.user = user
WidgetCenter.shared.reloadAllTimelines()
completion(httpResponse.statusCode)
} else {
completion(httpResponse.statusCode)
}
@ -150,12 +148,14 @@ class AuthService: IAuthService {
}
private func initManagerVM(token: String) {
DependencyInjection.shared.addSingleton(Manager.self, Manager(withBetDataManager: BetApiManager(withUserToken: token), withUserDataManager: UserApiManager(withUserToken: token)))
DependencyInjection.shared.addSingleton(Manager.self, Manager(withBetDataManager: BetApiManager(withUserToken: token, andApiUrl: Config.allInApi), withUserDataManager: UserApiManager(withUserToken: token, andApiUrl: Config.allInApi)))
}
public func logout() {
AppStateContainer.shared.authenticationRefresh = nil
AppStateContainer.shared.loggedState.connectedUser = false
AppStateContainer.shared.notificationState.removeAllNotifications()
WidgetCenter.shared.reloadAllTimelines()
}
}

@ -0,0 +1,50 @@
//
// NotificationService.swift
// AllIn
//
// Created by Emre on 22/02/2024.
//
import UserNotifications
struct NotificationItem {
var title: String
var content: String
var interval: TimeInterval
}
class NotificationService: ObservableObject {
@Published var notifications: [NotificationItem] = []
func scheduleNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
print("Permission for notifications granted.")
} else {
print("Permission for notifications denied.")
}
}
}
func loadNotifications() {
for notification in notifications {
scheduleNotification(with: notification)
}
}
func removeAllNotifications() {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
}
func scheduleNotification(with item: NotificationItem) {
let content = UNMutableNotificationContent()
content.title = item.title
content.body = item.content
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: item.interval, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
}

@ -0,0 +1,71 @@
//
// asyncCachedImage.swift
// AllIn
//
// Created by Lucas Delanier on 06/06/2024.
//
import SwiftUI
@MainActor
struct AsyncCachedImage<ImageView: View, PlaceholderView: View>: View {
// Input dependencies
var url: URL?
@ViewBuilder var content: (Image) -> ImageView
@ViewBuilder var placeholder: () -> PlaceholderView
// Downloaded image
@State var image: UIImage? = nil
init(
url: URL?,
@ViewBuilder content: @escaping (Image) -> ImageView,
@ViewBuilder placeholder: @escaping () -> PlaceholderView
) {
self.url = url
self.content = content
self.placeholder = placeholder
}
var body: some View {
VStack {
if let uiImage = image {
content(Image(uiImage: uiImage))
} else {
placeholder()
.onAppear {
Task {
image = await downloadPhoto()
}
}
}
}
}
// Downloads if the image is not cached already
// Otherwise returns from the cache
private func downloadPhoto() async -> UIImage? {
do {
guard let url else { return nil }
// Check if the image is cached already
if let cachedResponse = URLCache.shared.cachedResponse(for: .init(url: url)) {
return UIImage(data: cachedResponse.data)
} else {
let (data, response) = try await URLSession.shared.data(from: url)
// Save returned image data into the cache
URLCache.shared.storeCachedResponse(.init(response: response, data: data), for: .init(url: url))
guard let image = UIImage(data: data) else {
return nil
}
return image
}
} catch {
print("Error downloading: \(error)")
return nil
}
}
}

@ -0,0 +1,27 @@
//
// BetEndingValidationViewModel.swift
// AllIn
//
// Created by Emre on 07/02/2024.
//
import Foundation
import DependencyInjection
import Model
class BetEndingValidationViewModel: ObservableObject {
var id: String
@Inject var manager: Manager
@Published var selectedAnswer : String?
init(id: String) {
self.id = id
}
func post() {
if let answer = selectedAnswer {
manager.addResponse(withIdBet: id, andResponse: answer)
}
}
}

@ -13,18 +13,62 @@ import Combine
class BetViewModel: ObservableObject {
@Inject var manager: Manager
@Inject var authService: IAuthService
@Published var popularBet: Bet?
@Published private(set) var bets: [Bet] = []
@Published var betsOver: [BetDetail] = []
@Published var betsWon: [BetResultDetail] = []
@Published var showingSheetOver: Bool = false
@Published var showingSheetWon: Bool = false
@Published var filters: Set<BetFilter> = [] {
didSet {
getItems()
}
}
init() {
getItems()
getPopularBet()
getBetsOver()
getBetsWon()
}
func getItems() {
manager.getBets(withIndex: 0, withCount: 20) { bets in
self.bets = bets
manager.getBets(withIndex: 0, withCount: 20, filters: Array(filters)) { bets in
DispatchQueue.main.async {
self.bets = bets
}
}
}
func getBetsOver() {
manager.getBetsOver() { bets in
DispatchQueue.main.async {
self.betsOver = bets
if !self.betsOver.isEmpty {
self.showingSheetOver = true
}
}
}
}
func getBetsWon() {
manager.getBetsWon() { bets in
DispatchQueue.main.async {
self.betsWon = bets
if !self.betsWon.isEmpty {
self.showingSheetWon = true
self.authService.refreshAuthentication()
}
}
}
}
private var cancellables: Set<AnyCancellable> = []
func getPopularBet() {
manager.getPopularBet() { bet in
DispatchQueue.main.async {
self.popularBet = bet
}
}
}
}

@ -15,64 +15,127 @@ class CreationBetViewModel: ObservableObject {
@Inject var manager: Manager
@Published var theme: String = ""
@Published var description: String = ""
@Published var isPublic = true
@Published var isPrivate = false {
didSet {
invited.removeAll()
}
}
@Published var endRegisterDate = Date()
@Published var endBetDate = Date()
@Published var betAdded = false
@Published var selectedTypeBet = 0 {
didSet {
values.removeAll()
groupedItems.removeAll()
response = ""
}
}
@Published var values: [String] = []
@Published var invited: Set<String> = []
@Published var themeFieldError: String?
@Published var descriptionFieldError: String?
@Published var endRegisterDateFieldError: String?
@Published var endBetDateFieldError: String?
@Published var responsesFieldError: String?
@Published var errorMessage: String?
@Published var showErrorMessage = false
@Published var friends: [User] = []
let options: [(Int, String, String)] = [
(0, "questionMarkIcon", String(localized: "bet_type_binary")),
(1, "footballIcon", String(localized: "bet_type_match")),
(2, "paintbrushIcon", String(localized: "bet_type_custom"))
]
@Published var response = ""
@Published var groupedItems: [[String]] = [[String]] ()
init() {
getFriends()
}
func getFriends() {
manager.getFriends() { friends in
DispatchQueue.main.async {
self.friends = friends
}
}
}
func create() {
guard checkAndSetError(forTheme: true, forDescription: true, forEndRegisterDate: true, forEndBetDate: true) else {
guard checkAndSetError(forTheme: true, forDescription: true, forEndRegisterDate: true, forEndBetDate: true, forResponse: true) else {
return
}
resetAllFieldErrors()
if let user = AppStateContainer.shared.user {
manager.addBet(theme: theme, description: description, endRegister: endRegisterDate, endBet: endBetDate, isPublic: isPublic, creator: user)
manager.addBet(bet: toBet(theme: theme, description: description, endRegister: endRegisterDate, endBet: endBetDate, isPrivate: isPrivate, status: .inProgress, creator: user.username, invited: Array(invited), type: selectedTypeBet)) { statusCode in
print(statusCode)
switch statusCode {
case 201:
self.betAdded = true
let notificationItem = NotificationItem(title: String(localized: "notification_title_end_bet_date"), content: String(localized: "notification_subtitle_end_bet_date \(self.theme)"), interval: self.endBetDate.timeIntervalSinceNow)
AppStateContainer.shared.notificationState.scheduleNotification(with: notificationItem)
default:
self.setErrorMessage(errorMessage: String(localized: "network_error_text"))
}
}
}
betAdded = true
}
func checkAndSetError(forTheme checkTheme: Bool, forDescription checkDescription: Bool, forEndRegisterDate checkEndRegisterDate: Bool, forEndBetDate checkEndBetDate: Bool) -> Bool {
func checkAndSetError(forTheme checkTheme: Bool, forDescription checkDescription: Bool, forEndRegisterDate checkEndRegisterDate: Bool, forEndBetDate checkEndBetDate: Bool, forResponse checkResponse: Bool) -> Bool {
var newThemeFieldError: String?
var newDescriptionFieldError: String?
var newEndRegisterDateFieldError: String?
var newEndBetDateFieldError: String?
var newResponsesFieldError: String?
var hasError = false
// Theme
if checkTheme, theme.isEmpty {
newThemeFieldError = "Veuillez saisir le thème."
newThemeFieldError = String(localized: "field_error_mandatory")
hasError = true
}
// Description
if checkDescription, description.isEmpty {
newDescriptionFieldError = "Veuillez saisir la description."
newDescriptionFieldError = String(localized: "field_error_mandatory")
hasError = true
}
// End Register Date
if checkEndRegisterDate, endRegisterDate < Date() {
newEndRegisterDateFieldError = "La date de fin des inscriptions doit être ultérieure à la date actuelle."
newEndRegisterDateFieldError = String(localized: "bet_creation_error_past_date")
hasError = true
}
// End Bet Date
if checkEndBetDate, endBetDate < endRegisterDate {
newEndBetDateFieldError = "La date de fin des paris doit être ultérieure à la date de fin des inscriptions."
newEndBetDateFieldError = String(localized: "bet_creation_error_date_order")
hasError = true
}
if checkResponse, selectedTypeBet == 2 {
if values.count < 2 {
newResponsesFieldError = "Il doit y'avoir 2 réponses minimum"
hasError = true
} else {
let uniqueValues = Set(values)
if uniqueValues.count != values.count {
newResponsesFieldError = "Les réponses doivent être uniques"
hasError = true
}
}
}
if !hasError {
// No error
return true
@ -83,6 +146,7 @@ class CreationBetViewModel: ObservableObject {
descriptionFieldError = newDescriptionFieldError
endRegisterDateFieldError = newEndRegisterDateFieldError
endBetDateFieldError = newEndBetDateFieldError
responsesFieldError = newResponsesFieldError
}
return false
}
@ -93,6 +157,29 @@ class CreationBetViewModel: ObservableObject {
descriptionFieldError = nil
endRegisterDateFieldError = nil
endBetDateFieldError = nil
responsesFieldError = nil
}
}
func setErrorMessage(errorMessage: String) {
self.showErrorMessage = true
self.errorMessage = errorMessage
}
func toBet(theme: String, description: String, endRegister: Date, endBet: Date, isPrivate: Bool, status: BetStatus, creator: String, invited: [String], type: Int) -> Bet {
switch type {
case 0:
return BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator)
case 1:
return MatchBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator, nameTeam1: "", nameTeam2: "")
case 2:
var bet = CustomBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator)
for answer in values {
bet.addCustomResponse(answer)
}
return bet
default:
return BinaryBet(theme: theme, phrase: description, endRegisterDate: endRegister, endBetDate: endBet, isPrivate: isPrivate, status: status, invited: invited, author: creator)
}
}
}

@ -0,0 +1,30 @@
//
// CurrentBetViewModel.swift
// AllIn
//
// Created by Emre on 31/01/2024.
//
import Foundation
import DependencyInjection
import Model
class CurrentBetViewModel: ObservableObject {
@Inject var manager: Manager
@Published private(set) var bets: [BetDetail] = []
init() {
getItems()
}
func getItems() {
manager.getCurrentBets(withIndex: 0, withCount: 20) { bets in
DispatchQueue.main.async {
self.bets = bets
}
}
}
}

@ -9,14 +9,15 @@ import Foundation
import SwiftUI
import DependencyInjection
import Model
import WidgetKit
class DetailsViewModel: ObservableObject {
@Inject var manager: Manager
var id: String
@Published var answer = 0
@Published var selectedAnswer = AnswerDetail(response: "", totalStakes: 0, totalParticipants: 0, highestStake: 0, odds: 0)
@Published var mise: String = ""
@Published var betDetail: BetDetail?
init(id: String) {
@ -25,20 +26,52 @@ class DetailsViewModel: ObservableObject {
}
func getItem(withId id: String) {
let semaphore = DispatchSemaphore(value: 0)
manager.getBet(withId: id) { bet in
self.betDetail = bet
self.betDetail = bet
if let firstAnswer = bet.answers.first {
self.selectedAnswer = firstAnswer
}
semaphore.signal()
}
let result = semaphore.wait(timeout: DispatchTime.now() + .seconds(2))
if result == .timedOut {
print("The request has exceeded the deadline")
return
}
}
func addParticipate() {
if let stake = Int(mise) {
var rep: String = ""
if answer == 0 {
rep = "Yes"
manager.addParticipation(withId: id, withAnswer: selectedAnswer.response, andStake: stake) { statusCode in
switch statusCode {
case 201:
AppStateContainer.shared.user?.nbCoins -= stake
WidgetCenter.shared.reloadAllTimelines()
self.getItem(withId: self.id)
default:
break
}
}
}
mise = ""
if let firstAnswer = betDetail!.answers.first {
self.selectedAnswer = firstAnswer
}
}
func checkAndSetError() -> Bool {
if let stake = Int(mise) {
if stake <= AppStateContainer.shared.user?.nbCoins ?? 0 && stake > 0 {
return false
} else {
rep = "No"
return true
}
manager.addParticipation(withId: id, withAnswer: rep, andStake: stake)
} else {
return true
}
}
}

@ -13,23 +13,101 @@ class FriendsViewModel: ObservableObject {
@Inject var manager: Manager
@Published private(set) var users: [User] = []
@Published private(set) var requests: [User] = []
@Published var text: String = "" {
didSet {
if text.isEmpty {
getItems()
} else {
search()
}
}
}
init() {
getItems()
getRequests()
}
func getItems ( ) {
func getItems() {
manager.getFriends() { friends in
DispatchQueue.main.async {
self.users = friends
}
}
}
func deleteItem(indexSet: IndexSet) {
func getRequests() {
manager.getRequests() { friends in
DispatchQueue.main.async {
self.requests = friends
}
}
}
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, isRequest: Bool) {
let targetList = isRequest ? requests : users
guard let index = targetList.firstIndex(where: { $0.username == user.username }) else { return }
var updatedUser = targetList[index]
if isRequest {
if updatedUser.friendStatus == .requested {
updatedUser.friendStatus = .notFriend
deleteItem(username: user.username)
} else {
updatedUser.friendStatus = .friend
addItem(username: user.username)
}
requests.remove(at: index)
} else {
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 declineRequest(username: String){
guard let index = requests.firstIndex(where: { $0.username == username }) else { return }
manager.removeFriend(withUsername: username) { statusCode in
}
requests.remove(at: index)
}
func deleteItem(username: String) {
manager.removeFriend(withUsername: username) { statusCode in
}
}
func addItem(username: String) {
manager.addFriend(withUsername: username) { statusCode in
}
}
}

@ -6,19 +6,22 @@
//
import Foundation
import SwiftUI
import DependencyInjection
import Model
class HistoricBetViewModel: ObservableObject {
@Inject var manager: Manager
@Published private(set) var bets: [BetResultDetail] = []
init() {
getItems()
}
func getItems() {
manager.getHistoricBets(withIndex: 0, withCount: 20) { bets in
self.bets = bets
}
}
}

@ -36,10 +36,10 @@ class LoginViewModel: ObservableObject {
self.onLoginSuccess()
case 404:
AppStateContainer.shared.loggedState.connectedUser = false
self.setErrorMessage(errorMessage: "Login ou mot de passe incorrects.")
self.setErrorMessage(errorMessage: String(localized: "login_bad_credentials"))
default:
AppStateContainer.shared.loggedState.connectedUser = false
self.setErrorMessage(errorMessage: "La connexion a échoué. Veuillez réessayer.")
self.setErrorMessage(errorMessage: String(localized: "login_error_content"))
}
}
}
@ -55,13 +55,13 @@ class LoginViewModel: ObservableObject {
// Login
if checkLogin, loginIdentifier.isEmpty {
newLoginIdentifierFieldError = "Veuillez saisir votre identifiant."
newLoginIdentifierFieldError = String(localized: "field_error_mandatory")
hasError = true
}
// Password
if checkPassword, loginPassword.isEmpty {
newLoginPasswordFieldError = "Veuillez saisir votre mot de passe."
newLoginPasswordFieldError = String(localized: "field_error_mandatory")
hasError = true
}

@ -13,23 +13,19 @@ 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!)
DispatchQueue.main.async {
self.friends = friends.sorted(by: { $0.nbCoins > $1.nbCoins })
}
}
}
}

@ -40,10 +40,10 @@ class RegisterViewModel: ObservableObject {
self.onRegisterSuccess()
case 409:
AppStateContainer.shared.loggedState.connectedUser = false
self.setErrorMessage(errorMessage: "Email ou pseudo déjà utilisé.")
self.setErrorMessage(errorMessage: String(localized: "register_already_used"))
default:
AppStateContainer.shared.loggedState.connectedUser = false
self.setErrorMessage(errorMessage: "La connexion a échoué. Veuillez réessayer.")
self.setErrorMessage(errorMessage: String(localized: "register_error_content"))
}
}
}
@ -61,35 +61,33 @@ class RegisterViewModel: ObservableObject {
// Username
if checkUsername, registerUsername.isEmpty {
newRegisterUsernameFieldError = "Veuillez saisir votre pseudo."
newRegisterUsernameFieldError = String(localized: "field_error_mandatory")
hasError = true
}
// Email
if checkEmail, registerEmail.isEmpty {
newRegisterEmailFieldError = "Veuillez saisir votre email."
newRegisterEmailFieldError = String(localized: "field_error_mandatory")
hasError = true
}
if checkEmail, isValidEmail(email: registerEmail) {
newRegisterEmailFieldError = "Veuillez saisir un email valide."
} else if checkEmail, isValidEmail(email: registerEmail) {
newRegisterEmailFieldError = String(localized: "field_error_bad_email")
hasError = true
}
// Password
if checkPassword, registerPassword.isEmpty {
newRegisterPasswordFieldError = "Veuillez saisir votre mot de passe."
newRegisterPasswordFieldError = String(localized: "field_error_mandatory")
hasError = true
}
// Confirm password
if checkConfirmPassword, registerConfirmPassword.isEmpty {
newRegisterConfirmPasswordFieldError = "Veuillez confirmer votre mot de passe."
newRegisterConfirmPasswordFieldError = String(localized: "field_error_mandatory")
hasError = true
}
if checkConfirmPassword, registerConfirmPassword != registerPassword {
newRegisterConfirmPasswordFieldError = "Les mots de passe ne sont pas identiques."
newRegisterConfirmPasswordFieldError = String(localized: "field_error_not_identical")
hasError = true
}
@ -104,6 +102,7 @@ class RegisterViewModel: ObservableObject {
registerPasswordFieldError = newRegisterPasswordFieldError
registerConfirmPasswordFieldError = newRegisterConfirmPasswordFieldError
}
return false
}

@ -0,0 +1,103 @@
//
// BetEndingValidationView.swift
// AllIn
//
// Created by Lucas Delanier on 29/01/2024.
//
import Foundation
import SwiftUI
import Model
import StubLib
struct BetEndingValidationView: View {
@Environment(\.dismiss) var dismiss
@StateObject private var viewModel: BetEndingValidationViewModel
var betDetail: BetDetail
init(bet: BetDetail) {
self.betDetail = bet
self._viewModel = StateObject(wrappedValue: BetEndingValidationViewModel(id: bet.bet.id))
}
var body: some View {
ZStack{
GeometryReader { geometry in
InfiniteScroller(contentWidth: geometry.size.width) {
Image("marquee")
.resizable()
.scaledToFit()
}
VStack {
ZStack(alignment: .topLeading){
HStack{
Spacer()
Image("allinIcon")
.resizable()
.frame(width: 35, height: 35)
Spacer()
}
Image("crossIcon")
.resizable()
.frame(width: 25, height: 25)
.onTapGesture {
dismiss()
}
}
ReviewCard(bet: betDetail.bet, amount: 0, isWin: false)
.padding(.top, 20)
.padding(.bottom, 10)
Text("bet_confirmation_text")
.textStyle(weight: .regular, color: AllInColors.grey800Color, size: 13)
.multilineTextAlignment(.center)
Text("bet_confirmation_choose_response")
.font(.system(size: 17))
.foregroundStyle(.white)
.fontWeight(.bold)
.padding(.top, 30)
.padding(.bottom, 10)
.frame(maxWidth: .infinity, alignment: .leading)
VStack(spacing: 14){
ForEach(betDetail.answers) { answer in
ChoiceFinalAnswerCell(selected : answer.response == viewModel.selectedAnswer, answer: answer).onTapGesture {
if(viewModel.selectedAnswer == answer.response){
viewModel.selectedAnswer = nil
}
else {
viewModel.selectedAnswer = answer.response
}
}
}
}
Spacer()
Button {
dismiss()
viewModel.post()
} label: {
Text("generic_validate")
.font(.system(size: 23))
.foregroundColor(.white)
.fontWeight(.bold)
.frame(maxWidth: .infinity)
.padding(.vertical, 3)
}
.opacity(viewModel.selectedAnswer != nil ? 1 : 0)
.animation(.easeInOut(duration: 0.3), value: viewModel.selectedAnswer != nil)
.buttonStyle(.borderedProminent)
.tint(AllInColors.purpleAccentColor)
}
.padding([.all],20)
}
}.background(AllInColors.greyDarkColor)
}
}

@ -12,24 +12,29 @@ struct BetView: View {
@StateObject private var viewModel = BetViewModel()
@Binding var showMenu: Bool
@State var showingSheet: Bool = false
var body: some View {
VStack(alignment: .center, spacing: 0) {
TopBar(showMenu: self.$showMenu)
ScrollView(showsIndicators: false) {
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
LazyVStack(alignment: .center, spacing: 0, pinnedViews: [.sectionHeaders]) {
TrendingBetCard().padding(.top,25).padding([.leading,.trailing],25)
if let bet = viewModel.popularBet {
TrendingBetCard(bet: bet)
.padding(.top,25)
.padding([.leading,.trailing],25)
}
Section {
VStack(spacing: 20){
ForEach(viewModel.bets, id: \.id) { (bet: Bet) in
BetCard(bet: bet)
if(viewModel.bets.isEmpty){
EmptyInfo(emoji:"🎮", title: String(localized: "empty_bets_title"), explain: "")
}
Button("Show Sheet") {
showingSheet.toggle()
else{
ForEach(viewModel.bets, id: \.id) { (bet: Bet) in
BetCard(bet: bet)
}
}
}
.padding([.leading,.trailing],25)
@ -39,13 +44,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)
@ -57,21 +59,25 @@ struct BetView: View {
.refreshable {
viewModel.getItems()
}
.sheet(isPresented: $showingSheet) {
WinModal()
.sheet(isPresented: $viewModel.showingSheetOver, onDismiss: {
viewModel.betsOver.removeFirst()
viewModel.showingSheetOver = !viewModel.betsOver.isEmpty
}) {
if let firstBetDetail = viewModel.betsOver.first {
BetEndingValidationView(bet: firstBetDetail)
}
}
.sheet(isPresented: $viewModel.showingSheetWon, onDismiss: {
viewModel.betsWon.removeFirst()
viewModel.showingSheetWon = !viewModel.betsWon.isEmpty
}) {
if let firstBetResultDetail = viewModel.betsWon.first {
WinModal(betResult: firstBetResultDetail)
}
}
Spacer()
}
.edgesIgnoringSafeArea(.bottom)
.background(AllInColors.backgroundColor)
}
}
struct BetView_Previews: PreviewProvider {
static var previews: some View {
BetView(showMenu: .constant(false))
.preferredColorScheme(.light)
}
}

@ -28,18 +28,6 @@ struct CreationBetView: View {
}()
let screenWidth = UIScreen.main.bounds.width
@State private var response = ""
@State private var values: [String] = []
@State private var selectedOption = 0
let options: [(Int, String, String)] = [
(0, "questionMarkIcon", "Oui / Non"),
(1, "footballIcon", "Pari sportif"),
(2, "paintbrushIcon", "Réponses personnalisées")
]
@State var groupedItems: [[String]] = [[String]] ()
private func updateGroupedItems() {
var updatedGroupedItems: [[String]] = [[String]] ()
@ -47,7 +35,7 @@ struct CreationBetView: View {
var width: CGFloat = 0
var dynamicWidthLimit: CGFloat
for value in values {
for value in viewModel.values {
let label = UILabel()
label.text = value
label.sizeToFit()
@ -66,7 +54,7 @@ struct CreationBetView: View {
}
updatedGroupedItems.append(tempItems)
groupedItems = updatedGroupedItems
viewModel.groupedItems = updatedGroupedItems
}
var body: some View {
@ -82,7 +70,7 @@ struct CreationBetView: View {
VStack(spacing: 5) {
VStack() {
HStack(spacing: 5) {
Text("Thème")
Text("bet_creation_theme")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 17)
Image("questionMarkGreyIcon")
@ -92,7 +80,7 @@ struct CreationBetView: View {
showTitlePopover.toggle()
}
.allInPopover(isPresented: $showTitlePopover, paddingHorizontal: 20) {
"Généralement un nom commun décrivant le thème global du pari pour servir de référence."
String(localized: "bet_creation_theme_tooltip")
}
Spacer()
@ -105,10 +93,11 @@ struct CreationBetView: View {
Text(themeError)
.textStyle(weight: .bold, color: .red, size: 10)
}
TextField("", text: $viewModel.theme, prompt: Text("Études, sport, soirée...")
TextField("", text: $viewModel.theme, prompt: Text("bet_creation_theme_placeholder")
.foregroundColor(AllInColors.lightGrey300Color)
.font(.system(size: 14))
.fontWeight(.light))
.autocorrectionDisabled(true)
.padding()
.background(
RoundedRectangle(cornerRadius: 9)
@ -126,14 +115,14 @@ struct CreationBetView: View {
}
HStack(spacing: 5) {
Text("Phrase du BET")
Text("bet_creation_bet_phrase")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 17)
Image("questionMarkGreyIcon")
.resizable()
.frame(width: 14, height: 14)
.allInPopover(isPresented: $showDescriptionPopover, paddingHorizontal: 10) {
"Court descriptif du pari, souvent une question ouverte ou fermée."
String(localized: "bet_creation_phrase_tooltip")
}
Spacer()
@ -142,14 +131,17 @@ struct CreationBetView: View {
.padding(.leading, 10)
VStack {
if let descriptionError = $viewModel.descriptionFieldError.wrappedValue {
Text(descriptionError)
.textStyle(weight: .bold, color: .red, size: 10)
}
TextField("", text: $viewModel.description, prompt: Text("David sera absent Lundi matin en cours ?")
TextField("", text: $viewModel.description, prompt: Text("bet_creation_bet_phrase_placeholder")
.foregroundColor(AllInColors.lightGrey300Color)
.font(.system(size: 14))
.fontWeight(.light), axis: .vertical)
.autocorrectionDisabled(true)
.lineLimit(4, reservesSpace: true)
.padding()
.background(
@ -167,13 +159,13 @@ struct CreationBetView: View {
}
HStack(spacing: 5) {
Text("Date de fin des inscriptions")
Text("bet_creation_end_registration_date")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 17)
Image("questionMarkGreyIcon")
.resizable()
.frame(width: 14, height: 14)
.allInPopover(isPresented: $showRegistrationEndDatePopover) {
"Date de fin avant laquelle les joueurs peuvent s'inscrire en pariant leurs Allcoins."
String(localized: "bet_creation_register_tooltip")
}
Spacer()
@ -190,7 +182,7 @@ struct CreationBetView: View {
DatePicker(
"",
selection: $viewModel.endRegisterDate,
in: dateRange,
in: Date()...,
displayedComponents: [.date, .hourAndMinute]
)
.accentColor(AllInColors.lightPurpleColor)
@ -204,14 +196,14 @@ struct CreationBetView: View {
VStack(alignment: .leading, spacing: 5) {
VStack() {
HStack(spacing: 5) {
Text("Date de fin du BET")
Text("bet_creation_end_bet_date")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 17)
Image("questionMarkGreyIcon")
.resizable()
.frame(width: 14, height: 14)
.allInPopover(isPresented: $showBetEndDatePopover) {
"Date des résultats où seront redistribués les Allcoins aux vainqueurs."
String(localized: "bet_creation_bet_end_tooltip")
}
Spacer()
@ -228,7 +220,7 @@ struct CreationBetView: View {
DatePicker(
"",
selection: $viewModel.endBetDate,
in: dateRange,
in: viewModel.endRegisterDate...,
displayedComponents: [.date, .hourAndMinute]
)
.accentColor(AllInColors.lightPurpleColor)
@ -242,13 +234,13 @@ struct CreationBetView: View {
VStack {
HStack(spacing: 5) {
Text("Confidentialité du BET")
Text("bet_creation_bet_privacy")
.textStyle(weight: .bold, color: AllInColors.primaryTextColor, size: 17)
Image("questionMarkGreyIcon")
.resizable()
.frame(width: 14, height: 14)
.allInPopover(isPresented: $showConfidentialityPopover, paddingHorizontal: 15) {
"Option permettant d'ouvrir ou non le pari à des inconnus."
String(localized: "bet_creation_privacy_tooltip")
}
Spacer()
@ -256,15 +248,15 @@ struct CreationBetView: View {
.padding(.leading, 10)
HStack(spacing: 5) {
ConfidentialityButton(image: "globe", text: "Public", selected: viewModel.isPublic)
ConfidentialityButton(image: "globe", text: String(localized: "bet_public"), selected: !viewModel.isPrivate)
.onTapGesture {
viewModel.isPublic = true
viewModel.isPrivate = false
}
.padding(.trailing, 5)
ConfidentialityButton(image: "lock", text: "Privé", selected: !viewModel.isPublic)
ConfidentialityButton(image: "lock", text: String(localized: "bet_private"), selected: viewModel.isPrivate)
.onTapGesture {
viewModel.isPublic = false
viewModel.isPrivate = true
}
Spacer()
}
@ -275,28 +267,46 @@ struct CreationBetView: View {
VStack(spacing: 10) {
if !self.viewModel.isPublic {
DropDownFriends()
if self.viewModel.isPrivate {
DropDownFriends(selectedItems: $viewModel.invited, friends: viewModel.friends)
.padding(.bottom, 30)
HStack() {
Spacer()
Text("bet_creation_private_bottom_text_1")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
HStack() {
Spacer()
Text("bet_creation_private_bottom_text_2")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
} else {
HStack() {
Spacer()
Text("bet_creation_public_bottom_text_1")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
HStack() {
Spacer()
Text("bet_creation_public_bottom_text_2")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
}
HStack() {
Spacer()
Text("Votre BET sera visible par tous les utilisateurs.")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
HStack() {
Spacer()
Text("Tout le monde pourra rejoindre le BET.")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.multilineTextAlignment(.center)
Spacer()
}
HStack() {
Spacer()
Text("Vous pourrez inviter des amis à tout moment pendant la période dinscription.")
Text("bet_creation_bottom_text_3")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.padding(.leading, 35)
.multilineTextAlignment(.center)
@ -309,13 +319,13 @@ struct CreationBetView: View {
Button(action: {
viewModel.create()
}) {
Text("Publier le bet")
Text("bet_creation_publish")
.font(.system(size: 24))
.fontWeight(.bold)
.overlay {
AllInColors.primaryGradient.frame(width: 150)
AllInColors.primaryGradient.frame(width: 200)
.mask(
Text("Publier le bet")
Text("bet_creation_publish")
.font(.system(size: 24))
.fontWeight(.bold)
.frame(maxWidth: .infinity)
@ -341,37 +351,43 @@ struct CreationBetView: View {
VStack(spacing: 5) {
VStack() {
DropDownMenu(selectedOption: $selectedOption, options: options)
DropDownMenu(selectedOption: $viewModel.selectedTypeBet, options: viewModel.options)
}
.padding([.bottom], 15)
.frame(width: 340)
Group {
switch selectedOption {
switch viewModel.selectedTypeBet {
case 0:
Text("Les utilisateurs devront répondre au pari avec OUI ou NON.")
Text("bet_creation_yes_no_bottom_text_1")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.padding([.leading, .trailing], 20)
Text("Aucune autre réponse ne sera acceptée.")
Text("bet_creation_yes_no_bottom_text_2")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
case 2:
Text("Vous allez renseigner les différentes réponses disponibles dans ce pari.")
Text("bet_creation_custom_bottom_text_1")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.padding(.leading, 13)
Text("Faites attention a etre claire et éviter toutes incertitudes")
Text("bet_creation_custom_bottom_text_2")
.textStyle(weight: .bold, color: AllInColors.veryLightPurpleColor, size: 13)
.padding(.bottom, 15)
if let responseError = $viewModel.responsesFieldError.wrappedValue {
Text(responseError)
.textStyle(weight: .bold, color: .red, size: 10)
}
VStack(spacing: 5) {
HStack(spacing: 0) {
TextField("", text: $response, prompt: Text("Intitulé de réponse")
TextField("", text: $viewModel.response, prompt: Text("bet_creation_response_title")
.foregroundColor(AllInColors.lightGrey200Color)
.font(.system(size: 16))
.fontWeight(.medium))
.autocorrectionDisabled(true)
.padding()
.background(
Rectangle()
@ -381,20 +397,20 @@ struct CreationBetView: View {
)
.frame(width: 250, height: 38)
.foregroundColor(.black)
.onChange(of: response) { newValue in
.onChange(of: viewModel.response) { newValue in
if newValue.count > 20 {
response = String(newValue.prefix(20))
viewModel.response = String(newValue.prefix(20))
}
}
Button(action: {
if !response.isEmpty && values.count < 5 {
values.append(response)
if !viewModel.response.isEmpty && viewModel.values.count < 5 {
viewModel.values.append(viewModel.response)
updateGroupedItems()
response = ""
viewModel.response = ""
}
}) {
Text("Ajouter")
Text("generic_add")
.foregroundColor(.white)
}
.frame(width: 95, height: 40)
@ -404,12 +420,12 @@ struct CreationBetView: View {
}
HStack {
Spacer()
Text("encore \(5 - values.count) max.")
Text(String(localized: "bet_creation_max_answers \(5 - viewModel.values.count)"))
.textStyle(weight: .regular, color: AllInColors.primaryTextColor, size: 12)
}
VStack(spacing: 10) {
ForEach(groupedItems, id: \.self) { items in
ForEach(viewModel.groupedItems, id: \.self) { items in
HStack {
ForEach(items, id: \.self) { text in
HStack {
@ -417,8 +433,8 @@ struct CreationBetView: View {
.foregroundColor(.white)
.lineLimit(1)
Button(action: {
if let index = values.firstIndex(of: text) {
values.remove(at: index)
if let index = viewModel.values.firstIndex(of: text) {
viewModel.values.remove(at: index)
updateGroupedItems()
}
}) {
@ -439,7 +455,7 @@ struct CreationBetView: View {
}
}
default:
Text("En attente")
Text("generic_in_waiting")
}
}
Spacer()
@ -453,7 +469,7 @@ struct CreationBetView: View {
Button(action: {
selectedTab = 0
}) {
Text("Question")
Text("bet_creation_question")
.font(.system(size: 16))
.padding()
.fontWeight(selectedTab == 0 ? .bold : .semibold)
@ -463,7 +479,7 @@ struct CreationBetView: View {
Button(action: {
selectedTab = 1
}) {
Text("Réponses")
Text("bet_creation_answer")
.font(.system(size: 16))
.padding()
.fontWeight(selectedTab == 1 ? .bold : .semibold)
@ -477,6 +493,9 @@ struct CreationBetView: View {
.onTapGesture {
hideKeyboard()
}
.alert(isPresented: $viewModel.showErrorMessage) {
Alert(title: Text("bet_creation_error"), message: Text(viewModel.errorMessage ?? ""), dismissButton: .default(Text("generic_ok")))
}
.edgesIgnoringSafeArea(.bottom)
.background(AllInColors.backgroundColor)
}

@ -0,0 +1,41 @@
//
// CurrentBetView.swift
// AllIn
//
// Created by Emre on 31/01/2024.
//
import SwiftUI
import Model
struct CurrentBetView: View {
@StateObject private var viewModel = CurrentBetViewModel()
@Binding var showMenu: Bool
@State private var showingSheet = false
var body: some View {
VStack(alignment: .center, spacing: 0) {
TopBar(showMenu: self.$showMenu)
ScrollView(showsIndicators: false) {
Text("bet_history_current_title")
.textStyle(weight: .bold, color: AllInColors.grey500Color, size: 25)
.padding([.top],15)
VStack(spacing: 20){
ForEach(viewModel.bets, id: \.bet.id) { (betDetail: BetDetail) in
ReviewCard(bet: betDetail.bet, amount: betDetail.userParticipation?.stake ?? 0, isWin: false)
}
}
.padding([.trailing, .leading, .bottom],25)
}
.refreshable {
viewModel.getItems()
}
Spacer()
}
.edgesIgnoringSafeArea(.bottom)
.background(AllInColors.backgroundColor)
}
}

@ -0,0 +1,129 @@
//
// DailyGiftPage.swift
// AllIn
//
// Created by Emre on 02/02/2024.
//
import SwiftUI
struct DailyGiftPage: View {
enum Step {
case first
case second
case end
}
@State private var step: Step = .first
@State private var scale: CGFloat = 1.0
@State private var scale2: CGFloat = 0
@State private var rotate: CGFloat = 1
@Binding var show: Bool
@Binding var gain: Int
var body: some View {
GeometryReader { geometry in
LinearGradient(
gradient: Gradient(colors: [
Color.black.opacity(0.6),
Color.black.opacity(0.9)
]),
startPoint: .top,
endPoint: .bottom
)
.edgesIgnoringSafeArea(.all)
VStack {
Text("daily_reward_title")
.textStyle(weight: .bold, color: .white, size: 25)
Group {
switch step {
case .first:
Image("giftImage")
.transition(
.asymmetric(
insertion: .scale(scale: 0.9).combined(with: .opacity),
removal: .scale(scale: 1.7).combined(with: .opacity))
)
.scaleEffect(scale)
.rotationEffect(.degrees(Double(scale) * 10), anchor: .center)
.rotationEffect(.degrees(-10), anchor: .center)
.onAppear {
withAnimation(Animation.easeInOut(duration: 1).repeatForever()) {
scale = 1.1
}
}
case .second:
ZStack {
Image("giftEarnImage")
.rotationEffect(.degrees(Double(rotate) * 10), anchor: .center)
.rotationEffect(.degrees(-10), anchor: .center)
.onAppear {
withAnimation(Animation.easeInOut(duration: 1).repeatForever()) {
rotate = 1.3
}
}
HStack {
Text("+" + gain.description)
.textStyle(weight: .black, color: .white, size: 55)
Image("allcoinWhiteIcon")
.resizable()
.frame(width: 40, height: 40)
}
}
.scaleEffect(scale2)
.onAppear {
withAnimation(Animation.easeInOut(duration: 0.8)) {
scale2 = 1.0
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
show = false
step = .first
}
}
}
.onDisappear {
scale2 = 0
}
default :
EmptyView()
}
}
.frame(width: geometry.size.width * 0.8, height: geometry.size.height * 0.4)
.onTapGesture {
withAnimation {
switch step {
case .first:
step = .second
withAnimation {
AppStateContainer.shared.user?.nbCoins += gain
}
case .second:
show = false
step = .end
case .end:
step = .first
}
}
}
Text("daily_reward_subtitle")
.textStyle(weight: .medium, color: .white, size: 13)
.multilineTextAlignment(.center)
.padding(.horizontal, geometry.size.width * 0.13)
.opacity(0.67)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
struct DailyGiftPage_Previews: PreviewProvider {
static var previews: some View {
DailyGiftPage(show: .constant(false), gain: .constant(20))
}
}

@ -5,54 +5,32 @@ struct DetailsView: View {
@Binding var isModalPresented: Bool
@Binding var isModalParticipated: Bool
@State var progressValue: Float = 0.2
var id: String
@StateObject private var viewModel: DetailsViewModel
var isFinished: Bool {
viewModel.betDetail?.finalAnswer == nil ? false : true
}
var isDisabled: Bool {
if let endRegisterDate = viewModel.betDetail?.bet.endRegisterDate {
let currentDate = Date()
switch currentDate.compare(endRegisterDate) {
case .orderedAscending:
return false
case .orderedDescending:
return true
case .orderedSame:
return true
}
} else {
return true
}
viewModel.betDetail?.wonParticipation == nil ? false : true
}
var StatusValues: (String, Color) {
if let endRegisterDate = viewModel.betDetail?.bet.endRegisterDate {
let currentDate = Date()
switch currentDate.compare(endRegisterDate) {
case .orderedAscending:
return ("En cours...", AllInColors.purpleAccentColor)
case .orderedDescending:
return ("En attente...",AllInColors.pink100)
case .orderedSame:
return ("Fin des inscriptions...",AllInColors.grey50Color)
if let betType = viewModel.betDetail?.bet.status {
switch betType {
case .inProgress:
return (String(localized: "bet_status_in_progress"), AllInColors.darkPurpleColor)
case .waiting, .closing:
return (String(localized: "bet_status_waiting"), AllInColors.pink100)
case .finished:
return (String(localized: "bet_status_finished"), AllInColors.grey100Color)
case .cancelled:
return (String(localized: "bet_status_cancelled"), AllInColors.grey100Color)
}
} else {
return ("Statut indisponible", AllInColors.whiteColor)
return (String(localized: "bet_status_unavailable"), AllInColors.pink100)
}
}
init(isModalPresented: Binding<Bool>, isModalParticipated: Binding<Bool>, id: String) {
self._isModalPresented = isModalPresented
self._isModalParticipated = isModalParticipated
self.id = id
self._viewModel = StateObject(wrappedValue: DetailsViewModel(id: id))
}
@ -62,6 +40,7 @@ struct DetailsView: View {
VStack(alignment: .center) {
HStack{
Text(StatusValues.0)
.italic()
.font(.system(size: 25))
.fontWeight(.bold).padding(.bottom, 10)
.foregroundStyle(Color.black)
@ -78,86 +57,117 @@ struct DetailsView: View {
}
.padding(.horizontal, 15)
.background(StatusValues.1)
.transition(.slideInFromBottom(yOffset:0))
VStack(spacing: 0) {
VStack(alignment: .leading,spacing: 5){
HStack{
Spacer()
Text("proposé par " + (viewModel.betDetail?.bet.author.username ?? "Unknown").capitalized)
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
}
Text(viewModel.betDetail?.bet.theme ?? "Not loaded")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
Text(viewModel.betDetail?.bet.phrase ?? "Not loaded")
.font(.system(size: 20))
.fontWeight(.bold)
.padding(.bottom, 10)
HStack{
Text("Commence le")
.frame(maxWidth: 100)
if viewModel.betDetail != nil{
ScrollView {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 3) {
Spacer()
Text("bet_proposed_by_format")
.font(.system(size: 10))
.foregroundColor(AllInColors.grey800Color)
Text((viewModel.betDetail?.bet.author ?? "Unknown").capitalized)
.font(.system(size: 10))
.fontWeight(.semibold)
.foregroundColor(AllInColors.primaryTextColor)
}
Text(viewModel.betDetail?.bet.theme ?? "Not loaded")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: viewModel.betDetail?.bet.endRegisterDate ?? Date())
Spacer()
Text(viewModel.betDetail?.bet.phrase ?? "Not loaded")
.font(.system(size: 20))
.fontWeight(.bold)
.padding(.bottom, 10)
HStack {
HStack {
Spacer()
Text("bet_starting")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
}
.frame(width: 105)
.padding(.trailing, 10)
TextCapsule(date: viewModel.betDetail?.bet.endRegisterDate ?? Date())
Spacer()
}.padding(.bottom, 10)
HStack {
HStack {
Spacer()
Text("bet_ends")
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
}
.frame(width: 105)
.padding(.trailing, 10)
TextCapsule(date: viewModel.betDetail?.bet.endBetDate ?? Date())
Spacer()
}
}
.padding(.all, 15)
.padding(.top, 6)
.border(width: 1, edges: [.bottom], color: AllInColors.delimiterGrey)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(20, corners: [.topLeft,.topRight])
if isFinished {
ResultBanner(finalAnswer: (viewModel.betDetail?.wonParticipation)!, odds: (viewModel.betDetail?.odds)!)
}
VStack(alignment: .leading, spacing: 0) {
if let bet = viewModel.betDetail{
BetLineLoading(bet: bet).padding(.vertical, 20)
}
Text("bet_status_participants_list")
.font(.system(size: 18))
.foregroundStyle(AllInColors.grey100Color)
.fontWeight(.bold)
.padding(.bottom, 10)
}.padding(.bottom, 10)
HStack{
Text("Fini le")
.frame(maxWidth: 100)
.font(.system(size: 15))
.foregroundColor(AllInColors.grey800Color)
TextCapsule(date: viewModel.betDetail?.bet.endBetDate ?? Date())
Spacer()
ForEach(viewModel.betDetail?.participations ?? []) { participation in
UserInfo(username: participation.username, picture: nil , value: participation.stake).padding(.horizontal, 10)
}
.padding(.bottom)
Spacer()
}
.padding([.trailing,.leading], 15)
.padding(.bottom, 100)
}
.frame(width: .infinity)
.padding(.all,15).padding(.vertical, 10)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(20, corners: [.topLeft,.topRight]).padding(.bottom,0)
if isFinished {
ResultBanner()
}
VStack(alignment: .leading, spacing: 5) {
BetLineLoading(participations: viewModel.betDetail?.participations ?? [])
.padding(.vertical,15)
Text("Liste des participants")
.font(.system(size: 18))
.foregroundStyle(AllInColors.grey100Color)
.fontWeight(.bold)
.padding(.bottom, 10)
ForEach(viewModel.betDetail?.participations ?? []) { (participation: Participation) in
ParticiationCell(participation: participation).padding(.horizontal, 10)
}
.frame(maxWidth: .infinity, maxHeight: (geometry.size.height + geometry.safeAreaInsets.bottom) - 50)
.background(AllInColors.underComponentBackgroundColor)
.cornerRadius(15, corners: [.topLeft, .topRight])
}
else{
ScrollView {
HStack(alignment: .center){
Spacer()
Image(systemName: "exclamationmark.triangle.fill").font(.system(size: 45))
VStack(alignment:.leading){
Text("error_title").font(.system(size: 10))
Text("error_message").font(.system(size: 10))
}
Spacer()
}.opacity(0.6)
.padding(.vertical, 20)
Spacer()
}
.frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: .infinity)
.padding([.bottom,.trailing,.leading], 15)
.frame(maxWidth: .infinity, maxHeight: (geometry.size.height + geometry.safeAreaInsets.bottom) - 50)
.background(AllInColors.underComponentBackgroundColor)
.border(width: 1, edges: [.top], color: AllInColors.delimiterGrey)
Spacer()
.cornerRadius(15, corners: [.topLeft, .topRight])
}
.frame(maxWidth: .infinity, maxHeight: geometry.size.height*0.98)
.background(AllInColors.componentBackgroundColor)
.cornerRadius(15)
ParticipateButton(isOpen: $isModalPresented, isParticapatedOpen: $isModalParticipated, bet: viewModel.betDetail?.bet)
.padding(10)
.padding(.bottom, geometry.safeAreaInsets.bottom + 5)
.padding(.horizontal, 10)
}
.sheet(isPresented: $isModalParticipated) {
ParticipationModal(answer: $viewModel.answer, mise: $viewModel.mise, description: viewModel.betDetail?.bet.phrase ?? "Not loaded", participationAddedCallback: {
ParticipationModal(selectedAnswer: $viewModel.selectedAnswer, mise: $viewModel.mise, phrase: viewModel.betDetail?.bet.phrase ?? "", answers: viewModel.betDetail?.answers ?? [], participationAddedCallback: {
viewModel.addParticipate()
isModalParticipated.toggle()
})
},
checkAndSetError: viewModel.checkAndSetError)
.presentationDetents([.fraction(0.55)])
}
.edgesIgnoringSafeArea(.bottom)

@ -9,32 +9,112 @@ import SwiftUI
struct FriendsView: View {
@StateObject private var viewModel = FriendsViewModel()
@Binding var showMenu: Bool
@State private var selectedTab = 0
var body: some View {
VStack(alignment: .center, spacing: 0) {
TopBar(showMenu: self.$showMenu)
Text("Amis")
.textStyle(weight: .bold, color: AllInColors.grey500Color, size: 25)
.padding([.top,.bottom],15)
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")
TabView(selection: $selectedTab) {
ScrollView(showsIndicators: false){
VStack{
HStack {
TextField("Search...", text: $viewModel.text)
.padding(7)
.zIndex(200)
.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)
}
if(viewModel.users.isEmpty){
EmptyInfo(emoji:"👥", title: String(localized: "empty_friends_title"), explain: String(localized: "empty_friends_explain")).padding(.top, 40)
}
else{
ForEach(viewModel.users, id: \.self) { friend in
Friend(user: friend, isRequest: false, viewModel: viewModel)
}
}
}
Spacer()
}
.refreshable {
viewModel.getItems()
}
.padding(.top, 50)
.tag(0)
ScrollView{
VStack(alignment: .center, spacing: 0) {
if(viewModel.requests.isEmpty){
EmptyInfo(emoji:"📬", title: "Aucune demande d'amis en attente", explain: "").padding(.top, 40)
}
else{
ScrollView(showsIndicators: false){
ForEach(viewModel.requests, id: \.self) { request in
Friend(user: request, isRequest: true, viewModel: viewModel)
}
}
.refreshable {
viewModel.getRequests()
}
.padding(.top, 25)
}
Spacer()
}
}.refreshable {
viewModel.getRequests()
}
.padding(.top, 50)
.tag(1)
}
.padding(.top, 25)
Spacer()
.overlay(
HStack {
Button(action: {
selectedTab = 0
}) {
Text("friends_title")
.font(.system(size: 16))
.padding()
.fontWeight(selectedTab == 0 ? .bold : .semibold)
.foregroundColor(selectedTab == 0 ? AllInColors.primaryTextColor : .gray)
.offset(y: 0)
}
Button(action: {
selectedTab = 1
}) {
Text(String(localized: "friends_request") + (viewModel.requests.isEmpty ? "" : "(\(viewModel.requests.count.description))"))
.font(.system(size: 16))
.padding()
.fontWeight(selectedTab == 1 ? .bold : .semibold)
.foregroundColor(selectedTab == 1 ? AllInColors.primaryTextColor : .gray)
.offset(y: 0)
}
}
, alignment: .top)
.tabViewStyle(PageTabViewStyle())
}
.edgesIgnoringSafeArea(.bottom)
.background(AllInColors.backgroundColor)
}
}
struct FriendsView_Previews: PreviewProvider {
static var previews: some View {
FriendsView(showMenu: .constant(false))
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save