Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Calls for Events #83

Merged
merged 57 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
382c453
Marking view model classes as being unused
Daggerpov Jan 7, 2025
204ac5b
dependency injecting mock vs. API service depending on static var, `m…
Daggerpov Jan 7, 2025
6cd842f
`FeedView` & `MapView`: dependency injecting api service into view mo…
Daggerpov Jan 7, 2025
b803d45
View Model: making api call to back-end endpoint
Daggerpov Jan 7, 2025
e18c28c
view model: async with try catch
Daggerpov Jan 8, 2025
92a775b
`FeedView` & `MapView`: initializing with `user` argument, then fetch…
Daggerpov Jan 8, 2025
98c5c69
Supplying user from feed & map view initializing views
Daggerpov Jan 8, 2025
42d9353
`errorMessage` usage and addition into protocol
Daggerpov Jan 8, 2025
37ce550
bringing back "Grabbing Udon" event
Daggerpov Jan 8, 2025
e2e56a6
Fixed publishing changes from background thread error
Daggerpov Jan 9, 2025
da0f733
Update APIError.swift
Daggerpov Jan 9, 2025
caa69e5
(revert later) Temp fix: `creator` made nullable, and put `User.danie…
Daggerpov Jan 11, 2025
b8b297d
Encoding + send data method in api service
Daggerpov Jan 11, 2025
b1024c1
Chat Message timestamp string -> date
Daggerpov Jan 11, 2025
cdccf85
Event: `startTime` and `endTime`: String -> Date and edited mocks wit…
Daggerpov Jan 11, 2025
cb74aae
formatting text for chat message timestamp
Daggerpov Jan 11, 2025
6e0e08f
chat message date formatter
Daggerpov Jan 11, 2025
f668fe5
`fetchTagsForUser()` added to `TagsTabView` + `FeedView` & `MapView` …
Daggerpov Jan 13, 2025
ddc15e0
Useless user in tabs tab view
Daggerpov Jan 13, 2025
886427d
`createEvent()` method with new `sendData()` function in api service
Daggerpov Jan 13, 2025
00f1cac
touching up view models
Daggerpov Jan 13, 2025
414b19c
`createEvent()` being called view model instantiated fcreationor event
Daggerpov Jan 13, 2025
86cee66
`createTag()` method in `TagsViewModel`, and call from `AddTagButtonV…
Daggerpov Jan 14, 2025
53881a1
De-activating custom decoding for date times for now
Daggerpov Jan 14, 2025
d3496c3
OAuth login functions from buttons
Daggerpov Jan 19, 2025
3dfe842
Update EventCreationView.swift
Daggerpov Jan 19, 2025
a441166
Replaced placeholders with new component `EventInputFieldLabel` + adj…
Daggerpov Jan 19, 2025
9109322
event title optional
Daggerpov Jan 19, 2025
bc02a93
All custom `EventInputField` components + new `TimePicker` component
Daggerpov Jan 19, 2025
d3e0376
Fixed time picker binding values
Daggerpov Jan 19, 2025
92648e4
format file
Daggerpov Jan 19, 2025
3cadebf
Added date component with helper function to combine date and time in…
Daggerpov Jan 19, 2025
02fde53
toggling showing full date picker
Daggerpov Jan 19, 2025
a22face
Update EventCreationView.swift
Daggerpov Jan 19, 2025
6ce21a0
formatting and proper binding values
Daggerpov Jan 19, 2025
254658e
2 hours added to end time
Daggerpov Jan 19, 2025
ab4f5a6
Adjusting spacing + text font styling
Daggerpov Jan 19, 2025
e49274b
Moved helper functions into view model
Daggerpov Jan 19, 2025
b4ac422
matching datepicker background to time pickers
Daggerpov Jan 19, 2025
c50d068
Breaking up UI components
Daggerpov Jan 19, 2025
6b82bcf
formatting today's date as "today" instead of raw date
Daggerpov Jan 19, 2025
84e859c
design all good
Daggerpov Jan 19, 2025
6abee0d
date formatting without time working
Daggerpov Jan 19, 2025
1298988
UI wrap up from figma (#87)
Daggerpov Jan 19, 2025
9330362
Essentially cherry-picking #81
Daggerpov Jan 19, 2025
21d4639
split `eventCreationPopupView` & `eventDescriptionPopupView` into com…
Daggerpov Jan 19, 2025
f509210
Split off popup views with passed in vars & functions as callbacks
Daggerpov Jan 19, 2025
c372199
Revert "Split off popup views with passed in vars & functions as call…
Daggerpov Jan 19, 2025
39614ba
split off some components
Daggerpov Jan 19, 2025
76a04ab
Fixed bug with mapview creation popup dismissal
Daggerpov Jan 19, 2025
236af6b
Removing old friends view models
Daggerpov Jan 21, 2025
3783868
created FriendRequest model
Daggerpov Jan 21, 2025
5ae91e8
Create FriendsTabViewModel.swift
Daggerpov Jan 21, 2025
82724e1
Fixed friend req
Daggerpov Jan 21, 2025
57a3518
instantiating view model and fetching all data on appear
Daggerpov Jan 21, 2025
16ad1a7
fetching data from view on appear
Daggerpov Jan 21, 2025
8719ff4
setup all api calls for `FriendsTabViewModel`
Daggerpov Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions Spawn-App-iOS-SwiftUI/Models/ChatMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
class ChatMessage: Identifiable, Codable {
var id: UUID
var content: String
var timestamp: String // TODO: change data type alter
var timestamp: Date
var userSender: User
var eventId: UUID
// do I even need an `event` var here, if each `Event` has a list of chats?
Expand All @@ -19,7 +19,7 @@ class ChatMessage: Identifiable, Codable {
// tech note: in user's view of event, check if that user is in
// the `ChatMessage`'s `likedBy` array (`[User]`)

init(id: UUID, content: String, timestamp: String, userSender: User, eventId: UUID, likedBy: [User]? = nil) {
init(id: UUID, content: String, timestamp: Date, userSender: User, eventId: UUID, likedBy: [User]? = nil) {
self.id = id
self.content = content
self.timestamp = timestamp
Expand All @@ -30,12 +30,18 @@ class ChatMessage: Identifiable, Codable {
}

extension ChatMessage {
static let guysWya: ChatMessage = ChatMessage(
id: UUID(),
content: "yo guys, wya?",
timestamp: "2 minutes ago",
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter
}()
static let guysWya: ChatMessage = ChatMessage(
id: UUID(),
content: "yo guys, wya?",
timestamp: Date().addingTimeInterval(-120), // 2 minutes ago
userSender: User.michael,
eventId: Event.mockDinnerEvent.id,
likedBy: User.mockUsers
)
likedBy: User.mockUsers
)
}
158 changes: 81 additions & 77 deletions Spawn-App-iOS-SwiftUI/Models/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ class Event: Identifiable, Codable {
var id: UUID

// MARK: Info
var title: String
var startTime: String? // TODO: change to proper time later
var endTime: String? // TODO: change to proper time later
var location: Location?
var startTime: Date? // TODO: change to proper time later
var endTime: Date? // TODO: change to proper time later
var title: String?
var location: Location?
var note: String? // this corresponds to Figma design "my place at 10? I'm cooking guys" note in event

// MARK: Relations
var creator: User
var creator: User?

// tech note: I'll be able to check if current user is in an event's partipants to determine which symbol to show in feed
var participants: [User]?

Expand All @@ -31,12 +31,12 @@ class Event: Identifiable, Codable {

init(
id: UUID,
title: String,
startTime: String? = nil,
endTime: String? = nil,
title: String? = nil,
startTime: Date? = nil,
endTime: Date? = nil,
location: Location? = nil,
note: String? = nil,
creator: User,
creator: User? = User.danielAgapov,
participants: [User]? = nil,
chatMessages: [ChatMessage]? = nil,
invited: [User]? = nil
Expand All @@ -55,11 +55,17 @@ class Event: Identifiable, Codable {
}

extension Event {
static let iso8601DateFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()

static let mockDinnerEvent: Event = Event(
id: UUID(),
title: "Dinner time!!!!!!",
startTime: "10:00 PM",
endTime: "11:30 PM",
startTime: iso8601DateFormatter.date(from: "2025-01-10T22:00:00Z"),
endTime: iso8601DateFormatter.date(from: "2025-01-10T23:30:00Z"),
location: Location(id: UUID(), name: "Gather - Place Vanier", latitude: 49.26468617023799, longitude: -123.25859833051356),
note: "let's eat!",
creator: User.jennifer,
Expand All @@ -70,75 +76,73 @@ extension Event {
User.michael
]
)
static let mockEvents: [Event] = [
mockDinnerEvent,
Event(
id: UUID(),
title: "wanna run 5k with me?",
startTime: "04:00 PM",
location: Location(id: UUID(), name: "Wesbrook Mall", latitude: 49.25997722657244, longitude: -123.23986523529379),
creator: User.danielAgapov,
participants: [User.danielAgapov, User.jennifer, User.shannon, User.haley, User.danielLee],
chatMessages: [
ChatMessage(
id: UUID(),

static let mockEvents: [Event] = [
mockDinnerEvent,
Event(
id: UUID(),
title: "wanna run 5k with me?",
startTime: iso8601DateFormatter.date(from: "2025-01-11T16:00:00Z"),
location: Location(id: UUID(), name: "Wesbrook Mall", latitude: 49.25997722657244, longitude: -123.23986523529379),
creator: User.danielAgapov,
participants: [User.danielAgapov, User.jennifer, User.shannon, User.haley, User.danielLee],
chatMessages: [
ChatMessage(
id: UUID(),
content: "yo guys, wya?",
timestamp: "2 minutes ago",
timestamp: iso8601DateFormatter.date(from: "2025-01-11T16:02:00Z")!,
userSender: User.danielAgapov,
eventId: Event.mockDinnerEvent.id
),
eventId: mockDinnerEvent.id
),
ChatMessage(
id: UUID(),
content: "I just saw you",
timestamp: "30 seconds ago",
timestamp: iso8601DateFormatter.date(from: "2025-01-11T16:02:30Z")!,
userSender: User.danielLee,
eventId: mockDinnerEvent
.id)
]
),
Event(
id: UUID(),
title: "playing basketball!!!",
endTime: "07:00 PM",
location: Location(id: UUID(), name: "UBC Student Recreation Centre", latitude: 49.2687302352351, longitude: -123.24897582888525),
note: "let's play basketball!",
creator: User.danielAgapov
),
Event(
id: UUID(),
title: "Im painting rn lol",
startTime: "10:00 AM",
endTime: "11:30 AM",
location: Location(id: UUID(), name: "Ross Drive - Wesbrook Mall", latitude: 49.25189587512135, longitude: -123.237051932404),
creator: User.shannon,
participants: [User.danielLee]

),
// commenting this one out, since its location messes with the map zoom level (zooms it way out)
// TODO: we should find a system to handle this better
// Event(
// id: UUID(),
// title: "Grabbing Udon",
// startTime: "12:00 PM",
// endTime: "02:30 PM",
// location: Location(id: UUID(), name: "Marugame Udon", latitude: 49.28032597998406, longitude: -123.11026665974741),
// creator: User.danielAgapov
// ),
Event(
id: UUID(),
title: "Calendar Party",
startTime: "11:00 PM",
endTime: "02:30 AM",
location: Location(id: UUID(), name: "The Pit - Nest", latitude: 49.26694140754859, longitude: -123.25036565366581),
creator: User.danielLee
),
Event(
id: UUID(),
title: "Gym - Leg Day",
startTime: "10:00 AM",
endTime: "11:30 AM",
location: Location(id: UUID(), name: "UBC Student Recreation Centre", latitude: 49.2687302352351, longitude: -123.24897582888525),
creator: User.michael
)
]
eventId: mockDinnerEvent.id
)
]
),
Event(
id: UUID(),
title: "playing basketball!!!",
endTime: iso8601DateFormatter.date(from: "2025-01-11T19:00:00Z"),
location: Location(id: UUID(), name: "UBC Student Recreation Centre", latitude: 49.2687302352351, longitude: -123.24897582888525),
note: "let's play basketball!",
creator: User.danielAgapov
),
Event(
id: UUID(),
title: "Im painting rn lol",
startTime: iso8601DateFormatter.date(from: "2025-01-11T10:00:00Z"),
endTime: iso8601DateFormatter.date(from: "2025-01-11T11:30:00Z"),
location: Location(id: UUID(), name: "Ross Drive - Wesbrook Mall", latitude: 49.25189587512135, longitude: -123.237051932404),
creator: User.shannon,
participants: [User.danielLee]
),
Event(
id: UUID(),
title: "Grabbing Udon",
startTime: iso8601DateFormatter.date(from: "2025-01-11T12:00:00Z"),
endTime: iso8601DateFormatter.date(from: "2025-01-11T14:30:00Z"),
location: Location(id: UUID(), name: "Marugame Udon", latitude: 49.28032597998406, longitude: -123.11026665974741),
creator: User.danielAgapov
),
Event(
id: UUID(),
title: "Calendar Party",
startTime: iso8601DateFormatter.date(from: "2025-01-11T23:00:00Z"),
endTime: iso8601DateFormatter.date(from: "2025-01-12T02:30:00Z"),
location: Location(id: UUID(), name: "The Pit - Nest", latitude: 49.26694140754859, longitude: -123.25036565366581),
creator: User.danielLee
),
Event(
id: UUID(),
title: "Gym - Leg Day",
startTime: iso8601DateFormatter.date(from: "2025-01-11T10:00:00Z"),
endTime: iso8601DateFormatter.date(from: "2025-01-11T11:30:00Z"),
location: Location(id: UUID(), name: "UBC Student Recreation Centre", latitude: 49.2687302352351, longitude: -123.24897582888525),
creator: User.michael
)
]
}
25 changes: 25 additions & 0 deletions Spawn-App-iOS-SwiftUI/Models/FriendRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// FriendRequest.swift
// Spawn-App-iOS-SwiftUI
//
// Created by Daniel Agapov on 2025-01-20.
//

import Foundation

/// as defined in the back-end `FriendRequestDTO.java`
struct FriendRequest: Identifiable, Codable, Hashable {
static func == (lhs: FriendRequest, rhs: FriendRequest) -> Bool {
return lhs.id == rhs.id
}

var id: UUID
var senderUserId: UUID
var receiverUserId: UUID

init (id: UUID, senderUserId: UUID, receiverUserId: UUID) {
self.id = id
self.senderUserId = senderUserId
self.receiverUserId = receiverUserId
}
}
22 changes: 14 additions & 8 deletions Spawn-App-iOS-SwiftUI/Services/API/APIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@

import Foundation

enum APIError: Error {
enum APIError: LocalizedError {
case failedHTTPRequest(description: String)
case invalidStatusCode(statusCode: Int)
case failedJSONParsing
case invalidData
case URLError
case unknownError(error: Error)

var errorDescription: String {
var errorDescription: String? {
switch self {
case let .failedHTTPRequest(description): return "\(description)"
case let .invalidStatusCode(statusCode): return "Invalid Status Code: \(statusCode)"
case .failedJSONParsing: return "Failed to properly parse JSON received from request"
case .invalidData: return "Invalid data"
case .URLError: return "URL Error: Please check which URL you're trying to access, or if it's potentially down at the moment."
case let .unknownError(error): return "An Unknown Error Occurred Fetching Data: \(error)"
case let .failedHTTPRequest(description):
return description
case let .invalidStatusCode(statusCode):
return "Invalid Status Code: \(statusCode)"
case .failedJSONParsing:
return "Failed to properly parse JSON received from request."
case .invalidData:
return "Invalid data received."
case .URLError:
return "URL Error: Please check the URL or if the server is currently down."
case let .unknownError(error):
return "An unknown error occurred: \(error.localizedDescription)"
}
}
}
67 changes: 65 additions & 2 deletions Spawn-App-iOS-SwiftUI/Services/API/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,44 @@ import Foundation
class APIService: IAPIService {
static var baseURL: String = "https://spawn-app-back-end-production.up.railway.app/api/v1/"

private var errorMessage: String? // TODO: currently not being accessed; maybe use in alert to user
var errorMessage: String? // TODO: currently not being accessed; maybe use in alert to user

// Shared JSONDecoder for decoding data from the backend
private static func makeDecoder() -> JSONDecoder {
let decoder = JSONDecoder()

// Custom date decoding strategy
decoder.dateDecodingStrategy = .custom { decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)

let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

if let date = formatter.date(from: dateString) {
return date
} else {
throw DecodingError.dataCorruptedError(in: container,
debugDescription: "Invalid date format: \(dateString)")
}
}
return decoder
}

// Shared JSONEncoder for encoding data to send to the backend
private static func makeEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .custom { date, encoder in
var container = encoder.singleValueContainer()

let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

let dateString = formatter.string(from: date)
try container.encode(dateString)
}
return encoder
}

internal func fetchData<T: Decodable>(from url: URL) async throws -> T where T: Decodable {
let (data, response) = try await URLSession.shared.data(from: url)
Expand All @@ -23,7 +60,7 @@ class APIService: IAPIService {
}

guard httpResponse.statusCode == 200 else {
errorMessage = "invalid status code for \(url)"
errorMessage = "invalid status code \(httpResponse.statusCode) for \(url)"
print(errorMessage ?? "no error message to log")

throw APIError.invalidStatusCode(statusCode: httpResponse.statusCode)
Expand All @@ -38,4 +75,30 @@ class APIService: IAPIService {
throw APIError.failedJSONParsing
}
}

internal func sendData<T: Encodable>(_ object: T, to url: URL) async throws {
let encoder = APIService.makeEncoder()
let encodedData = try encoder.encode(object)

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = encodedData

let (_, response) = try await URLSession.shared.data(for: request)

guard let httpResponse = response as? HTTPURLResponse else {
errorMessage = "HTTP request failed for \(url)"
print(errorMessage ?? "no error message to log")

throw APIError.failedHTTPRequest(description: "The HTTP request has failed.")
}

guard httpResponse.statusCode == 200 else {
errorMessage = "invalid status code \(httpResponse.statusCode) for \(url)"
print(errorMessage ?? "no error message to log")

throw APIError.invalidStatusCode(statusCode: httpResponse.statusCode)
}
}
}
3 changes: 3 additions & 0 deletions Spawn-App-iOS-SwiftUI/Services/API/IAPIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import Foundation

protocol IAPIService {
var errorMessage: String? { get set }
/// generic function for fetching data from API, given a model of type, T
func fetchData<T: Decodable>(from url: URL) async throws -> T where T: Decodable
/// generic function for sending data to an API, given a model of type, T
func sendData<T: Encodable>(_ object: T, to url: URL) async throws
}
Loading