Last active
October 17, 2023 08:39
-
-
Save allfinlir/ab5c23d8d4ee46142f97c235f27d37d4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct BetDetailsView: View { | |
var bet: BetModel | |
@ObservedObject var betViewModel: BetViewModel | |
// States for the button animations | |
@State var scaleWinButton: CGFloat = 1.0 | |
@State var scaleLooseButton: CGFloat = 1.0 | |
@State var scalePushButton: CGFloat = 1.0 | |
// Variables for the matched geometry effect | |
@Binding var showBetDetails: Bool | |
var animation: Namespace.ID | |
@State var loadContent: Bool = false | |
var body: some View { | |
ScrollView { | |
VStack(alignment: .leading, spacing: 10) { | |
HStack { | |
Button(action: { | |
withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) { | |
showBetDetails.toggle() | |
} | |
}, label: { | |
Image(systemName: "chevron.left") | |
.font(.title) | |
.foregroundColor(.black) | |
}) | |
Spacer() | |
} | |
HStack { | |
Spacer() | |
Text(bet.dateForBet, style: .date) | |
} | |
Text(bet.league) | |
.foregroundColor(.secondary) | |
.font(.title3) | |
.fontWeight(.semibold) | |
HStack { | |
Text(bet.homeTeam) | |
Text("vs") | |
.foregroundColor(.secondary) | |
.font(.title3) | |
Text(bet.awayTeam) | |
} | |
.font(.title2) | |
HStack(alignment: .center, spacing: 15) { | |
RoundedRectangle(cornerRadius: 4) | |
.frame(width: 50, height: 25) | |
.foregroundColor(bet.winBet ? .green : .green.opacity(0.1)) | |
.overlay { | |
Text("W") | |
.font(.system(size: 13)) | |
.foregroundColor(bet.winBet ? .white : .black) | |
.fontWeight(bet.winBet ? .semibold : .light) | |
} | |
.scaleEffect(scaleWinButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scaleWinButton) | |
.onTapGesture { | |
betViewModel.updateWinBet(bet: bet) | |
scaleWinButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scaleWinButton = 1.0 | |
} | |
} | |
RoundedRectangle(cornerRadius: 5) | |
.frame(width: 50, height: 25) | |
.foregroundColor(bet.looseBet ? .red : .red.opacity(0.1)) | |
.overlay { | |
Text("L") | |
.font(.system(size: 13)) | |
.foregroundColor(bet.looseBet ? .white : .black) | |
.fontWeight(bet.looseBet ? .semibold : .light) | |
} | |
.scaleEffect(scaleLooseButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scaleLooseButton) | |
.onTapGesture { | |
betViewModel.updateLooseBet(bet: bet) | |
scaleLooseButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scaleLooseButton = 1.0 | |
} | |
} | |
RoundedRectangle(cornerRadius: 5) | |
.frame(width: 50, height: 25) | |
.foregroundColor(bet.pushBet ? .blue : .blue.opacity(0.1)) | |
.overlay { | |
Text("P") | |
.font(.system(size: 13)) | |
.foregroundColor(bet.pushBet ? .white : .black) | |
.fontWeight(bet.pushBet ? .semibold : .light) | |
} | |
.scaleEffect(scalePushButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scalePushButton) | |
.onTapGesture { | |
betViewModel.updatePushBet(bet: bet) | |
scalePushButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scalePushButton = 1.0 | |
} | |
} | |
} | |
.padding(.top, 15) | |
.frame(maxWidth: .infinity) | |
.matchedGeometryEffect(id: bet.id, in: animation) | |
VStack(alignment: .leading, spacing: 25) { | |
HStack { | |
Text("Bet:") | |
Text(bet.typeOfBet) | |
.foregroundStyle(.secondary) | |
.padding(.trailing, 8) | |
Text("Odds:") | |
Text("\(bet.odds, specifier: "%.2f")") | |
.foregroundStyle(.secondary) | |
.padding(.trailing, 8) | |
Text("Amount:") | |
Text("\(bet.betAmount, specifier: "%.2f")") | |
.foregroundStyle(.secondary) | |
} | |
.padding(.top, 25) | |
HStack(alignment: .bottom, spacing: 20) { | |
Text("Result:") | |
Text("\(bet.potentialWin, specifier: "%.2f")") | |
.foregroundStyle(bet.potentialWin < 0.0 ? .red : .green) | |
.font(.title2) | |
.opacity(bet.winBet || bet.looseBet || bet.pushBet ? 1 : 0) | |
} | |
} | |
Text("Motivation") | |
.foregroundColor(.secondary) | |
.font(.title3) | |
.fontWeight(.semibold) | |
.padding(.top, 15) | |
if bet.description.isEmpty { | |
VStack { | |
Text("No motivation for bet") | |
} | |
} else { | |
Text(bet.description) | |
.font(.title3) | |
.multilineTextAlignment(.center) | |
.padding(.vertical, 15) | |
.padding(.horizontal, 25) | |
.background( | |
ZStack { | |
RoundedRectangle(cornerRadius: 10) | |
.foregroundColor(.white) | |
.shadow(color: .black, radius: 5, x: 5, y: 5) | |
} | |
) | |
} | |
} | |
.padding() | |
} | |
.onAppear { | |
withAnimation(Animation.spring().delay(1.15)) { | |
loadContent.toggle() | |
} | |
} | |
} | |
} | |
struct BetDetailsView_Previews: PreviewProvider { | |
static var betItem1 = BetModel(league: "LaLiga", homeTeam: "Real Madrid", awayTeam: "Atletico Madrid", dateForBEt: Date(), typeOfBet: "1", odds: 1.78, betAmount: 100, bettor: ["Rikard", "Petros"], potentialWin: 178, winBet: false, looseBet: false, pushBet: false, betCount: 1, description: "I think Real Madrid will win because they are so much better than Atletico :)") | |
@Namespace static var namespace | |
@State static var bool: Bool = false | |
static var previews: some View { | |
BetDetailsView(bet: betItem1, betViewModel: BetViewModel(), showBetDetails: $bool, animation: namespace) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftUI | |
struct BetModel: Identifiable, Codable { // Identifiable so we have id on each item and codable so it can handle JSON data | |
let id: String | |
var league: String | |
let homeTeam: String | |
let awayTeam: String | |
var dateForBet: Date | |
let typeOfBet: String | |
let odds: Double | |
let betAmount: Double | |
var bettor: [String] | |
var potentialWin: Double | |
var winBet: Bool | |
var looseBet: Bool | |
var pushBet: Bool | |
var betCount: Int | |
let description: String | |
init(id: String = UUID().uuidString, league: String, homeTeam: String, awayTeam: String, dateForBEt: Date, typeOfBet: String, odds: Double, betAmount: Double, bettor: [String], potentialWin: Double, winBet: Bool, looseBet: Bool, pushBet: Bool, betCount: Int, description: String) { | |
self.id = id | |
self.league = league | |
self.homeTeam = homeTeam | |
self.awayTeam = awayTeam | |
self.dateForBet = dateForBEt | |
self.typeOfBet = typeOfBet | |
self.odds = odds | |
self.betAmount = betAmount | |
self.bettor = bettor | |
self.potentialWin = potentialWin | |
self.winBet = winBet | |
self.looseBet = looseBet | |
self.pushBet = pushBet | |
self.betCount = betCount | |
self.description = description | |
} | |
func updateWinCompletion() -> BetModel { | |
return BetModel(id: id, league: league, homeTeam: homeTeam, awayTeam: awayTeam, dateForBEt: dateForBet, typeOfBet: typeOfBet, odds: odds, betAmount: betAmount, bettor: bettor, potentialWin: winBet ? 0.0 : (odds * betAmount) - betAmount, winBet: !winBet, looseBet: looseBet, pushBet: pushBet, betCount: betCount, description: description) | |
} | |
func updateLostCompletion() -> BetModel { | |
return BetModel(id: id, league: league, homeTeam: homeTeam, awayTeam: awayTeam, dateForBEt: dateForBet, typeOfBet: typeOfBet, odds: odds, betAmount: betAmount, bettor: bettor, potentialWin: looseBet ? 0.0 : -betAmount, winBet: winBet, looseBet: !looseBet, pushBet: pushBet, betCount: betCount, description: description) | |
} | |
func updatePushCompletion() -> BetModel { | |
return BetModel(id: id, league: league, homeTeam: homeTeam, awayTeam: awayTeam, dateForBEt: dateForBet, typeOfBet: typeOfBet, odds: odds, betAmount: betAmount, bettor: bettor, potentialWin: 0.0, winBet: winBet, looseBet: looseBet, pushBet: !pushBet, betCount: betCount, description: description) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct BetRowView: View { | |
var bet: BetModel | |
@EnvironmentObject var betViewModel: BetViewModel | |
@State var scaleWinButton: CGFloat = 1.0 | |
@State var scaleLooseButton: CGFloat = 1.0 | |
@State var scalePushButton: CGFloat = 1.0 | |
@State var currentTime = Date() | |
var animation: Namespace.ID | |
var body: some View { | |
ZStack(alignment: .top) { | |
VStack(spacing: 5) { | |
if currentTime <= bet.dateForBet { | |
VStack { | |
Text(bet.dateForBet, style: .offset) | |
Text("to game time") | |
} | |
.font(.system(size: 10)) | |
} else if currentTime >= bet.dateForBet.addingTimeInterval(5) { | |
VStack { | |
Text("Game ended") | |
.font(.system(size: 10)) | |
} | |
.padding(2) | |
.background( | |
Color.red | |
.opacity(0.4) | |
) | |
} else { | |
Text("Game started") | |
.font(.system(size: 10)) | |
} | |
} | |
.onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in | |
currentTime = Date() | |
} | |
HStack(alignment: .center) { | |
VStack(alignment: .leading, spacing: 5) { | |
Text(bet.league) | |
.foregroundColor(.secondary) | |
.font(.system(size: 10)) | |
.fontWeight(.semibold) | |
Text(bet.homeTeam) | |
.font(.system(size: 15)) | |
Text(bet.awayTeam) | |
.font(.system(size: 15)) | |
HStack(spacing: 3) { | |
Text("Bet:") | |
.fontWeight(.semibold) | |
Text(bet.typeOfBet) | |
.foregroundColor(.secondary) | |
.padding(.trailing, 8) | |
Text("Odds:") | |
.fontWeight(.semibold) | |
Text("\(bet.odds, specifier: "%.2f")") | |
.foregroundColor(.secondary) | |
.padding(.trailing, 8) | |
Text("Amount:") | |
.fontWeight(.semibold) | |
Text("\(bet.betAmount, specifier: "%.2f")") | |
.foregroundColor(.secondary) | |
} | |
.font(.system(size: 10)) | |
} | |
.lineLimit(1) | |
Spacer() | |
VStack(spacing: 5) { | |
RoundedRectangle(cornerRadius: 4) | |
.frame(width: 35, height: 17) | |
.foregroundColor(bet.winBet ? .green : .green.opacity(0.1)) | |
.overlay { | |
Text("W") | |
.font(.system(size: 10)) | |
.foregroundColor(bet.winBet ? .white : .black) | |
.fontWeight(bet.winBet ? .semibold : .light) | |
} | |
.scaleEffect(scaleWinButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scaleWinButton) | |
.onTapGesture { | |
betViewModel.updateWinBet(bet: bet) | |
scaleWinButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scaleWinButton = 1.0 | |
} | |
} | |
RoundedRectangle(cornerRadius: 5) | |
.frame(width: 35, height: 17) | |
.foregroundColor(bet.looseBet ? .red : .red.opacity(0.1)) | |
.overlay { | |
Text("L") | |
.font(.system(size: 10)) | |
.foregroundColor(bet.looseBet ? .white : .black) | |
.fontWeight(bet.looseBet ? .semibold : .light) | |
} | |
.scaleEffect(scaleLooseButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scaleLooseButton) | |
.onTapGesture { | |
betViewModel.updateLooseBet(bet: bet) | |
scaleLooseButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scaleLooseButton = 1.0 | |
} | |
} | |
RoundedRectangle(cornerRadius: 5) | |
.frame(width: 35, height: 17) | |
.foregroundColor(bet.pushBet ? .blue : .blue.opacity(0.1)) | |
.overlay { | |
Text("P") | |
.font(.system(size: 10)) | |
.foregroundColor(bet.pushBet ? .white : .black) | |
.fontWeight(bet.pushBet ? .semibold : .light) | |
} | |
.scaleEffect(scalePushButton) | |
.animation(.easeInThenBackOut(duration: 0.3), value: scalePushButton) | |
.onTapGesture { | |
betViewModel.updatePushBet(bet: bet) | |
scalePushButton = 1.4 | |
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { | |
scalePushButton = 1.0 | |
} | |
} | |
} | |
.matchedGeometryEffect(id: bet.id, in: animation) | |
} | |
.frame(maxWidth: .infinity) | |
} | |
} | |
} | |
extension Animation { | |
static var easeInThenBackOut: Animation { | |
Animation.timingCurve(0.5, -1, 0.5, 1.5) | |
} | |
static func easeInThenBackOut(duration: TimeInterval = 0.2) -> Animation { | |
Animation.timingCurve(0.5, -1, 0.5, 1.5, duration: duration) | |
} | |
} | |
struct BetRowView_Previews: PreviewProvider { | |
static var betItem1 = BetModel(league: "LaLiga", homeTeam: "Real Madrid", awayTeam: "Atletico Madrid", dateForBEt: Date(), typeOfBet: "U2.5", odds: 1.73, betAmount: 100, bettor: ["Rikard", "Petros"], potentialWin: 178, winBet: false, looseBet: false, pushBet: false, betCount: 1, description: "Hello there") | |
@Namespace static var namespace | |
static var previews: some View { | |
BetRowView(bet: betItem1, animation: namespace) | |
.previewLayout(.sizeThatFits) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct BetView: View { | |
@EnvironmentObject var betViewModel: BetViewModel | |
// Variables for the matched geometry effect animation to betDetailsView | |
@State var selectedBet: BetModel? | |
@State var showBetDetails: Bool = false | |
@Namespace var animation | |
var body: some View { | |
ZStack { | |
NavigationStack { // ändra till NavigationStack o mdet inte funkar med matched geometry effecten | |
VStack { | |
if betViewModel.betItems.isEmpty { | |
NoBetView() | |
} else { | |
ZStack { | |
List { | |
ForEach(betViewModel.betItems) { bet in | |
BetRowView(bet: bet, animation: animation) | |
.contentShape(Rectangle()) | |
.onTapGesture { | |
withAnimation(.spring(response: 0.35, dampingFraction: 0.6)) { | |
selectedBet = bet | |
showBetDetails.toggle() | |
} | |
} | |
/* .background { | |
NavigationLink("", destination: BetDetailsView(betDetail: bet)) | |
.opacity(0) | |
} */ // lägg tillbaka denna om du vill köra med navigation link istället för matched geometry effect | |
} | |
.onDelete(perform: betViewModel.deleteBet) | |
.onMove(perform: betViewModel.moveBet) | |
//.listRowSeparator(.hidden) | |
} | |
.listStyle(.inset) | |
VStack { | |
Spacer() | |
HStack { | |
Spacer() | |
NavigationLink(destination: AddBetView(), label: { | |
Image(systemName: "plus") | |
.foregroundColor(.white) | |
.font(.system(size: 30)) | |
.padding() | |
.background( | |
Circle() | |
.foregroundColor(.green) | |
.shadow(color: .black.opacity(0.2), radius: 1, x: 1, y: 1) | |
) | |
}) | |
.padding(20) | |
} | |
} | |
} | |
} | |
} | |
.navigationBarItems(leading: EditButton(), trailing: NavigationLink("Add", destination: AddBetView())) | |
} | |
.opacity(showBetDetails ? 0 : 1) | |
if showBetDetails { | |
BetDetailsView(bet: selectedBet!, betViewModel: betViewModel, showBetDetails: $showBetDetails, animation: animation) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class BetViewModel: ObservableObject { | |
@Published var betItems: [BetModel] = [] | |
@Published var newBettors: [AddBettorDataModel] = [] | |
init() { | |
getBets() // fetches my data from getBets() function | |
} | |
public var totalWinningsLosses: Double { | |
return betItems.reduce(0) { sum, betResults in | |
sum + betResults.potentialWin | |
} | |
} | |
func newBettorAdded(bettor: String) { | |
let newBettor = AddBettorDataModel(newBettor: bettor) | |
newBettors.insert(newBettor, at: 0) | |
// saveBet() | |
} | |
func makeChartData(from bets: [BetModel]) -> [BetDataPoint] { | |
let sortedBets = betItems.sorted { $0.dateForBet < $1.dateForBet } | |
var currentTotal = 0.0 | |
let dataPoints = sortedBets.map { bet in | |
currentTotal += bet.potentialWin | |
return BetDataPoint(date: bet.dateForBet, total: currentTotal, league: bet.league) | |
} | |
return dataPoints | |
} | |
func chartDataPerWeek(from bets: [BetModel]) -> [BetDataPointPerWeek] { | |
let lastWeek = Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? Date() | |
let sortedBetsLastWeek = betItems.filter { $0.dateForBet >= lastWeek }.sorted(by: { $0.dateForBet < $1.dateForBet }) | |
// let sortedBetsLastWeek = betItems.sorted { $0.dateForBet < $1.dateForBet } | |
var currentWeekTotal = 0.0 | |
let weekDataPoints = sortedBetsLastWeek.map { weekBets in | |
currentWeekTotal += weekBets.potentialWin | |
return BetDataPointPerWeek(date: weekBets.dateForBet, totalLastWeek: currentWeekTotal) | |
} | |
return weekDataPoints | |
} | |
let betItemKey: String = "Bets" | |
func getBets() { | |
guard let data = UserDefaults.standard.data(forKey: betItemKey), | |
let savedBetItems = try? JSONDecoder().decode([BetModel].self, from: data) | |
else { return } | |
self.betItems = savedBetItems.sorted(by: { $0.dateForBet > $1.dateForBet }) // this will sort all bets in the array by date. | |
} | |
// MARK: Sorts all bets by date in the WinChart View | |
func sortListofBetsByDate() -> [BetModel] { | |
betItems.sorted(by: { $0.dateForBet > $1.dateForBet }) | |
} | |
// MARK: sorts the bets to only the latest 7 days bet results | |
func resultsLastWeek() -> [BetModel] { | |
let lastWeek = Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? Date() | |
return betItems.filter { $0.dateForBet >= lastWeek }.sorted(by: { $0.dateForBet < $1.dateForBet}) | |
} | |
// MARK: So you can delete bets from the list | |
func deleteBet(indexSet: IndexSet) { | |
betItems.remove(atOffsets: indexSet) | |
saveBet() | |
} | |
// MARK: So you can move items in the bet list | |
func moveBet(from: IndexSet, to: Int) { | |
betItems.move(fromOffsets: from, toOffset: to) | |
} | |
// MARK: Counts all winning bets | |
func winningBets() -> Int { | |
betItems.filter { $0.winBet == true }.count | |
} | |
// MARK: Counts all loosing bets. | |
func loosingBets() -> Int { | |
betItems.filter { $0.looseBet == true }.count | |
} | |
// MARK: counts all bet that are push bets | |
func pushBets() -> Int { | |
betItems.filter { $0.pushBet == true }.count | |
} | |
// MARK: calculates all bets that have not been decided yet, those that are ongoing | |
func ongoingBets() -> Int { | |
betItems.filter { $0.winBet == false && $0.looseBet == false && $0.pushBet == false }.count | |
} | |
// func addBettor() -> [String] { | |
//} | |
// MARK: So you can add a bet in the bet view, save saveBet() is there so the list is saved when app is closed by user. | |
func addBet(league: String, homeTeam: String, awayTeam: String, dateForBet: Date, typeOfBet: String, odds: Double, betAmount: Double, bettor: [String], potentialWin: Double, betCount: Int, description: String) { | |
let newBet = BetModel(league: league, homeTeam: homeTeam, awayTeam: awayTeam, dateForBEt: dateForBet, typeOfBet: typeOfBet, odds: odds, betAmount: betAmount, bettor: bettor, potentialWin: potentialWin, winBet: false, looseBet: false, pushBet: false, betCount: betCount, description: description) | |
// betItems.append(newBet) | |
betItems.insert(newBet, at: 0) // use this instead if you want your bet to append at the top of your list | |
saveBet() | |
} | |
// MARK: Updates the bet row view and toggles the winbet boolean from false to true. | |
func updateWinBet(bet: BetModel) { | |
if let betWinIndex = betItems.firstIndex(where: {$0.id == bet.id }) { | |
betItems[betWinIndex] = bet.updateWinCompletion() // updateWinCompletion sätter vi ju in vår BetModel | |
} | |
} | |
// MARK: Updates the bet row view and toggles the loosebet boolean from false to true. | |
func updateLooseBet(bet: BetModel) { | |
if let betLooseIndex = betItems.firstIndex(where: {$0.id == bet.id }) { | |
betItems[betLooseIndex] = bet.updateLostCompletion() // updateLostCompletion sätter vi ju in vår BetModel | |
} | |
} | |
// MARK: Updates the bet row view and toggles the pushbet boolean from false to true. | |
func updatePushBet(bet: BetModel) { | |
if let betPushIndex = betItems.firstIndex(where: {$0.id == bet.id}) { | |
betItems[betPushIndex] = bet.updatePushCompletion() | |
} | |
} | |
// MARK: So data get saved once the app is closed by the user, | |
func saveBet() { | |
if let encodedBetData = try? JSONEncoder().encode(betItems) { | |
UserDefaults.standard.set(encodedBetData, forKey: betItemKey) | |
getBets() // by adding the function here, all new items in the array will be sorted on date as well. | |
} else { | |
print("Couldn't save bet") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment