fix contact

This commit is contained in:
cheykrym 2025-12-03 23:10:36 +03:00
parent e44d56e71b
commit e9b43e76fa
2 changed files with 84 additions and 17 deletions

View File

@ -25,6 +25,11 @@ struct ContactPayload: Decodable {
let createdAt: Date
}
struct ContactsListPayload: Decodable {
let items: [ContactPayload]
let hasMore: Bool
}
final class ContactsService {
private let client: NetworkClient
private let decoder: JSONDecoder
@ -36,16 +41,20 @@ final class ContactsService {
self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
}
func fetchContacts(completion: @escaping (Result<[ContactPayload], Error>) -> Void) {
func fetchContacts(limit: Int, offset: Int, completion: @escaping (Result<ContactsListPayload, Error>) -> Void) {
client.request(
path: "/v1/user/contact/list",
method: .get,
query: [
"limit": String(limit),
"offset": String(offset)
],
requiresAuth: true
) { [decoder] result in
switch result {
case .success(let response):
do {
let apiResponse = try decoder.decode(APIResponse<[ContactPayload]>.self, from: response.data)
let apiResponse = try decoder.decode(APIResponse<ContactsListPayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить контакты.", comment: "Contacts service unexpected status")
completion(.failure(ContactsServiceError.unexpectedStatus(message)))
@ -71,9 +80,9 @@ final class ContactsService {
}
}
func fetchContacts() async throws -> [ContactPayload] {
func fetchContacts(limit: Int, offset: Int) async throws -> ContactsListPayload {
try await withCheckedThrowingContinuation { continuation in
fetchContacts { result in
fetchContacts(limit: limit, offset: offset) { result in
continuation.resume(with: result)
}
}

View File

@ -5,9 +5,13 @@ struct ContactsTab: View {
@State private var contacts: [Contact] = []
@State private var isLoading = false
@State private var loadError: String?
@State private var pagingError: String?
@State private var activeAlert: ContactsAlert?
@State private var hasMore = true
@State private var offset = 0
private let contactsService = ContactsService()
private let pageSize = 25
var body: some View {
List {
@ -20,7 +24,7 @@ struct ContactsTab: View {
} else if contacts.isEmpty {
emptyState
} else {
ForEach(contacts) { contact in
ForEach(Array(contacts.enumerated()), id: \.element.id) { index, contact in
Button {
showContactPlaceholder(for: contact)
} label: {
@ -57,16 +61,29 @@ struct ContactsTab: View {
}
}
.listRowInsets(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
.onAppear {
if index >= contacts.count - 5 {
Task {
await loadContacts(reset: false)
}
}
}
}
if isLoading && !contacts.isEmpty {
loadingState
} else if let pagingError, !contacts.isEmpty {
pagingErrorState(pagingError)
}
}
}
.background(Color(UIColor.systemBackground))
.listStyle(.plain)
.task {
await loadContacts()
await loadContacts(reset: false)
}
.refreshable {
await loadContacts()
await refreshContacts()
}
.alert(item: $activeAlert) { alert in
switch alert {
@ -106,7 +123,25 @@ struct ContactsTab: View {
.font(.subheadline)
.foregroundColor(.orange)
Spacer()
Button(action: { Task { await loadContacts() } }) {
Button(action: { Task { await refreshContacts() } }) {
Text(NSLocalizedString("Обновить", comment: "Contacts retry button"))
.font(.subheadline)
}
}
.padding(.vertical, 10)
.listRowInsets(EdgeInsets(top: 10, leading: 12, bottom: 10, trailing: 12))
.listRowSeparator(.hidden)
}
private func pagingErrorState(_ message: String) -> some View {
HStack(alignment: .center, spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.orange)
Text(message)
.font(.subheadline)
.foregroundColor(.orange)
Spacer()
Button(action: { Task { await loadContacts(reset: false) } }) {
Text(NSLocalizedString("Обновить", comment: "Contacts retry button"))
.font(.subheadline)
}
@ -136,20 +171,43 @@ struct ContactsTab: View {
}
@MainActor
private func loadContacts() async {
if isLoading {
return
}
private func refreshContacts() async {
hasMore = true
offset = 0
pagingError = nil
loadError = nil
contacts.removeAll()
await loadContacts(reset: true)
}
@MainActor
private func loadContacts(reset: Bool) async {
if isLoading { return }
if !reset && !hasMore { return }
isLoading = true
loadError = nil
if offset == 0 {
loadError = nil
}
pagingError = nil
do {
let payloads = try await contactsService.fetchContacts()
contacts = payloads.map(Contact.init)
let payload = try await contactsService.fetchContacts(limit: pageSize, offset: offset)
let newContacts = payload.items.map(Contact.init)
if reset {
contacts = newContacts
} else {
contacts.append(contentsOf: newContacts)
}
offset += newContacts.count
hasMore = payload.hasMore
} catch {
loadError = error.localizedDescription
// activeAlert = .error(message: error.localizedDescription)
let message = error.localizedDescription
if contacts.isEmpty {
loadError = message
} else {
pagingError = message
}
if AppConfig.DEBUG { print("[ContactsTab] load contacts failed: \(error)") }
}