Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions Spawn-App-iOS-SwiftUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
Expand Down Expand Up @@ -449,6 +450,7 @@
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 6.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
};
Expand Down Expand Up @@ -485,7 +487,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand Down Expand Up @@ -521,7 +523,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_PRECOMPILE_BRIDGING_HEADER = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand All @@ -538,7 +540,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "danielagapov.Spawn-App-iOS-SwiftUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Spawn-App-iOS-SwiftUI.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Spawn-App-iOS-SwiftUI";
};
Expand All @@ -556,7 +558,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "danielagapov.Spawn-App-iOS-SwiftUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Spawn-App-iOS-SwiftUI.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Spawn-App-iOS-SwiftUI";
};
Expand All @@ -572,7 +574,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "danielagapov.Spawn-App-iOS-SwiftUIUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = "Spawn-App-iOS-SwiftUI";
};
Expand All @@ -588,7 +590,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "danielagapov.Spawn-App-iOS-SwiftUIUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = "Spawn-App-iOS-SwiftUI";
};
Expand Down
9 changes: 5 additions & 4 deletions Spawn-App-iOS-SwiftUI/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ struct ContentView: View {
.zIndex(999) // Below notifications but above everything else
}
}
.testInAppNotification() // Triple-tap anywhere to test in-app notifications
}

// MARK: - Deep Link Handling
Expand All @@ -271,7 +270,8 @@ struct ContentView: View {
selectedTabsEnum = .home

// Add a small delay to ensure tab switching completes before setting deep link state
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
Task { @MainActor in
try? await Task.sleep(for: .seconds(0.1))
// Set up the deep linked activity state
deepLinkedActivityId = activityId
shouldShowDeepLinkedActivity = true
Expand Down Expand Up @@ -309,7 +309,7 @@ struct ContentView: View {

if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
// Show success notification
DispatchQueue.main.async {
Task { @MainActor in
InAppNotificationManager.shared.showNotification(
title: "You're invited!",
message: "You've been added to this activity. Check it out!",
Expand All @@ -335,7 +335,8 @@ struct ContentView: View {
selectedTabsEnum = .friends

// Add a small delay to ensure tab switching completes before setting deep link state
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
Task { @MainActor in
try? await Task.sleep(for: .seconds(0.1))
// Set up the deep linked profile state
deepLinkedProfileId = profileId
shouldShowDeepLinkedProfile = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct ActivityCreationResponseDTO: Codable {
struct ActivityCreationResponseDTO: Codable, Sendable {
let activity: FullFeedActivityDTO
let friendSuggestion: ActivityTypeFriendSuggestionDTO?

Expand All @@ -17,7 +17,7 @@ struct ActivityCreationResponseDTO: Codable {
}
}

struct ActivityTypeFriendSuggestionDTO: Codable {
struct ActivityTypeFriendSuggestionDTO: Codable, Sendable {
let activityTypeId: UUID
let activityTypeTitle: String
let suggestedFriends: [BaseUserDTO]
Expand Down
8 changes: 5 additions & 3 deletions Spawn-App-iOS-SwiftUI/Models/DTOs/Activity/ActivityDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

import Foundation

// Unified ActivityDTO that matches the back-end ActivityDTO structure
// Replaces both ActivityCreationDTO and handles both creation and updates
class ActivityDTO: Identifiable, Codable, Equatable, ObservableObject {
/// Unified ActivityDTO that matches the back-end ActivityDTO structure.
/// Replaces both ActivityCreationDTO and handles both creation and updates.
/// Uses @unchecked Sendable because it's a class with mutable state that needs to cross
/// async boundaries. The mutable state is only modified on MainActor in practice.
class ActivityDTO: Identifiable, Codable, Equatable, ObservableObject, @unchecked Sendable {
static func == (lhs: ActivityDTO, rhs: ActivityDTO) -> Bool {
return lhs.id == rhs.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// DTO for partial activity updates using PATCH requests
struct ActivityPartialUpdateDTO: Codable {
struct ActivityPartialUpdateDTO: Codable, Sendable {
var title: String?
var icon: String?
var startTime: String? // ISO8601 formatted string
Expand Down
29 changes: 14 additions & 15 deletions Spawn-App-iOS-SwiftUI/Models/DTOs/Activity/ActivityTypeDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//
import Foundation

class ActivityTypeDTO: Identifiable, Codable, Equatable {
struct ActivityTypeDTO: Identifiable, Codable, Equatable, Sendable {
var id: UUID
var title: String
var associatedFriends: [BaseUserDTO]
Expand All @@ -27,15 +27,10 @@ class ActivityTypeDTO: Identifiable, Codable, Equatable {
self.ownerUserId = ownerUserId
self.isPinned = isPinned
}

// MARK: - Equatable
static func == (lhs: ActivityTypeDTO, rhs: ActivityTypeDTO) -> Bool {
return lhs.id == rhs.id
}
}

// DTO for batch updating activity types
struct BatchActivityTypeUpdateDTO: Codable {
struct BatchActivityTypeUpdateDTO: Codable, Sendable {
let updatedActivityTypes: [ActivityTypeDTO]
let deletedActivityTypeIds: [UUID]

Expand All @@ -46,19 +41,23 @@ struct BatchActivityTypeUpdateDTO: Codable {
}

extension ActivityTypeDTO {
static var mockChillActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(), title: "Chill", icon: "🛋️", associatedFriends: [BaseUserDTO.danielLee, BaseUserDTO.danielAgapov],
static let mockChillActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(uuidString: "A1B2C3D4-E5F6-7890-ABCD-EF1234567890") ?? UUID(),
title: "Chill", icon: "🛋️", associatedFriends: [BaseUserDTO.danielLee, BaseUserDTO.danielAgapov],
orderNum: 0, isPinned: false
)
static var mockFoodActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(), title: "Food", icon: "🍽️",
static let mockFoodActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(uuidString: "B2C3D4E5-F678-9012-BCDE-F12345678901") ?? UUID(),
title: "Food", icon: "🍽️",
associatedFriends: [BaseUserDTO.danielLee, BaseUserDTO.haley, BaseUserDTO.haley], orderNum: 1, isPinned: true)
static var mockActiveActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(), title: "Active", icon: "🏃",
static let mockActiveActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(uuidString: "C3D4E5F6-7890-1234-CDEF-123456789012") ?? UUID(),
title: "Active", icon: "🏃",
associatedFriends: [BaseUserDTO.haley, BaseUserDTO.danielLee, BaseUserDTO.haley, BaseUserDTO.danielLee],
orderNum: 2, isPinned: false)
static var mockStudyActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(), title: "Study", icon: "✏️", associatedFriends: BaseUserDTO.mockUsers, orderNum: 3, isPinned: false)
static let mockStudyActivityType: ActivityTypeDTO = ActivityTypeDTO(
id: UUID(uuidString: "D4E5F678-9012-3456-DEF1-234567890123") ?? UUID(),
title: "Study", icon: "✏️", associatedFriends: BaseUserDTO.mockUsers, orderNum: 3, isPinned: false)

/// Creates a new ActivityTypeDTO instance with default values for creating a new activity type
static func createNew() -> ActivityTypeDTO {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

import Foundation

class FullFeedActivityDTO: Identifiable, Codable, Equatable, ObservableObject {
/// Full activity DTO with all details for display in the feed.
/// Uses @unchecked Sendable because it's a class with @Published properties that needs to cross
/// async boundaries. The mutable state is only modified on MainActor in practice.
class FullFeedActivityDTO: Identifiable, Codable, Equatable, ObservableObject, @unchecked Sendable {
static func == (lhs: FullFeedActivityDTO, rhs: FullFeedActivityDTO) -> Bool {
return lhs.id == rhs.id
}
Expand Down
6 changes: 1 addition & 5 deletions Spawn-App-iOS-SwiftUI/Models/DTOs/Activity/LocationDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@

import Foundation

class LocationDTO: Identifiable, Codable, Equatable {
static func == (lhs: LocationDTO, rhs: LocationDTO) -> Bool {
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.name == rhs.name
}

struct LocationDTO: Identifiable, Codable, Equatable, Sendable {
var id: UUID
var name: String
var latitude: Double
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

class ProfileActivityDTO: FullFeedActivityDTO {
class ProfileActivityDTO: FullFeedActivityDTO, @unchecked Sendable {
var isPastActivity: Bool

// Custom initializer to handle the additional property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct BlockedUserCreationDTO: Codable {
struct BlockedUserCreationDTO: Codable, Sendable {
let blockerId: UUID
let blockedId: UUID
let reason: String
Expand Down
2 changes: 1 addition & 1 deletion Spawn-App-iOS-SwiftUI/Models/DTOs/BlockedUserDTO.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct BlockedUserDTO: Codable {
struct BlockedUserDTO: Codable, Sendable {
let id: UUID
let blockerId: UUID
let blockedId: UUID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct CacheValidationResponse: Codable {
struct CacheValidationResponse: Codable, Sendable {
var invalidate: Bool
var updatedItems: Data?
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import SwiftUI

struct CalendarActivityDTO: Codable, Identifiable {
struct CalendarActivityDTO: Codable, Identifiable, Sendable {
let id: UUID
let date: String // ISO format: YYYY-MM-DD (matches backend)
let title: String?
Expand Down
3 changes: 2 additions & 1 deletion Spawn-App-iOS-SwiftUI/Models/DTOs/CreateChatMessageDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import Foundation

// Will correspond to `CreateChatMessageDTO` in the back-end:
class CreateChatMessageDTO: Identifiable, Codable {
struct CreateChatMessageDTO: Identifiable, Codable, Sendable {
var id: UUID = UUID()
var content: String
var senderUserId: UUID
var activityId: UUID
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct CreateReportedContentDTO: Codable {
struct CreateReportedContentDTO: Codable, Sendable {
let reporterUserId: UUID
let contentId: UUID
let contentType: EntityType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

struct FetchFeedbackSubmissionDTO: Codable, Identifiable {
struct FetchFeedbackSubmissionDTO: Codable, Identifiable, Sendable {
var id: UUID?
var type: FeedbackType
var fromUserId: UUID?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct FetchReportedContentDTO: Codable, Identifiable {
struct FetchReportedContentDTO: Codable, Identifiable, Sendable {
let id: UUID
let reporterUserId: UUID?
let reporterUsername: String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// as defined in the back-end `CreateFriendRequestDTO.java`
struct CreateFriendRequestDTO: Identifiable, Codable, Hashable {
struct CreateFriendRequestDTO: Identifiable, Codable, Hashable, Sendable {
static func == (lhs: CreateFriendRequestDTO, rhs: CreateFriendRequestDTO) -> Bool {
return lhs.id == rhs.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// as defined in the back-end `FetchFriendRequestDTO.java`
struct FetchFriendRequestDTO: Identifiable, Codable, Hashable {
struct FetchFriendRequestDTO: Identifiable, Codable, Hashable, Sendable {
static func == (lhs: FetchFriendRequestDTO, rhs: FetchFriendRequestDTO) -> Bool {
return lhs.id == rhs.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// as defined in the back-end `FetchSentFriendRequestDTO.java`
struct FetchSentFriendRequestDTO: Identifiable, Codable, Hashable {
struct FetchSentFriendRequestDTO: Identifiable, Codable, Hashable, Sendable {
static func == (lhs: FetchSentFriendRequestDTO, rhs: FetchSentFriendRequestDTO) -> Bool {
return lhs.id == rhs.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

class FullActivityChatMessageDTO: Identifiable, Codable, Equatable {
struct FullActivityChatMessageDTO: Identifiable, Codable, Equatable, Sendable {
var id: UUID
var content: String
var timestamp: Date
Expand All @@ -19,10 +19,6 @@ class FullActivityChatMessageDTO: Identifiable, Codable, Equatable {
// tech note: in user's view of activity, check if that user is in
// the `ChatMessage`'s `likedBy` array (`[User]`)

static func == (lhs: FullActivityChatMessageDTO, rhs: FullActivityChatMessageDTO) -> Bool {
return lhs.id == rhs.id
}

init(
id: UUID, content: String, timestamp: Date, senderUser: BaseUserDTO,
activityId: UUID, likedByUsers: [BaseUserDTO]? = nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Data transfer object for registering a device token with the backend
struct DeviceTokenDTO: Codable {
struct DeviceTokenDTO: Codable, Sendable {
/// The device token from APNS
let token: String

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Data transfer object for managing notification preferences
struct NotificationPreferencesDTO: Codable {
struct NotificationPreferencesDTO: Codable, Sendable {
/// Whether the user wants to receive friend request notifications
let friendRequestsEnabled: Bool

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Defines all notification types used in the app
enum NotificationType: String, Codable {
enum NotificationType: String, Codable, Sendable {
/// Friend request notifications
case friendRequest = "friendRequest"

Expand All @@ -25,7 +25,7 @@ enum NotificationType: String, Codable {
}

/// Utility for building notification data
struct NotificationDataBuilder {
struct NotificationDataBuilder: Sendable {
/// Build notification data for a friend request
static func friendRequest(senderId: UUID, requestId: UUID) -> [String: String] {
return [
Expand Down
Loading