feat: ios mobil arayüz tasarımı

This commit is contained in:
2026-02-11 18:06:35 +03:00
parent 69884db0ab
commit 261b2f58cc
42 changed files with 2501 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
import Foundation
protocol APIClientProtocol {
func get<T: Decodable>(path: String, queryItems: [URLQueryItem], token: String?) async throws -> T
func post<T: Decodable, Body: Encodable>(path: String, body: Body, token: String?) async throws -> T
}
enum APIError: LocalizedError {
case invalidURL
case invalidResponse
case unauthorized
case server(status: Int, message: String)
case decoding(Error)
case transport(Error)
var errorDescription: String? {
switch self {
case .invalidURL: return "Geçersiz URL"
case .invalidResponse: return "Geçersiz sunucu yanıtı"
case .unauthorized: return "Oturum süresi doldu"
case .server(_, let message): return message
case .decoding: return "Sunucu verisi çözümlenemedi"
case .transport(let error): return error.localizedDescription
}
}
}
final class APIClient: APIClientProtocol {
private let baseURL: URL
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
init(baseURL: URL, session: URLSession = .shared) {
self.baseURL = baseURL
self.session = session
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
func get<T: Decodable>(path: String, queryItems: [URLQueryItem] = [], token: String? = nil) async throws -> T {
var request = try buildRequest(path: path, method: "GET", queryItems: queryItems, token: token)
request.httpBody = nil
return try await perform(request)
}
func post<T: Decodable, Body: Encodable>(path: String, body: Body, token: String? = nil) async throws -> T {
var request = try buildRequest(path: path, method: "POST", queryItems: [], token: token)
request.httpBody = try encoder.encode(body)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return try await perform(request)
}
private func buildRequest(path: String, method: String, queryItems: [URLQueryItem], token: String?) throws -> URLRequest {
guard var components = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false) else {
throw APIError.invalidURL
}
if !queryItems.isEmpty {
components.queryItems = queryItems
}
guard let url = components.url else { throw APIError.invalidURL }
var request = URLRequest(url: url)
request.httpMethod = method
request.timeoutInterval = 20
if let token {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
return request
}
private func perform<T: Decodable>(_ request: URLRequest) async throws -> T {
#if DEBUG
print("[API] \(request.httpMethod ?? "") \(request.url?.absoluteString ?? "")")
#endif
do {
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse }
if http.statusCode == 401 { throw APIError.unauthorized }
guard (200...299).contains(http.statusCode) else {
let message = (try? JSONSerialization.jsonObject(with: data) as? [String: Any])?["message"] as? String ?? "Sunucu hatası"
throw APIError.server(status: http.statusCode, message: message)
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw APIError.decoding(error)
}
} catch let error as APIError {
throw error
} catch {
throw APIError.transport(error)
}
}
}
extension Bundle {
var apiBaseURL: URL {
let raw = (object(forInfoDictionaryKey: "API_BASE_URL") as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
// 1) Normal case: full URL in Info.plist / xcconfig.
if let url = URL(string: raw), let host = url.host, !host.isEmpty {
return url
}
// 2) If scheme is missing (e.g. "192.168.1.124:8080"), prepend http://.
if !raw.isEmpty, !raw.contains("://"),
let url = URL(string: "http://\(raw)"),
let host = url.host, !host.isEmpty {
return url
}
// 3) Device-local fallback for current dev network.
if let fallback = URL(string: "http://192.168.1.124:8080") {
#if DEBUG
print("[API] Invalid API_BASE_URL='\(raw)'. Falling back to \(fallback.absoluteString)")
#endif
return fallback
}
// 4) Last resort.
return URL(string: "http://localhost:8080")!
}
}