From 490af24652e8e6cbba49cbe0955ff511aa38dc47 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 26 Jun 2026 08:51:04 +0200 Subject: [PATCH 1/5] wip Signed-off-by: Marino Faggiana --- .../Data/NCManageDatabase+Metadata.swift | 34 +++-- iOSClient/NCGlobal.swift | 1 + iOSClient/Transfers/NCTransfersModel.swift | 113 ++++++++++++--- iOSClient/Transfers/NCTransfersView.swift | 136 ++++++++++++++++-- 4 files changed, 238 insertions(+), 46 deletions(-) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 0135c4dc62..e86ea6115c 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1467,23 +1467,39 @@ extension NCManageDatabase { } } - func getTransferAsync(tranfersSuccess: [tableMetadata]) async -> [tableMetadata] { + func getTransferAsync(tranfersSuccess: [tableMetadata], status: [Int], offset: Int, limit: Int) async -> (metadatas: [tableMetadata], inWaiting: Int, inProgress: Int, inError: Int) { await core.performRealmReadAsync { realm in - let predicate = NSPredicate(format: "status IN %@", NCGlobal.shared.metadataStatusTransfers) + let allTransfers = realm.objects(tableMetadata.self) + .filter("status IN %@", NCGlobal.shared.metadataStatusTransfers) + + let excludedIds = Set(tranfersSuccess.compactMap(\.ocIdTransfer)) + + let inWaiting = allTransfers.filter("status IN %@", NCGlobal.shared.metadatasStatusInWaiting).count + let inProgress = allTransfers.filter("status IN %@", NCGlobal.shared.metadatasStatusDownloadingUploading).count + let inError = allTransfers.filter("status IN %@", NCGlobal.shared.metadatasStatusInError).count + let sortDescriptors = [ RealmSwift.SortDescriptor(keyPath: "status", ascending: false), - RealmSwift.SortDescriptor(keyPath: "sessionDate", ascending: true) + RealmSwift.SortDescriptor(keyPath: "sessionDate", ascending: true), + RealmSwift.SortDescriptor(keyPath: "ocId", ascending: true) ] - let results = realm.objects(tableMetadata.self) - .filter(predicate) + let results = allTransfers + .filter("status IN %@", status) .sorted(by: sortDescriptors) + .filter { !excludedIds.contains($0.ocIdTransfer) } - let excludedIds = Set(tranfersSuccess.compactMap { $0.ocIdTransfer }) - let filtered = results.filter { !excludedIds.contains($0.ocIdTransfer) } + let startIndex = min(offset, results.count) + let endIndex = min(startIndex + limit, results.count) + let metadatas = results[startIndex.. Int { diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 32d28416d8..d45b6f9410 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -234,6 +234,7 @@ final class NCGlobal: Sendable { let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] + let metadatasStatusInError = [-3, 3] let metadatasStatusInWaitingDownloadUpload = [-1, 1] let metadatasStatusDownloadingUploading = [-2, 2] diff --git a/iOSClient/Transfers/NCTransfersModel.swift b/iOSClient/Transfers/NCTransfersModel.swift index 61249cc232..c138e031dd 100644 --- a/iOSClient/Transfers/NCTransfersModel.swift +++ b/iOSClient/Transfers/NCTransfersModel.swift @@ -2,9 +2,27 @@ // SPDX-FileCopyrightText: 2025 Marino Faggiana // SPDX-License-Identifier: GPL-3.0-or-later + import Foundation import NextcloudKit +internal enum TransfersFilter: Sendable { + case waiting + case progress + case error + + var statuses: [Int] { + switch self { + case .waiting: + return NCGlobal.shared.metadatasStatusInWaiting + case .progress: + return NCGlobal.shared.metadatasStatusDownloadingUploading + case .error: + return NCGlobal.shared.metadatasStatusInError + } + } +} + final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDelegate { @Published var metadatas: [tableMetadata] = [] @Published var progressMap: [String: Float] = [:] @@ -14,6 +32,13 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele @Published var inProgressCount = 0 @Published var inErrorCount = 0 + @Published private(set) var selectedFilter: TransfersFilter = .progress + + private let pageSize = 100 + @Published private(set) var currentPage = 0 + @Published private(set) var hasNextPage = true + private var isLoadingPage = false + // Dependencies private let session: NCSession.Session private let database = NCManageDatabase.shared @@ -47,34 +72,78 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele func pollTransfers() async { while !Task.isCancelled { if !isXcodeRunningForPreviews { - isLoading = true + await loadPage(currentPage, force: true) + } + try? await Task.sleep(for: .seconds(0.5)) + } + } - // Items - let transfersSuccess = await networking.metadataTranfersSuccess.getAll() - let results = await database.getTransferAsync(tranfersSuccess: transfersSuccess) - metadatas = results.filter { - self.global.metadataStatusTransfers.contains($0.status) - } + @MainActor + func selectFilter(_ filter: TransfersFilter) async { + guard selectedFilter != filter else { + return + } + + selectedFilter = filter + currentPage = 0 + hasNextPage = true + await loadPage(0, force: true) + } + + @MainActor + func loadNextPage() async { + guard hasNextPage else { + return + } - // inWaitingCount - let countTransfersSuccess = await NCNetworking.shared.metadataTranfersSuccess.count() - let countWaiting = await NCManageDatabase.shared.getMetadatasStatusCountAsync(status: NCGlobal.shared.metadatasStatusInWaiting) - inWaitingCount = max(0, countWaiting - countTransfersSuccess) + await loadPage(currentPage + 1, force: false) + } - // inProgressCount - inProgressCount = metadatas.compactMap(\.status) - .filter { NCGlobal.shared.metadatasStatusDownloadingUploading.contains($0) } - .count + @MainActor + func loadPreviousPage() async { + guard currentPage > 0 else { + return + } - // inErrorCount - inErrorCount = metadatas.compactMap(\.errorCode) - .filter { $0 != 0 } - .count + await loadPage(currentPage - 1, force: false) + } - isLoading = false - } - try? await Task.sleep(for: .seconds(0.5)) + @MainActor + private func loadPage(_ page: Int, force: Bool) async { + guard !isLoadingPage else { + return + } + + let page = max(0, page) + let offset = page * pageSize + + isLoadingPage = true + isLoading = true + defer { + isLoadingPage = false + isLoading = false + } + + let transfersSuccess = await networking.metadataTranfersSuccess.getAll() + let result = await database.getTransferAsync( + tranfersSuccess: transfersSuccess, + status: selectedFilter.statuses, + offset: offset, + limit: pageSize + ) + + guard force || !result.metadatas.isEmpty || page == 0 else { + hasNextPage = false + return } + + metadatas = result.metadatas + currentPage = page + hasNextPage = result.metadatas.count == pageSize + + inWaitingCount = result.inWaiting + inProgressCount = result.inProgress + inErrorCount = result.inError } func cancel(item: tableMetadata) async { diff --git a/iOSClient/Transfers/NCTransfersView.swift b/iOSClient/Transfers/NCTransfersView.swift index 4b3c9a73ee..52073e5578 100644 --- a/iOSClient/Transfers/NCTransfersView.swift +++ b/iOSClient/Transfers/NCTransfersView.swift @@ -49,15 +49,33 @@ struct TransfersView: View { @ViewBuilder private var contentView: some View { - if model.showFlushMessage || (model.metadatas.isEmpty && model.inWaitingCount == 0) { + if model.showFlushMessage || ( + model.inWaitingCount == 0 && + model.inProgressCount == 0 && + model.inErrorCount == 0 + ) { EmptyTransfersView(model: model) } else { List { Section(header: TransfersSummaryHeader( + selectedFilter: model.selectedFilter, inWaitingCount: model.inWaitingCount, inProgressCount: model.inProgressCount, - inErrorCount: model.inErrorCount + inErrorCount: model.inErrorCount, + onSelect: { filter in + Task { + await model.selectFilter(filter) + } + } ).font(.headline)) { + if model.metadatas.isEmpty { + ContentUnavailableView( + NSLocalizedString("_no_transfer_", comment: ""), + systemImage: "tray", + description: Text(NSLocalizedString("_no_transfer_sub_", comment: "")) + ) + .listRowSeparator(.hidden) + } ForEach(model.metadatas, id: \.ocId) { item in TransferRowView(model: model, item: item) { await model.cancel(item: item) @@ -65,6 +83,25 @@ struct TransfersView: View { .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } + + if !model.metadatas.isEmpty { + TransferPaginationControls( + currentPage: model.currentPage, + hasNextPage: model.hasNextPage, + onPrevious: { + Task { + await model.loadPreviousPage() + } + }, + onNext: { + Task { + await model.loadNextPage() + } + } + ) + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) + } } } .listStyle(.plain) @@ -75,32 +112,101 @@ struct TransfersView: View { // MARK: - Summary Header struct TransfersSummaryHeader: View { + let selectedFilter: TransfersFilter let inWaitingCount: Int let inProgressCount: Int let inErrorCount: Int + let onSelect: (TransfersFilter) -> Void var body: some View { HStack(spacing: 8) { - summaryPill(title: "_in_waiting_", value: inWaitingCount) - summaryPill(title: "_in_progress_", value: inProgressCount) - summaryPill(title: "_in_error_", value: inErrorCount) + summaryButton( + title: "_in_progress_", + value: inProgressCount, + filter: .progress + ) + + summaryButton( + title: "_in_waiting_", + value: inWaitingCount, + filter: .waiting + ) + + summaryButton( + title: "_in_error_", + value: inErrorCount, + filter: .error + ) + Spacer() } .padding(.vertical, 6) } - private func summaryPill(title: String, value: Int) -> some View { - HStack(spacing: 6) { - Text(NSLocalizedString(title, comment: "")) - .font(.caption) + private func summaryButton( + title: String, + value: Int, + filter: TransfersFilter + ) -> some View { + Button { + onSelect(filter) + } label: { + HStack(spacing: 6) { + Text(NSLocalizedString(title, comment: "")) + .font(.caption) + + Text("\(value)") + .font(.caption) + .fontWeight(.semibold) + } + .foregroundStyle(selectedFilter == filter ? .primary : .secondary) + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background( + selectedFilter == filter ? AnyShapeStyle(.ultraThinMaterial) : AnyShapeStyle(.clear), + in: Capsule() + ) + } + .buttonStyle(.plain) + } +} + +// MARK: - Pagination + +struct TransferPaginationControls: View { + let currentPage: Int + let hasNextPage: Bool + let onPrevious: () -> Void + let onNext: () -> Void + + var body: some View { + HStack(spacing: 12) { + Button { + onPrevious() + } label: { + Label("_previous_", systemImage: "chevron.left") + } + .disabled(currentPage == 0) + + Spacer() + + Text("\(currentPage + 1)") + .font(.footnote) .foregroundStyle(.secondary) - Text("\(value)") - .font(.caption) - .fontWeight(.semibold) + + Spacer() + + Button { + onNext() + } label: { + Label("_next_", systemImage: "chevron.right") + .labelStyle(.titleAndIcon) + } + .disabled(!hasNextPage) } - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background(.ultraThinMaterial, in: Capsule()) + .buttonStyle(.borderless) + .padding(.horizontal, 15) + .padding(.vertical, 12) } } From b0dbf12f565b03d7de5760cebeebf64a903b07e3 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 26 Jun 2026 10:10:12 +0200 Subject: [PATCH 2/5] wip Signed-off-by: Marino Faggiana --- .../Data/NCManageDatabase+Metadata.swift | 2 +- iOSClient/NCGlobal.swift | 1 - iOSClient/Transfers/NCTransfersView.swift | 138 ++++++++---------- 3 files changed, 59 insertions(+), 82 deletions(-) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index e86ea6115c..00fbf11c8b 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -1470,7 +1470,7 @@ extension NCManageDatabase { func getTransferAsync(tranfersSuccess: [tableMetadata], status: [Int], offset: Int, limit: Int) async -> (metadatas: [tableMetadata], inWaiting: Int, inProgress: Int, inError: Int) { await core.performRealmReadAsync { realm in let allTransfers = realm.objects(tableMetadata.self) - .filter("status IN %@", NCGlobal.shared.metadataStatusTransfers) + .filter("status != 0") let excludedIds = Set(tranfersSuccess.compactMap(\.ocIdTransfer)) diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index d45b6f9410..76162ca9d4 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -231,7 +231,6 @@ final class NCGlobal: Sendable { let metadataStatusForScreenAwake = [-1, -2, 1, 2] let metadataStatusHideInView = [1, 2, 3, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] - let metadataStatusTransfers = [-2, -3, 2, 3, 10, 11, 12, 13, 14, 15] let metadatasStatusInWaiting = [-1, 1, 10, 11, 12, 13, 14, 15] let metadatasStatusInError = [-3, 3] diff --git a/iOSClient/Transfers/NCTransfersView.swift b/iOSClient/Transfers/NCTransfersView.swift index 52073e5578..6954e95130 100644 --- a/iOSClient/Transfers/NCTransfersView.swift +++ b/iOSClient/Transfers/NCTransfersView.swift @@ -7,7 +7,6 @@ import SwiftUI // MARK: - Main View struct TransfersView: View { - @Environment(\.dismiss) private var dismiss @StateObject private var model: TransfersViewModel private let onClose: (() -> Void)? @@ -34,9 +33,7 @@ struct TransfersView: View { .toolbar { ToolbarItem(placement: .cancellationAction) { Button("_close_") { - if let onClose { - onClose() - } + onClose?() } } } @@ -47,6 +44,39 @@ struct TransfersView: View { .presentationDetents([.medium, .large]) } + private var emptyFilterTitle: String { + switch model.selectedFilter { + case .progress: + return NSLocalizedString("_no_transfer_in_progress_", comment: "") + case .waiting: + return NSLocalizedString("_no_transfer_in_waiting_", comment: "") + case .error: + return NSLocalizedString("_no_transfer_in_error_", comment: "") + } + } + + private var emptyFilterDescription: String { + switch model.selectedFilter { + case .progress: + return NSLocalizedString("_no_transfer_in_progress_sub_", comment: "") + case .waiting: + return NSLocalizedString("_no_transfer_in_waiting_sub_", comment: "") + case .error: + return NSLocalizedString("_no_transfer_in_error_sub_", comment: "") + } + } + + private var emptyFilterSymbol: String { + switch model.selectedFilter { + case .progress: + return "arrow.triangle.2.circlepath" + case .waiting: + return "clock" + case .error: + return "exclamationmark.triangle" + } + } + @ViewBuilder private var contentView: some View { if model.showFlushMessage || ( @@ -70,12 +100,13 @@ struct TransfersView: View { ).font(.headline)) { if model.metadatas.isEmpty { ContentUnavailableView( - NSLocalizedString("_no_transfer_", comment: ""), - systemImage: "tray", - description: Text(NSLocalizedString("_no_transfer_sub_", comment: "")) + emptyFilterTitle, + systemImage: emptyFilterSymbol, + description: Text(emptyFilterDescription) ) .listRowSeparator(.hidden) } + ForEach(model.metadatas, id: \.ocId) { item in TransferRowView(model: model, item: item) { await model.cancel(item: item) @@ -83,24 +114,14 @@ struct TransfersView: View { .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } - - if !model.metadatas.isEmpty { - TransferPaginationControls( - currentPage: model.currentPage, - hasNextPage: model.hasNextPage, - onPrevious: { - Task { - await model.loadPreviousPage() - } - }, - onNext: { - Task { - await model.loadNextPage() - } - } - ) - .listRowInsets(EdgeInsets()) - .listRowSeparator(.hidden) + if model.selectedFilter == .waiting, + model.inWaitingCount > model.metadatas.count { + Text("\(model.metadatas.count) di \(model.inWaitingCount) trasferimenti in attesa") + .font(.footnote) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 12) + .listRowSeparator(.hidden) } } } @@ -171,45 +192,6 @@ struct TransfersSummaryHeader: View { } } -// MARK: - Pagination - -struct TransferPaginationControls: View { - let currentPage: Int - let hasNextPage: Bool - let onPrevious: () -> Void - let onNext: () -> Void - - var body: some View { - HStack(spacing: 12) { - Button { - onPrevious() - } label: { - Label("_previous_", systemImage: "chevron.left") - } - .disabled(currentPage == 0) - - Spacer() - - Text("\(currentPage + 1)") - .font(.footnote) - .foregroundStyle(.secondary) - - Spacer() - - Button { - onNext() - } label: { - Label("_next_", systemImage: "chevron.right") - .labelStyle(.titleAndIcon) - } - .disabled(!hasNextPage) - } - .buttonStyle(.borderless) - .padding(.horizontal, 15) - .padding(.vertical, 12) - } -} - // MARK: - Empty State struct EmptyTransfersView: View { @@ -243,14 +225,8 @@ struct EmptyTransfersView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .task(id: model.showFlushMessage) { - if model.showFlushMessage { - withAnimation(.spring(response: 0.35, dampingFraction: 0.82)) { - flash = true - } - } else { - withAnimation(.easeInOut(duration: 0.25)) { - flash = false - } + withAnimation(flash ? .easeInOut(duration: 0.25) : .spring(response: 0.35, dampingFraction: 0.82)) { + flash = model.showFlushMessage } } } @@ -273,7 +249,8 @@ struct TransferRowView: View { .font(.icon(30)) VStack(alignment: .leading, spacing: 6) { - Text(item.fileName).font(.headline) + Text(item.fileName) + .font(.headline) if !status.status.isEmpty { Text(status.status) @@ -304,10 +281,7 @@ struct TransferRowView: View { } label: { ZStack { Circle() - .stroke( - Color.gray.opacity(0.2), - lineWidth: 2 - ) + .stroke(Color.gray.opacity(0.2), lineWidth: 2) .frame(width: 36, height: 36) Circle() @@ -319,6 +293,7 @@ struct TransferRowView: View { .rotationEffect(.degrees(-90)) .frame(width: 36, height: 36) .animation(.easeInOut(duration: 0.25), value: model.progress(for: item)) + Image(systemName: "stop.fill") .font(.icon(14, weight: .bold)) .foregroundStyle(.primary) @@ -328,6 +303,7 @@ struct TransferRowView: View { .accessibilityLabel(NSLocalizedString("_cancel_", comment: "")) } .contentShape(Rectangle()) + Divider() } .padding(.horizontal, 15) @@ -341,9 +317,11 @@ struct TransfersView_Previews: PreviewProvider { static var previews: some View { let metadatas: [tableMetadata] = [ tableMetadata(ocId: "1", fileName: "filename 1", status: NCGlobal.shared.metadataStatusWaitCreateFolder), - tableMetadata(ocId: "2", fileName: "filename 2", size: 7230000, status: NCGlobal.shared.metadataStatusUploading), - tableMetadata(ocId: "3", fileName: "filename 3", size: 5230000, status: NCGlobal.shared.metadataStatusDownloading), - tableMetadata(ocId: "4", fileName: "filename 4", size: 7230000, status: NCGlobal.shared.metadataStatusUploadError, sessionError: "Disk full Disk full Disk full Disk full Disk full Disk full Disk full Disk full", errorCode: 1)] + tableMetadata(ocId: "2", fileName: "filename 2", size: 7_230_000, status: NCGlobal.shared.metadataStatusUploading), + tableMetadata(ocId: "3", fileName: "filename 3", size: 5_230_000, status: NCGlobal.shared.metadataStatusDownloading), + tableMetadata(ocId: "4", fileName: "filename 4", size: 7_230_000, status: NCGlobal.shared.metadataStatusUploadError, sessionError: "Disk full Disk full Disk full Disk full Disk full Disk full Disk full Disk full", errorCode: 1) + ] + return TransfersView(previewMetadatas: metadatas) .previewDisplayName("Transfers – Preview Items") } From 2d301fd64a96ce853554d4b49864954cede09fb7 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 26 Jun 2026 11:45:26 +0200 Subject: [PATCH 3/5] new label Signed-off-by: Marino Faggiana --- .../en.lproj/Localizable.strings | 11 ++++-- iOSClient/Transfers/NCTransfersView.swift | 37 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9cb813b125..06001238b4 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -435,9 +435,14 @@ "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; - -"_no_transfer_" = "No transfers yet"; -"_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_transfer_" = "No transfers yet"; +"_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_transfer_in_progress_" = "No transfers in progress"; +"_no_transfer_in_progress_sub_" = "Active uploads and downloads from this device will appear here."; +"_no_transfer_in_waiting_" = "No transfers waiting"; +"_no_transfer_in_waiting_sub_" = "Transfers waiting to start will appear here."; +"_no_transfer_in_error_" = "No transfer errors"; +"_no_transfer_in_error_sub_" = "Transfers that need your attention will appear here."; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; diff --git a/iOSClient/Transfers/NCTransfersView.swift b/iOSClient/Transfers/NCTransfersView.swift index 6954e95130..22b9b63f66 100644 --- a/iOSClient/Transfers/NCTransfersView.swift +++ b/iOSClient/Transfers/NCTransfersView.swift @@ -85,6 +85,33 @@ struct TransfersView: View { model.inErrorCount == 0 ) { EmptyTransfersView(model: model) + } else if model.metadatas.isEmpty { + VStack(spacing: 0) { + TransfersSummaryHeader( + selectedFilter: model.selectedFilter, + inWaitingCount: model.inWaitingCount, + inProgressCount: model.inProgressCount, + inErrorCount: model.inErrorCount, + onSelect: { filter in + Task { + await model.selectFilter(filter) + } + } + ) + .font(.headline) + .padding(.horizontal, 15) + .padding(.vertical, 6) + + Spacer() + + ContentUnavailableView( + emptyFilterTitle, + systemImage: emptyFilterSymbol, + description: Text(emptyFilterDescription) + ) + + Spacer() + } } else { List { Section(header: TransfersSummaryHeader( @@ -98,15 +125,6 @@ struct TransfersView: View { } } ).font(.headline)) { - if model.metadatas.isEmpty { - ContentUnavailableView( - emptyFilterTitle, - systemImage: emptyFilterSymbol, - description: Text(emptyFilterDescription) - ) - .listRowSeparator(.hidden) - } - ForEach(model.metadatas, id: \.ocId) { item in TransferRowView(model: model, item: item) { await model.cancel(item: item) @@ -114,6 +132,7 @@ struct TransfersView: View { .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } + if model.selectedFilter == .waiting, model.inWaitingCount > model.metadatas.count { Text("\(model.metadatas.count) di \(model.inWaitingCount) trasferimenti in attesa") From 5b4a4de7d90bbdddc9a3580b36cf6ba720896a02 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 26 Jun 2026 12:07:32 +0200 Subject: [PATCH 4/5] wip Signed-off-by: Marino Faggiana --- .../en.lproj/Localizable.strings | 1 + iOSClient/Transfers/NCTransfersModel.swift | 57 ++++--------------- iOSClient/Transfers/NCTransfersView.swift | 22 ++++--- 3 files changed, 25 insertions(+), 55 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 06001238b4..6b648fd281 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -443,6 +443,7 @@ "_no_transfer_in_waiting_sub_" = "Transfers waiting to start will appear here."; "_no_transfer_in_error_" = "No transfer errors"; "_no_transfer_in_error_sub_" = "Transfers that need your attention will appear here."; +"_transfers_waiting_shown_" = "%1$d of %2$d transfers waiting"; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; diff --git a/iOSClient/Transfers/NCTransfersModel.swift b/iOSClient/Transfers/NCTransfersModel.swift index c138e031dd..d190184073 100644 --- a/iOSClient/Transfers/NCTransfersModel.swift +++ b/iOSClient/Transfers/NCTransfersModel.swift @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2025 Marino Faggiana // SPDX-License-Identifier: GPL-3.0-or-later - import Foundation import NextcloudKit @@ -26,7 +25,6 @@ internal enum TransfersFilter: Sendable { final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDelegate { @Published var metadatas: [tableMetadata] = [] @Published var progressMap: [String: Float] = [:] - @Published var isLoading = false @Published var showFlushMessage = false @Published var inWaitingCount = 0 @Published var inProgressCount = 0 @@ -34,10 +32,8 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele @Published private(set) var selectedFilter: TransfersFilter = .progress - private let pageSize = 100 - @Published private(set) var currentPage = 0 - @Published private(set) var hasNextPage = true - private var isLoadingPage = false + private var isLoadingTransfers = false + private let transfersLimit = 100 // Dependencies private let session: NCSession.Session @@ -72,7 +68,7 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele func pollTransfers() async { while !Task.isCancelled { if !isXcodeRunningForPreviews { - await loadPage(currentPage, force: true) + await loadTransfers() } try? await Task.sleep(for: .seconds(0.5)) } @@ -85,62 +81,29 @@ final class TransfersViewModel: ObservableObject, NCMetadataTransfersSuccessDele } selectedFilter = filter - currentPage = 0 - hasNextPage = true - await loadPage(0, force: true) - } - - @MainActor - func loadNextPage() async { - guard hasNextPage else { - return - } - - await loadPage(currentPage + 1, force: false) - } - - @MainActor - func loadPreviousPage() async { - guard currentPage > 0 else { - return - } - - await loadPage(currentPage - 1, force: false) + await loadTransfers() } @MainActor - private func loadPage(_ page: Int, force: Bool) async { - guard !isLoadingPage else { + private func loadTransfers() async { + guard !isLoadingTransfers else { return } - let page = max(0, page) - let offset = page * pageSize - - isLoadingPage = true - isLoading = true + isLoadingTransfers = true defer { - isLoadingPage = false - isLoading = false + isLoadingTransfers = false } let transfersSuccess = await networking.metadataTranfersSuccess.getAll() let result = await database.getTransferAsync( tranfersSuccess: transfersSuccess, status: selectedFilter.statuses, - offset: offset, - limit: pageSize + offset: 0, + limit: transfersLimit ) - guard force || !result.metadatas.isEmpty || page == 0 else { - hasNextPage = false - return - } - metadatas = result.metadatas - currentPage = page - hasNextPage = result.metadatas.count == pageSize - inWaitingCount = result.inWaiting inProgressCount = result.inProgress inErrorCount = result.inError diff --git a/iOSClient/Transfers/NCTransfersView.swift b/iOSClient/Transfers/NCTransfersView.swift index 22b9b63f66..9c8da7dcce 100644 --- a/iOSClient/Transfers/NCTransfersView.swift +++ b/iOSClient/Transfers/NCTransfersView.swift @@ -69,9 +69,9 @@ struct TransfersView: View { private var emptyFilterSymbol: String { switch model.selectedFilter { case .progress: - return "arrow.triangle.2.circlepath" + return "arrow.up.arrow.down.circle" case .waiting: - return "clock" + return "arrow.triangle.2.circlepath" case .error: return "exclamationmark.triangle" } @@ -135,12 +135,18 @@ struct TransfersView: View { if model.selectedFilter == .waiting, model.inWaitingCount > model.metadatas.count { - Text("\(model.metadatas.count) di \(model.inWaitingCount) trasferimenti in attesa") - .font(.footnote) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.vertical, 12) - .listRowSeparator(.hidden) + Text( + String( + format: NSLocalizedString("_transfers_waiting_shown_", comment: ""), + model.metadatas.count, + model.inWaitingCount + ) + ) + .font(.footnote) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 12) + .listRowSeparator(.hidden) } } } From cbdc3f90a8a147437659fbb8ca1810fd9789e83e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 26 Jun 2026 12:15:06 +0200 Subject: [PATCH 5/5] build 3 Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index e883662fec..8721ff73b2 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -6237,7 +6237,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; @@ -6305,7 +6305,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = NKUJUXUJ3B;