-
-
Notifications
You must be signed in to change notification settings - Fork 244
Release v5.16.1 #1444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Release v5.16.1 #1444
Changes from all commits
e69fefc
f0383dc
8dae45e
0433382
ff9dd7e
62981f5
b289bfd
175da81
c6926f9
2ab7297
d50ab56
d8a7988
b26e02e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -352,17 +352,19 @@ struct ItemListView: View { | |||||||||
| activeAlert = .downloadURL("") | ||||||||||
| } | ||||||||||
| Button( | ||||||||||
| String(format: | ||||||||||
| "download_from_integration_title".localized, | ||||||||||
| String( | ||||||||||
| format: | ||||||||||
| "download_from_integration_title".localized, | ||||||||||
| "Jellyfin" | ||||||||||
| ), | ||||||||||
| image: .jellyfinIcon | ||||||||||
| ) { | ||||||||||
| activeSheet = .jellyfin | ||||||||||
| } | ||||||||||
| Button( | ||||||||||
| String(format: | ||||||||||
| "download_from_integration_title".localized, | ||||||||||
| String( | ||||||||||
| format: | ||||||||||
| "download_from_integration_title".localized, | ||||||||||
| "AudiobookShelf" | ||||||||||
| ), | ||||||||||
| image: .audiobookshelfIcon | ||||||||||
|
|
@@ -466,43 +468,46 @@ struct ItemListView: View { | |||||||||
| let item = model.selectedItems.first | ||||||||||
| let isSingle = model.selectedItems.count == 1 | ||||||||||
|
|
||||||||||
| Spacer() | ||||||||||
|
|
||||||||||
| Button { | ||||||||||
| activeSheet = .itemDetails(item!) | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "square.and.pencil") | ||||||||||
| } | ||||||||||
| .disabled(!isSingle) | ||||||||||
|
|
||||||||||
| Spacer() | ||||||||||
|
|
||||||||||
| Button { | ||||||||||
| activeAlert = .moveOptions | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "folder") | ||||||||||
| } | ||||||||||
| .disabled(model.selectedItems.isEmpty) | ||||||||||
| // Left group: Edit, Move, Delete | ||||||||||
| HStack { | ||||||||||
| Button { | ||||||||||
| activeSheet = .itemDetails(item!) | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "square.and.pencil") | ||||||||||
| .frame(width: 44, height: 44) | ||||||||||
| } | ||||||||||
| .disabled(!isSingle) | ||||||||||
|
|
||||||||||
| Spacer() | ||||||||||
| Button { | ||||||||||
| activeAlert = .moveOptions | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "folder") | ||||||||||
| .frame(width: 44, height: 44) | ||||||||||
| } | ||||||||||
| .disabled(model.selectedItems.isEmpty) | ||||||||||
|
|
||||||||||
| Button { | ||||||||||
| activeAlert = .delete | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "trash") | ||||||||||
| Button { | ||||||||||
| activeAlert = .delete | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "trash") | ||||||||||
| .frame(width: 44, height: 44) | ||||||||||
| } | ||||||||||
| .disabled(model.selectedItems.isEmpty) | ||||||||||
| } | ||||||||||
| .disabled(model.selectedItems.isEmpty) | ||||||||||
|
|
||||||||||
| Spacer() | ||||||||||
|
|
||||||||||
| Button { | ||||||||||
| activeConfirmationDialog = .itemOptions | ||||||||||
| } label: { | ||||||||||
| // Right: More options | ||||||||||
| if model.selectedItems.isEmpty { | ||||||||||
| Image(systemName: "ellipsis") | ||||||||||
| .foregroundStyle(.secondary) | ||||||||||
| } else { | ||||||||||
| Menu { | ||||||||||
| itemOptionsMenu() | ||||||||||
| } label: { | ||||||||||
| Image(systemName: "ellipsis") | ||||||||||
| } | ||||||||||
| } | ||||||||||
| .disabled(model.selectedItems.isEmpty) | ||||||||||
|
|
||||||||||
| Spacer() | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private func handleArtworkTap(for item: SimpleLibraryItem) { | ||||||||||
|
|
@@ -562,103 +567,186 @@ extension ItemListView { | |||||||||
| return title | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // MARK: - Item Options (Dialog order: top to bottom) | ||||||||||
|
|
||||||||||
| @ViewBuilder | ||||||||||
| // swiftlint:disable:next function_body_length | ||||||||||
| func itemOptionsDialog() -> some View { | ||||||||||
| let item = model.selectedItems.first | ||||||||||
| let isSingle = model.selectedItems.count == 1 | ||||||||||
| detailsOption(forMenu: false) | ||||||||||
| moveOption(forMenu: false) | ||||||||||
| shareOption(forMenu: false) | ||||||||||
| jumpToStartOption(forMenu: false) | ||||||||||
| markFinishedOption(forMenu: false) | ||||||||||
| boundBooksOption(forMenu: false) | ||||||||||
| downloadOption(forMenu: false) | ||||||||||
| deleteOption(forMenu: false) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let areAllFinished: Bool = model.selectedItems.allSatisfy { $0.isFinished } | ||||||||||
| let markTitle: String = | ||||||||||
| areAllFinished | ||||||||||
| ? "mark_unfinished_title".localized | ||||||||||
| : "mark_finished_title".localized | ||||||||||
| /// Menu version with reversed order (Menu displays first item at bottom) | ||||||||||
| @ViewBuilder | ||||||||||
| func itemOptionsMenu() -> some View { | ||||||||||
| deleteOption(forMenu: true) | ||||||||||
| downloadOption(forMenu: true) | ||||||||||
| boundBooksOption(forMenu: true) | ||||||||||
| markFinishedOption(forMenu: true) | ||||||||||
| jumpToStartOption(forMenu: true) | ||||||||||
| shareOption(forMenu: true) | ||||||||||
| moveOption(forMenu: true) | ||||||||||
| detailsOption(forMenu: true) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let allAreBound: Bool = model.selectedItems.allSatisfy { $0.type == .bound } | ||||||||||
| let multipleBooks: Bool = model.selectedItems.count > 1 && model.selectedItems.allSatisfy { $0.type == .book } | ||||||||||
| let singleFolder: Bool = isSingle && (item?.type == .folder) | ||||||||||
| let canCreateBound: Bool = multipleBooks || singleFolder | ||||||||||
| // MARK: - Individual Option Builders | ||||||||||
|
|
||||||||||
| @ViewBuilder | ||||||||||
| private func detailsOption(forMenu: Bool) -> some View { | ||||||||||
| let item = model.selectedItems.first | ||||||||||
| let isSingle = model.selectedItems.count == 1 | ||||||||||
|
|
||||||||||
| Button("details_title") { | ||||||||||
| Button { | ||||||||||
| activeSheet = .itemDetails(item!) | ||||||||||
|
||||||||||
| activeSheet = .itemDetails(item!) | |
| if let item = item { | |
| activeSheet = .itemDetails(item) | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -506,12 +506,17 @@ extension ItemListViewModel { | |||||||||||||||
| let gotAccess = url.startAccessingSecurityScopedResource() | ||||||||||||||||
| if !gotAccess { return } | ||||||||||||||||
|
|
||||||||||||||||
| defer { url.stopAccessingSecurityScopedResource() } | ||||||||||||||||
|
|
||||||||||||||||
| // Prevent importing the app's own Documents folder or subfolders | ||||||||||||||||
| if DataManager.isAppOwnFolder(url) { | ||||||||||||||||
| return | ||||||||||||||||
| } | ||||||||||||||||
|
Comment on lines
+512
to
+514
|
||||||||||||||||
|
|
||||||||||||||||
| let destinationURL = documentsFolder.appendingPathComponent(url.lastPathComponent) | ||||||||||||||||
| if !FileManager.default.fileExists(atPath: destinationURL.path) { | ||||||||||||||||
| try! FileManager.default.copyItem(at: url, to: destinationURL) | ||||||||||||||||
|
||||||||||||||||
| try! FileManager.default.copyItem(at: url, to: destinationURL) | |
| do { | |
| try FileManager.default.copyItem(at: url, to: destinationURL) | |
| } catch { | |
| // Handle copy errors gracefully instead of crashing | |
| loadingState.error = error | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force-unwrapping 'item!' is unsafe here. While the button is disabled when not isSingle, there's still a risk if the state changes between the disabled check and the button action. Consider using optional binding instead: 'if let item = item { activeSheet = .itemDetails(item) }' or using a guard statement to safely handle the nil case.