Skip to content
Open
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
Binary file modified .DS_Store
Binary file not shown.
58 changes: 23 additions & 35 deletions Resell.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@
2E8A5A292DBCC82E00B1F281 /* TinyHTTPServer in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5A282DBCC82E00B1F281 /* TinyHTTPServer */; };
2E8A5A5D2DBCC87500B1F281 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5A5C2DBCC87500B1F281 /* GoogleSignIn */; };
2E8A5A5F2DBCC87500B1F281 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5A5E2DBCC87500B1F281 /* GoogleSignInSwift */; };
2E8A5A622DBCC87F00B1F281 /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5A612DBCC87F00B1F281 /* Flow */; };
2E8A5A652DBCC8A100B1F281 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5A642DBCC8A100B1F281 /* Kingfisher */; };
2E8A5A662DBCCB0400B1F281 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C191D1B2CFD196C0001D2E0 /* Notification.swift */; };
2E8A5A672DBCCB0900B1F281 /* Chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0961AFD2D6E47F200DCC293 /* Chat.swift */; };
Expand Down Expand Up @@ -147,31 +146,18 @@
2E8A5AAE2DBCD16500B1F281 /* FirebaseStorageCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5AAD2DBCD16500B1F281 /* FirebaseStorageCombine-Community */; };
2E8A5AB02DBCD16500B1F281 /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 2E8A5AAF2DBCD16500B1F281 /* FirebaseVertexAI */; };
2E8A5AB12DBCD68200B1F281 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D051D8162D7E2E0500C089AF /* GoogleService-Info.plist */; };
2E8A5AB22DBCD68700B1F281 /* resell-service.json in Resources */ = {isa = PBXBuildFile; fileRef = D0B4A25F2DA7184C00A1722C /* resell-service.json */; };
2E8A5AB22DBCD68700B1F281 /* (null) in Resources */ = {isa = PBXBuildFile; };
2E8A5AB52DBD5B4300B1F281 /* SavedRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8A5AB42DBD5B3200B1F281 /* SavedRow.swift */; };
2E8C3D972DBD8A8B0074BFAB /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1CE75D2CF6D04F00D38C25 /* NotificationsView.swift */; };
2E8C3D992DBEE07B0074BFAB /* DetailedFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8C3D982DBEE06E0074BFAB /* DetailedFilterView.swift */; };
2E8C3D9D2DBEE35D0074BFAB /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8C3D9C2DBEE3590074BFAB /* SearchBar.swift */; };
2EBB64182D8B783800CCAC48 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBB64172D8B783600CCAC48 /* Filter.swift */; };
2ECB2F652E749ADD00CAACA2 /* ForYouView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECB2F642E749AD900CAACA2 /* ForYouView.swift */; };
2ECB2F672E74E03700CAACA2 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECB2F662E74E02F00CAACA2 /* SearchViewModel.swift */; };
D037B9E62D7E308C00EF3024 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9E52D7E308C00EF3024 /* FirebaseAnalytics */; };
D037B9E82D7E308C00EF3024 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9E72D7E308C00EF3024 /* FirebaseAnalyticsOnDeviceConversion */; };
D037B9EA2D7E308C00EF3024 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9E92D7E308C00EF3024 /* FirebaseAnalyticsWithoutAdIdSupport */; };
D037B9EC2D7E308C00EF3024 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9EB2D7E308C00EF3024 /* FirebaseAppCheck */; };
D037B9EE2D7E308C00EF3024 /* FirebaseAppDistribution-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9ED2D7E308C00EF3024 /* FirebaseAppDistribution-Beta */; };
D037B9F02D7E313100EF3024 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9EF2D7E313100EF3024 /* FirebaseMessaging */; };
D037B9F22D7E314D00EF3024 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9F12D7E314D00EF3024 /* FirebaseFirestore */; };
D037B9F42D7E317700EF3024 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = D037B9F32D7E317700EF3024 /* FirebaseAuth */; };
D043ED6D2D70CBEB00389DC1 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = D043ED6C2D70CBEB00389DC1 /* GoogleSignIn */; };
D043ED6F2D70CBEB00389DC1 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D043ED6E2D70CBEB00389DC1 /* GoogleSignInSwift */; };
2EEAAB2B2F1B07B20006FF5C /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 2EEAAB2A2F1B07B20006FF5C /* Flow */; };
C6B37F592E970D7700A564DB /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B37F582E970D7700A564DB /* FiltersViewModel.swift */; };
D051D8172D7E2E0500C089AF /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D051D8162D7E2E0500C089AF /* GoogleService-Info.plist */; };
D0961AF82D6E28D600DCC293 /* MessageDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0961AF72D6E28D300DCC293 /* MessageDocument.swift */; };
D0961AFC2D6E42D500DCC293 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0961AFB2D6E42D100DCC293 /* Message.swift */; };
D0961AFE2D6E47F500DCC293 /* Chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0961AFD2D6E47F200DCC293 /* Chat.swift */; };
D0A25DEE2E5804A900607E1F /* EmptyStateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A25DED2E5804A900607E1F /* EmptyStateModifier.swift */; };
D0DAEF292D6F607300641151 /* MessageCluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DAEF282D6F606C00641151 /* MessageCluster.swift */; };
D0DAEF2B2D6FF48800641151 /* MessagesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DAEF2A2D6FF47E00641151 /* MessagesViewModel.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -304,8 +290,8 @@
2EBB64172D8B783600CCAC48 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
2ECB2F642E749AD900CAACA2 /* ForYouView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForYouView.swift; sourceTree = "<group>"; };
2ECB2F662E74E02F00CAACA2 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
C6B37F582E970D7700A564DB /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
D051D8162D7E2E0500C089AF /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
D063A0182DBC268700F17A9C /* Untitled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Untitled.swift; sourceTree = "<group>"; };
D0961AF72D6E28D300DCC293 /* MessageDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDocument.swift; sourceTree = "<group>"; };
D0961AFB2D6E42D100DCC293 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
D0961AFD2D6E47F200DCC293 /* Chat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chat.swift; sourceTree = "<group>"; };
Expand All @@ -327,6 +313,7 @@
2E8A5A922DBCD16500B1F281 /* FirebaseCrashlytics in Frameworks */,
2E8A5A5D2DBCC87500B1F281 /* GoogleSignIn in Frameworks */,
2E8A5AA82DBCD16500B1F281 /* FirebasePerformance in Frameworks */,
2EEAAB2B2F1B07B20006FF5C /* Flow in Frameworks */,
2E8A5A982DBCD16500B1F281 /* FirebaseFirestore in Frameworks */,
2E8A5A292DBCC82E00B1F281 /* TinyHTTPServer in Frameworks */,
2E8A5A842DBCD16500B1F281 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */,
Expand All @@ -343,7 +330,6 @@
2CF3CC7C2D017897001B90B5 /* OAuth2 in Frameworks */,
2E8A5A5F2DBCC87500B1F281 /* GoogleSignInSwift in Frameworks */,
2E8A5AAE2DBCD16500B1F281 /* FirebaseStorageCombine-Community in Frameworks */,
2E8A5A622DBCC87F00B1F281 /* Flow in Frameworks */,
2E8A5A652DBCC8A100B1F281 /* Kingfisher in Frameworks */,
2CF3CC7A2D017897001B90B5 /* OAuth1 in Frameworks */,
2E8A5A9E2DBCD16500B1F281 /* FirebaseFunctionsCombine-Community in Frameworks */,
Expand Down Expand Up @@ -652,6 +638,7 @@
2C525B802CB1F195007D5B8E /* SendFeedbackViewModel.swift */,
2C18FFE92CA1E4C900564577 /* SettingsViewModel.swift */,
2C4DD97C2C98D45B0055D0AB /* SetupProfileViewModel.swift */,
C6B37F582E970D7700A564DB /* FiltersViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand Down Expand Up @@ -735,7 +722,6 @@
2E8A5A282DBCC82E00B1F281 /* TinyHTTPServer */,
2E8A5A5C2DBCC87500B1F281 /* GoogleSignIn */,
2E8A5A5E2DBCC87500B1F281 /* GoogleSignInSwift */,
2E8A5A612DBCC87F00B1F281 /* Flow */,
2E8A5A642DBCC8A100B1F281 /* Kingfisher */,
2E8A5A812DBCD16500B1F281 /* FirebaseAnalytics */,
2E8A5A832DBCD16500B1F281 /* FirebaseAnalyticsOnDeviceConversion */,
Expand All @@ -761,6 +747,7 @@
2E8A5AAB2DBCD16500B1F281 /* FirebaseStorage */,
2E8A5AAD2DBCD16500B1F281 /* FirebaseStorageCombine-Community */,
2E8A5AAF2DBCD16500B1F281 /* FirebaseVertexAI */,
2EEAAB2A2F1B07B20006FF5C /* Flow */,
);
productName = Resell;
productReference = 2C9B4CC72C8FB7B70029DF61 /* Resell.app */;
Expand Down Expand Up @@ -837,9 +824,9 @@
packageReferences = (
2E8A5A212DBCC82E00B1F281 /* XCRemoteSwiftPackageReference "google-auth-library-swift" */,
2E8A5A5B2DBCC87500B1F281 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */,
2E8A5A602DBCC87F00B1F281 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */,
2E8A5A632DBCC8A100B1F281 /* XCRemoteSwiftPackageReference "Kingfisher" */,
2E8A5A802DBCD16500B1F281 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
2EEAAB292F1B07B20006FF5C /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */,
);
productRefGroup = 2C9B4CC82C8FB7B70029DF61 /* Products */;
projectDirPath = "";
Expand All @@ -857,7 +844,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2E8A5AB22DBCD68700B1F281 /* resell-service.json in Resources */,
2E8A5AB22DBCD68700B1F281 /* (null) in Resources */,
D051D8172D7E2E0500C089AF /* GoogleService-Info.plist in Resources */,
2C9EAF702CF26DA00010A44C /* Rubik-Regular.ttf in Resources */,
2C9B4D0C2C90EF1D0029DF61 /* Launch Screen.storyboard in Resources */,
Expand Down Expand Up @@ -915,6 +902,7 @@
2C9337542C935C9500818C8E /* ChatsView.swift in Sources */,
2E8A5AB52DBD5B4300B1F281 /* SavedRow.swift in Sources */,
2CD7CAB92CE937B10056209E /* Listing.swift in Sources */,
C6B37F592E970D7700A564DB /* FiltersViewModel.swift in Sources */,
2CD6CA8C2CB48286005A4F78 /* PopupModal.swift in Sources */,
2CF3561F2CDE93E00045A173 /* EditProfileViewModel.swift in Sources */,
2C02B3992CC040AE0020DF90 /* PriceInputView.swift in Sources */,
Expand Down Expand Up @@ -1351,14 +1339,6 @@
minimumVersion = 8.0.0;
};
};
2E8A5A602DBCC87F00B1F281 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/tevelee/SwiftUI-Flow.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.0.2;
};
};
2E8A5A632DBCC8A100B1F281 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
Expand All @@ -1375,6 +1355,14 @@
minimumVersion = 11.12.0;
};
};
2EEAAB292F1B07B20006FF5C /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/tevelee/SwiftUI-Flow.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.1.1;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand Down Expand Up @@ -1424,11 +1412,6 @@
package = 2E8A5A5B2DBCC87500B1F281 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */;
productName = GoogleSignInSwift;
};
2E8A5A612DBCC87F00B1F281 /* Flow */ = {
isa = XCSwiftPackageProductDependency;
package = 2E8A5A602DBCC87F00B1F281 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */;
productName = Flow;
};
2E8A5A642DBCC8A100B1F281 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 2E8A5A632DBCC8A100B1F281 /* XCRemoteSwiftPackageReference "Kingfisher" */;
Expand Down Expand Up @@ -1554,6 +1537,11 @@
package = 2E8A5A802DBCD16500B1F281 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseVertexAI;
};
2EEAAB2A2F1B07B20006FF5C /* Flow */ = {
isa = XCSwiftPackageProductDependency;
package = 2EEAAB292F1B07B20006FF5C /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */;
productName = Flow;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 2C9B4CBF2C8FB7B70029DF61 /* Project object */;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified Resell/.DS_Store
Binary file not shown.
128 changes: 122 additions & 6 deletions Resell/API/NetworkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import Combine
import Foundation
import os

/// Empty body for POST requests that don't require a body
struct EmptyBody: Codable {}

class NetworkManager: APIClient {

// MARK: - Singleton Instance
Expand All @@ -21,9 +24,23 @@ class NetworkManager: APIClient {

// MARK: - Properties

private let hostURL: String = Keys.devServerURL
private let hostURL: String = Keys.localServerURL
private let maxAttempts = 2

/// ISO8601 decoder for date parsing
private lazy var iso8601Decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return decoder
}()

/// ISO8601 encoder for date formatting
private lazy var iso8601Encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
return encoder
}()

// MARK: - Init

private init() { }
Expand Down Expand Up @@ -480,13 +497,112 @@ class NetworkManager: APIClient {

return try await post(url: url, body: image)
}

// MARK: - Notifications Networking Functions

// func createNotif(notifBody: Notification) async throws -> ListingResponse {
// let url = try constructURL(endpoint: "/notif/")
//
// return try await post(url: url, body: notifBody)
// }
/// Custom GET for notifications with ISO8601 date decoding
private func getNotifications(url: URL, attempt: Int = 1) async throws -> [Notifications] {
let request = try createRequest(url: url, method: "GET")

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

do {
try handleResponse(data: data, response: response)
} catch {
return try await handleNetworkError(error, attempt: attempt) {
try await getNotifications(url: url, attempt: attempt + 1)
}
}


// Try decoding as array first (direct response)
if let notifications = try? iso8601Decoder.decode([Notifications].self, from: data) {
return notifications
}

// Try decoding as wrapped response { "notifications": [...] }
if let wrapped = try? iso8601Decoder.decode(NotificationsResponse.self, from: data) {
return wrapped.notifications
}

// If both fail, throw the actual decoding error for debugging
return try iso8601Decoder.decode([Notifications].self, from: data)
}

/// Custom POST for notifications with ISO8601 date decoding
private func postNotification<T: Decodable>(url: URL, body: some Encodable, attempt: Int = 1) async throws -> T {
var request = try createRequest(url: url, method: "POST")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try iso8601Encoder.encode(body)

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

do {
try handleResponse(data: data, response: response)
} catch {
return try await handleNetworkError(error, attempt: attempt) {
try await postNotification(url: url, body: body, attempt: attempt + 1)
}
}


return try iso8601Decoder.decode(T.self, from: data)
}

/// Get unread notifications
func getNewNotifications() async throws -> [Notifications] {
let url = try constructURL(endpoint: "/notif/new")
return try await getNotifications(url: url)
}

/// Get recent notifications (last 10)
func getRecentNotifications() async throws -> [Notifications] {
let url = try constructURL(endpoint: "/notif/recent")
return try await getNotifications(url: url)
}

/// Get notifications from last 7 days
func getLast7DaysNotifications() async throws -> [Notifications] {
let url = try constructURL(endpoint: "/notif/last7days")
return try await getNotifications(url: url)
}

/// Get notifications from last 30 days
func getLast30DaysNotifications() async throws -> [Notifications] {
let url = try constructURL(endpoint: "/notif/last30days")
return try await getNotifications(url: url)
}

/// Mark a notification as read
func markNotificationAsRead(notificationId: String) async throws -> Notifications {
let url = try constructURL(endpoint: "/notif/read/\(notificationId)")

var request = try createRequest(url: url, method: "POST")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

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

do {
try handleResponse(data: data, response: response)
} catch {
throw error
}


// Try different response formats
if let wrapped = try? iso8601Decoder.decode(MarkReadResponse.self, from: data) {
return wrapped.notification
}

if let wrapped = try? iso8601Decoder.decode(SingleNotificationResponse.self, from: data) {
return wrapped.notification
}

// Try direct notification
return try iso8601Decoder.decode(Notifications.self, from: data)
}

}



2 changes: 2 additions & 0 deletions Resell/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
<string>$(RESELL_DEV_URL)</string>
<key>RESELL_PROD_URL</key>
<string>$(RESELL_PROD_URL)</string>
<key>RESELL_LOCAL_URL</key>
<string>$(RESELL_LOCAL_URL)</string>
<key>UIAppFonts</key>
<array>
<string>Rubik-Regular.ttf</string>
Expand Down
2 changes: 1 addition & 1 deletion Resell/Models/MessageCluster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Peter Bidoshi on 2/26/25.
//

import SwiftUICore
import SwiftUI

struct MessageCluster: Equatable {

Expand Down
Loading