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
2 changes: 1 addition & 1 deletion Share/Model/NCShareViewModel+Accounts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension NCShareViewModel {
}

func selectAccount(_ account: String) {
guard isAuthenticated, !isClosing, !isPreparingUpload, !isSwitchingAccount,
guard isAuthenticated, !isClosing, !isLoading, !isPreparingUpload, !isSwitchingAccount, !isLoadingAccounts,
accounts.contains(where: { $0.id == account }) else { return }
guard account != sessionStore.getSession().account else { return }
changeAccount(account)
Expand Down
25 changes: 17 additions & 8 deletions Share/Model/NCShareViewModel+Folders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ extension NCShareViewModel {
guard isCurrentBrowser(browserID, account: session.account) else { return }
// Publish the replacement snapshot together, without an empty intermediate list.
rootFolder = root
selectedFolder = root
// Leave the destination unset while the most recent folder is awaiting validation.
selectedFolder = recent.isEmpty ? root : nil
isRecentFolderSelected = false
expandedFolderURLs = [home]
recentFolders = recent
Expand All @@ -72,14 +73,14 @@ extension NCShareViewModel {

func refreshInitialFolderBrowser() {
guard let rootFolder else { return }
if let latest = recentFolders.first {
if selectedFolder == nil, let latest = recentFolders.first {
selectFolder(latest, fromRecents: true, automatically: true)
}
requestFolderChildren(rootFolder, refresh: true)
}

func toggleFolder(_ folder: NCShareFolderItem) {
guard canUseFolderBrowser, canAccessFolder(folder) else { return }
guard canUseFolderBrowser, !isLoading, canAccessFolder(folder) else { return }
if expandedFolderURLs.contains(folder.serverUrl) {
expandedFolderURLs.remove(folder.serverUrl)
} else {
Expand All @@ -88,7 +89,7 @@ extension NCShareViewModel {
}

func retryFolder(_ folder: NCShareFolderItem) {
guard canUseFolderBrowser, canAccessFolder(folder) else { return }
guard canUseFolderBrowser, !isLoading, canAccessFolder(folder) else { return }
expandedFolderURLs.insert(folder.serverUrl)
requestFolderChildren(folder, refresh: true)
}
Expand All @@ -108,14 +109,22 @@ extension NCShareViewModel {
/// Validates the destination on the server before selecting it.
/// fromRecents controls which of the two occurrences displays the checkmark.
func selectFolder(_ folder: NCShareFolderItem, fromRecents: Bool = false, automatically: Bool = false) {
guard canSelectFolder, automatically || !isLoadingAccounts, let capabilities = serverCapabilities else { return }
guard canAccessFolder(folder, showError: !automatically) else { return }
guard canSelectFolder, !isLoading, automatically || !isLoadingAccounts, let capabilities = serverCapabilities else { return }
guard canAccessFolder(folder, showError: !automatically) else {
if automatically { selectedFolder = rootFolder }
return
}
let session = sessionStore.getSession()
let browserID = folderBrowserID
isLoading = true
folderSelectionTask = Task { @MainActor in
defer {
if folderBrowserID == browserID { isLoading = false }
if isCurrentBrowser(browserID, account: session.account) {
if automatically, isAuthenticated, selectedFolder == nil {
selectedFolder = rootFolder
}
isLoading = false
}
}
// Recent destinations may have been deleted or moved since last use.
let result = await NCNetworking.shared.readFileAsync(serverUrlFileName: folder.serverUrl, account: session.account)
Expand Down Expand Up @@ -154,7 +163,7 @@ extension NCShareViewModel {
}

private var canSelectFolder: Bool {
isAuthenticated && !isClosing && isAccountReady && !isLoading && !isPreparingUpload
isAuthenticated && !isClosing && isAccountReady && !isPreparingUpload
}

private func canAccessFolder(_ folder: NCShareFolderItem, showError: Bool = true) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion Share/Model/NCShareViewModel+Upload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension NCShareViewModel {
/// Upload flow: inspect the destination, resolve name conflicts if needed,
/// stage local copies, then transfer. Keep editing disabled throughout this flow.
func upload() {
guard canUpload, let capabilities = serverCapabilities else { return }
guard canUpload, !isLoading, folderTasks.isEmpty, let capabilities = serverCapabilities else { return }
runUpload { [self] in
let plan = try await prepareUploadPlan(capabilities: capabilities)
try Task.checkCancellation()
Expand Down
10 changes: 8 additions & 2 deletions Share/Model/NCShareViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ final class NCShareViewModel: NSObject, ObservableObject {

var canUseUploadDestination: Bool {
guard let selectedFolder else { return false }
return canUseFolderBrowser && !isLoadingCapabilities && folderTasks.isEmpty &&
return canUseFolderBrowser && !isLoadingCapabilities &&
folderLoadErrors[selectedFolder.serverUrl] == nil && alertMessage.isEmpty &&
!isPreparingInput && inputErrorMessage.isEmpty
}
Expand Down Expand Up @@ -192,6 +192,8 @@ final class NCShareViewModel: NSObject, ObservableObject {
guard !isOffline, isAuthenticated, !isClosing, !isSwitchingAccount, !isPreparingUpload else { return }
if serverCapabilities == nil {
retryCapabilities()
} else if selectedFolder == nil {
refreshInitialFolderBrowser()
} else {
for row in visibleFolderRows where expandedFolderURLs.contains(row.folder.serverUrl) {
requestFolderChildren(row.folder, refresh: true)
Expand Down Expand Up @@ -359,7 +361,8 @@ final class NCShareViewModel: NSObject, ObservableObject {
}

func createFolder() {
guard canUseUploadDestination, let folder = selectedFolder, let capabilities = serverCapabilities,
guard canUseUploadDestination, !isLoading, folderTasks.isEmpty,
let folder = selectedFolder, let capabilities = serverCapabilities,
let presenter, presenter.viewIfLoaded?.window != nil,
presenter.presentedViewController == nil,
!presenter.isBeingDismissed else { return }
Expand Down Expand Up @@ -467,6 +470,9 @@ final class NCShareViewModel: NSObject, ObservableObject {
guard self.validateAccount() else { return }
self.access = .unlocked
if self.accounts.isEmpty { self.loadAccounts() }
if self.selectedFolder == nil, self.serverCapabilities != nil {
self.refreshInitialFolderBrowser()
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Share/View/NCShareUploadButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct NCShareUploadButton: View {
let fileCount: Int
var isPreparing = false
var title: String?
var minimumLabelHeight: CGFloat?
let action: @MainActor () -> Void

var body: some View {
Expand All @@ -25,7 +26,7 @@ struct NCShareUploadButton: View {
.lineLimit(2)
.truncationMode(.middle)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, minHeight: minimumLabelHeight)
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.roundedRectangle(radius: 12))
Expand Down
8 changes: 5 additions & 3 deletions Share/View/NCShareUploadConflictView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import SwiftUI
struct NCShareUploadConflictView: View {
let confirmation: NCShareUploadConfirmation
let resolve: @MainActor (NCShareUploadChoice?) -> Void
private let actionLabelHeight: CGFloat = 44

var body: some View {
let renamedItems = confirmation.renamedItems
Expand Down Expand Up @@ -47,14 +48,14 @@ struct NCShareUploadConflictView: View {
.padding(.vertical, 12)
}
.safeAreaInset(edge: .bottom, spacing: 0) {
VStack(spacing: 16) {
VStack(spacing: 24) {
if !confirmation.isBatch, confirmation.replacementFileName != nil {
Button(role: .destructive) {
resolve(.replace)
} label: {
Text(NSLocalizedString("_share_conflict_replace_", comment: ""))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, minHeight: actionLabelHeight)
}
.buttonStyle(.bordered)
.buttonBorderShape(.roundedRectangle(radius: 12))
Expand All @@ -64,7 +65,8 @@ struct NCShareUploadConflictView: View {

NCShareUploadButton(
fileCount: confirmation.items.count,
title: confirmation.isBatch ? nil : NSLocalizedString("_share_conflict_upload_with_new_name_", comment: "")
title: confirmation.isBatch ? nil : NSLocalizedString("_share_conflict_upload_with_new_name_", comment: ""),
minimumLabelHeight: actionLabelHeight
) {
resolve(.keepBoth)
}
Expand Down
59 changes: 25 additions & 34 deletions Share/View/NCShareView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ struct NCShareView: View {

private var canShowContent: Bool { model.rootFolder != nil && model.isAuthenticated }

private var isRefreshing: Bool {
model.isLoading || model.isSwitchingAccount || model.isLoadingAccounts ||
model.isLoadingCapabilities || !model.folderTasks.isEmpty
}

var body: some View {
NavigationStack {
VStack(spacing: 0) {
Expand Down Expand Up @@ -78,7 +73,7 @@ struct NCShareView: View {
if canShowContent {
let account = ToolbarItem(placement: .topBarLeading) {
NCShareAccountMenu(model: model)
.disabled(model.isLoading || model.isPreparingUpload || model.isSwitchingAccount || model.isLoadingAccounts)
.disabled(model.isPreparingUpload || model.isSwitchingAccount || model.isLoadingAccounts)
}
if #available(iOS 27.1, *) {
account.axisBehavior(.horizontalOnly)
Expand All @@ -87,18 +82,6 @@ struct NCShareView: View {
account
}
}
if canShowContent && isRefreshing {
let spinner = ToolbarItem(placement: .topBarLeading) {
ProgressView()
.controlSize(.regular)
.accessibilityLabel(NSLocalizedString("_request_in_progress_", comment: ""))
}
if #available(iOS 27.1, *) {
spinner.axisBehavior(.horizontalOnly)
} else {
spinner
}
}
ToolbarItem(placement: .topBarTrailing) {
Button(NSLocalizedString("_cancel_", comment: "")) { model.cancel() }
}
Expand Down Expand Up @@ -201,8 +184,8 @@ struct NCShareView: View {
.frame(maxHeight: .infinity)
.clipped()
.background(Color(uiColor: .systemGroupedBackground))
.opacity(isRefreshing ? 0.45 : 1)
.disabled(isRefreshing || model.isPreparingUpload || !model.isAccountReady)
.opacity(model.isSwitchingAccount ? 0.45 : 1)
.disabled(model.isSwitchingAccount)
}

private func folderListTitle(_ key: String) -> some View {
Expand Down Expand Up @@ -246,10 +229,10 @@ struct NCShareView: View {
model.toggleFolder(folder)
} label: {
Image(systemName: model.expandedFolderURLs.contains(folder.serverUrl) ? "chevron.down" : "chevron.right")
.foregroundStyle(Color.accentColor)
.frame(width: 50, height: 50)
.contentShape(Rectangle())
}
.buttonStyle(.borderless)
.accessibilityLabel(String(format: NSLocalizedString(
model.expandedFolderURLs.contains(folder.serverUrl) ? "_share_collapse_folder_" : "_share_expand_folder_", comment: ""), folder.name))
.padding(.leading, CGFloat(min(depth, 8)) * 16)
Expand Down Expand Up @@ -304,30 +287,38 @@ struct NCShareView: View {
}
}
Spacer(minLength: 4)
if isSelected {
Image(systemName: "checkmark")
.frame(width: 50, height: 50)
.accessibilityHidden(true)
}
Image(systemName: "checkmark")
.foregroundStyle(Color.accentColor)
.frame(width: 50, height: 50)
.opacity(isSelected ? 1 : 0)
.accessibilityHidden(true)
}
.frame(minHeight: 50)
.contentShape(Rectangle())
}
.buttonStyle(.borderless)
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
.buttonStyle(FolderRowButtonStyle())
.disabled(!model.canUseFolderBrowser)
.background {
if isSelected {
RoundedRectangle(cornerRadius: 8)
.fill(Color.accentColor.opacity(0.1))
.padding(.horizontal, 5)
}
RoundedRectangle(cornerRadius: 8)
.fill(Color.accentColor.opacity(isSelected ? 0.1 : 0))
.padding(.horizontal, 5)
}
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 2, bottom: 0, trailing: 2))
.listRowBackground(Color(uiColor: .secondarySystemGroupedBackground))
}

private struct FolderRowButtonStyle: ButtonStyle {
@Environment(\.isEnabled) private var isEnabled

func makeBody(configuration: Configuration) -> some View {
configuration.label
.opacity(isEnabled ? 1 : 0.55)
}
}

private func fileList(fillsHeight: Bool = false, horizontalInset: CGFloat = 16) -> some View {
List(model.fileNames, id: \.self) { fileName in
Button {
Expand Down Expand Up @@ -388,8 +379,8 @@ struct NCShareView: View {
.background(Color(uiColor: .secondarySystemGroupedBackground))
.clipShape(RoundedRectangle(cornerRadius: 12))
.padding(.horizontal, horizontalInset)
.opacity(isRefreshing ? 0.45 : 1)
.disabled(isRefreshing || model.isPreparingUpload || model.isPreparingInput || !model.isAccountReady || !model.inputErrorMessage.isEmpty)
.opacity(model.isSwitchingAccount ? 0.45 : 1)
.disabled(model.isPreparingUpload || model.isPreparingInput || !model.isAccountReady || !model.inputErrorMessage.isEmpty)
}

private var uploadButton: some View {
Expand Down
24 changes: 12 additions & 12 deletions iOSClient/Supporting Files/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@
"_empty_trash_" = "Empty trash";
"_trash_no_trash_" = "No files deleted";
"_trash_no_trash_description_" = "You can restore deleted files from here";
"_confirm_delete_selected_" = "Are you sure you want to delete the selected items?";
"_confirm_delete_selected_" = "Are you sure you want to delete the selected files?";
"_set_available_offline_" = "Set as available offline";
"_remove_available_offline_" = "Remove as available offline";
"_create_new_document_" = "Document";
Expand Down Expand Up @@ -570,7 +570,7 @@
"_additional_options_" = "Additional options";
"_unauthorized_files_passcode_" = "Files app cannot be used with an activated passcode";
"_files_app_disabled_" = "Files app cannot be used because it is disabled";
"_no_items_" = "No items";
"_no_items_" = "Nothing to show";
"_check_back_later_" = "Check back later";
"_no_data_available_" = "No data available";
"_recent_activity_" = "Recent activity";
Expand Down Expand Up @@ -603,14 +603,14 @@
"_finalizing_wait_" = "Waiting for finalization …";
"_in_this_folder_" = "In this folder";
"_media_no_longer_available_" = "Media no longer available";
"_this_item_has_been_deleted_" = "This item has been deleted.";
"_this_item_has_been_deleted_" = "This file has been deleted.";
"_video_not_available_" = "Video not available";
"_disable_" = "Disable";
"_no_subtitles_available_" = "No subtitles available";
"_no_audio_tracks_available_" = "No audio tracks available";
"_add_external_subtitle_" = "Add external subtitle";
"_repeat_current_media_" = "Repeat current item";
"_play_next_media_automatically_" = "Play next item automatically";
"_repeat_current_media_" = "Repeat current file";
"_play_next_media_automatically_" = "Play next file automatically";
"_previous_media_" = "Previous";
"_next_media_" = "Next";
"_play_" = "Play";
Expand Down Expand Up @@ -694,7 +694,7 @@
"_multiple_albums_" = "Multiple albums";
"_smart_albums_" = "Smart albums";
"_albums_" = "Albums";
"_auto_upload_no_new_items_to_upload_" = "No new items to upload";
"_auto_upload_no_new_items_to_upload_" = "No new files to upload";
"_background_upload_extension_" = "PhotoKit background upload extension";
"_experimental_" = "Experimental";
"_background_upload_extension_footer_" = "Experimental: delegate automatic photo uploads to PhotoKit in the background. The compatibility setting (HEIC to JPG) is ignored, so HEIC images are uploaded in their original format.";
Expand All @@ -715,12 +715,12 @@
"_focused_auto_upload_countdown_" = "Do not lock the screen or exit the app. The screen will turn dark in %d seconds.";
"_stop_focused_auto_upload_" = "Stop Focused Auto Upload";
"_finish_" = "Finish";
"_item_with_same_name_already_exists_" = "An item with the same name already exists.";
"_item_with_same_name_already_exists_" = "This name is already in use.";

// MARK: Migration Multi Domains
"_preparing_migration_" = "Preparing migration …";
"_scanning_files_" = "Scanning files …";
"_moving_items_to_domain_" = "Moving items to correct domain …";
"_moving_items_to_domain_" = "Moving files to the correct domain…";
"_finishing_up_" = "Finishing up …";

// MARK: E2EE
Expand Down Expand Up @@ -839,13 +839,13 @@

/* Share extension input preparation */
"_share_import_preparing_" = "Preparing shared files…";
"_share_import_failed_" = "Some shared items could not be prepared. Review the files before uploading.";
"_share_import_no_files_" = "The shared items could not be prepared.";
"_share_import_item_error_" = "Item %d: %@";
"_share_import_failed_" = "Some shared files could not be prepared. Review the files before uploading.";
"_share_import_no_files_" = "The shared files could not be prepared.";
"_share_import_item_error_" = "File %d: %@";

/* Share extension upload conflicts */
"_share_conflict_title_" = "Files already exist";
"_share_conflict_single_message_" = "An item named \"%@\" already exists in this folder.";
"_share_conflict_single_message_" = "A file named \"%@\" already exists in this folder.";
"_share_conflict_batch_message_" = "Some names are already in use. These files will be uploaded with new names, keeping the existing files.";
"_share_conflict_new_names_" = "New file names";
"_share_conflict_replace_" = "Replace existing file";
Expand Down
4 changes: 2 additions & 2 deletions iOSClient/Supporting Files/en.lproj/Localizable.stringsdict
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>You have %d item to back up</string>
<string>You have %d file to back up</string>
<key>other</key>
<string>You have %d items to back up</string>
<string>You have %d files to back up</string>
</dict>
</dict>
<key>_focused_auto_upload_failed_</key>
Expand Down
Loading