Skip to content

Commit

Permalink
Merge pull request #1208 from lysanntranvouez/feature/jellyfin-download
Browse files Browse the repository at this point in the history
Jellyfin audiobook download
  • Loading branch information
GianniCarlo authored Dec 12, 2024
2 parents bd6fdc7 + fbb3284 commit 613071d
Show file tree
Hide file tree
Showing 52 changed files with 2,959 additions and 172 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
excluded:
- BookPlayer/Generated/AutoMockable.generated.swift
- BookPlayerTests
- BookPlayer/Utils/Extensions/BlurHashDecode.swift
cyclomatic_complexity:
ignores_case_statements: true
# line_length: 120
Expand Down
145 changes: 145 additions & 0 deletions BookPlayer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "a666f382d5a4884d3a9212a8e4b32eb8280bc2409cc22962bfa19e19731621ac",
"originHash" : "ba453c3eb37ad982efef5d10374603d6da1a1e576981abea5aa656dce0280e2e",
"pins" : [
{
"identity" : "devicekit",
Expand All @@ -19,6 +19,15 @@
"version" : "2.7.3"
}
},
{
"identity" : "get",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Get",
"state" : {
"revision" : "31249885da1052872e0ac91a2943f62567c0d96d",
"version" : "2.2.1"
}
},
{
"identity" : "idzswiftcommoncrypto",
"kind" : "remoteSourceControl",
Expand All @@ -28,6 +37,15 @@
"version" : "0.16.1"
}
},
{
"identity" : "jellyfin-sdk-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jellyfin/jellyfin-sdk-swift.git",
"state" : {
"revision" : "a0e848a7aaec55991610818de6128b15cfcec725",
"version" : "0.4.0"
}
},
{
"identity" : "kingfisher",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -91,6 +109,15 @@
"version" : "1.1.0"
}
},
{
"identity" : "urlqueryencoder",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CreateAPI/URLQueryEncoder",
"state" : {
"revision" : "4ce950479707ea109f229d7230ec074a133b15d7",
"version" : "0.2.1"
}
},
{
"identity" : "ziparchive",
"kind" : "remoteSourceControl",
Expand Down
27 changes: 27 additions & 0 deletions BookPlayer/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
"cancel_download_title" = "Cancel download";
"remove_downloaded_file_title" = "Remove from device";
"download_from_url_title" = "Download from URL";
"download_from_jellyfin_title" = "Download from Jellyfin";
"done_title" = "Done";
"select_title" = "Select";
"sort_button_title" = "Sort";
Expand Down Expand Up @@ -322,3 +323,29 @@ We're working hard on providing a seamless experience, if possible, please conta
"repeat_turn_on_title" = "Turn on Repeat for this book";
"repeat_turn_off_title" = "Turn off Repeat for this book";
"benefits_watchapp_description" = "Stream or download your books and listen on the go without your phone.";
"jellyfin_connection_title" = "Jellyfin";
"jellyfin_connection_details_title" = "Connection Details";
"jellyfin_connect_button" = "Connect";
"jellyfin_sign_in_button" = "Sign In";
"jellyfin_to_library_button" = "Library";
"jellyfin_section_server_url" = "Server URL";
"jellyfin_server_url_placeholder" = "http://jellyfin.example.com:8096";
"jellyfin_section_server_url_footer" = "Connect to your Jellyfin server";
"jellyfin_section_server" = "Server";
"jellyfin_server_name_label" = "Name";
"jellyfin_server_url_label" = "URL";
"jellyfin_section_login" = "Login";
"jellyfin_username_placeholder" = "Username";
"jellyfin_password_placeholder" = "Password";
"jellyfin_password_remember_me_label" = "Remember Me";
"jellyfin_sign_out_button" = "Sign Out";
"settings_jellyfin_title" = "Jellyfin";
"settings_jellyfin_manage_connection_title" = "Manage Connection";
"jellyfin_internal_error_invalid_url" = "Internal error: Request URL is invalid: %@";
"jellyfin_internal_error_build_url" = "Internal error: Failed to build request URL";
"jellyfin_internal_error_no_client" = "Cannot contact Jellyfin server. Check the provided server URL.";
"jellyfin_error_unexpected_response" = "Unexpected server response";
"jellyfin_error_unexpected_response_with_code" = "Unexpected server response (Code: %d - %@)";
"jellyfin_error_unauthorized" = "Sign In failed. Check your username and password.";
"file_size_unknown" = "Unknown size";
"runtime_unknown" = "Unknown runtime";
12 changes: 9 additions & 3 deletions BookPlayer/Coordinators/FolderListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@ class FolderListCoordinator: ItemListCoordinator {
flow: BPCoordinatorPresentationFlow,
folderRelativePath: String,
playerManager: PlayerManagerProtocol,
singleFileDownloadService: SingleFileDownloadService,
libraryService: LibraryServiceProtocol,
playbackService: PlaybackServiceProtocol,
syncService: SyncServiceProtocol,
importManager: ImportManager,
listRefreshService: ListSyncRefreshService
listRefreshService: ListSyncRefreshService,
jellyfinConnectionService: JellyfinConnectionService
) {
self.folderRelativePath = folderRelativePath

super.init(
flow: flow,
playerManager: playerManager,
singleFileDownloadService: singleFileDownloadService,
libraryService: libraryService,
playbackService: playbackService,
syncService: syncService,
importManager: importManager,
listRefreshService: listRefreshService
listRefreshService: listRefreshService,
jellyfinConnectionService: jellyfinConnectionService
)
}

Expand All @@ -40,7 +44,7 @@ class FolderListCoordinator: ItemListCoordinator {
let viewModel = ItemListViewModel(
folderRelativePath: self.folderRelativePath,
playerManager: self.playerManager,
networkClient: NetworkClient(),
singleFileDownloadService: self.singleFileDownloadService,
libraryService: self.libraryService,
playbackService: self.playbackService,
syncService: self.syncService,
Expand All @@ -56,6 +60,8 @@ class FolderListCoordinator: ItemListCoordinator {
self.loadPlayer(relativePath)
case .showDocumentPicker:
self.showDocumentPicker()
case .showJellyfinDownloader:
self.showJellyfinDownloader()
case .showSearchList(let relativePath, let placeholderTitle):
self.showSearchList(at: relativePath, placeholderTitle: placeholderTitle)
case .showItemDetails(let item):
Expand Down
21 changes: 19 additions & 2 deletions BookPlayer/Coordinators/ItemListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,37 @@ import UniformTypeIdentifiers

class ItemListCoordinator: NSObject, Coordinator, AlertPresenter, BPLogger {
let playerManager: PlayerManagerProtocol
let singleFileDownloadService: SingleFileDownloadService
let libraryService: LibraryServiceProtocol
let playbackService: PlaybackServiceProtocol
let syncService: SyncServiceProtocol
let importManager: ImportManager
let listRefreshService: ListSyncRefreshService
let jellyfinConnectionService: JellyfinConnectionService
let flow: BPCoordinatorPresentationFlow

weak var documentPickerDelegate: UIDocumentPickerDelegate?

init(
flow: BPCoordinatorPresentationFlow,
playerManager: PlayerManagerProtocol,
singleFileDownloadService: SingleFileDownloadService,
libraryService: LibraryServiceProtocol,
playbackService: PlaybackServiceProtocol,
syncService: SyncServiceProtocol,
importManager: ImportManager,
listRefreshService: ListSyncRefreshService
listRefreshService: ListSyncRefreshService,
jellyfinConnectionService: JellyfinConnectionService
) {
self.flow = flow
self.playerManager = playerManager
self.singleFileDownloadService = singleFileDownloadService
self.libraryService = libraryService
self.playbackService = playbackService
self.syncService = syncService
self.importManager = importManager
self.listRefreshService = listRefreshService
self.jellyfinConnectionService = jellyfinConnectionService
}

func start() {
Expand All @@ -54,11 +60,13 @@ class ItemListCoordinator: NSObject, Coordinator, AlertPresenter, BPLogger {
flow: .pushFlow(navigationController: flow.navigationController),
folderRelativePath: relativePath,
playerManager: playerManager,
singleFileDownloadService: singleFileDownloadService,
libraryService: libraryService,
playbackService: playbackService,
syncService: syncService,
importManager: importManager,
listRefreshService: listRefreshService
listRefreshService: listRefreshService,
jellyfinConnectionService: jellyfinConnectionService
)
child.start()
}
Expand Down Expand Up @@ -160,6 +168,15 @@ extension ItemListCoordinator {
flow.navigationController.present(providerList, animated: true, completion: nil)
}

func showJellyfinDownloader() {
let child = JellyfinCoordinator(
flow: .modalFlow(presentingController: flow.navigationController, modalPresentationStyle: .pageSheet),
singleFileDownloadService: singleFileDownloadService,
jellyfinConnectionService: jellyfinConnectionService
)
child.start()
}

func showExportController(for items: [SimpleLibraryItem]) {
let providers = items.map { BookActivityItemProvider($0) }

Expand Down
21 changes: 9 additions & 12 deletions BookPlayer/Coordinators/LibraryListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,27 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
init(
flow: BPCoordinatorPresentationFlow,
playerManager: PlayerManagerProtocol,
singleFileDownloadService: SingleFileDownloadService,
libraryService: LibraryServiceProtocol,
playbackService: PlaybackServiceProtocol,
syncService: SyncServiceProtocol,
importManager: ImportManager,
listRefreshService: ListSyncRefreshService,
accountService: AccountServiceProtocol
accountService: AccountServiceProtocol,
jellyfinConnectionService: JellyfinConnectionService
) {
self.accountService = accountService

super.init(
flow: flow,
playerManager: playerManager,
singleFileDownloadService: singleFileDownloadService,
libraryService: libraryService,
playbackService: playbackService,
syncService: syncService,
importManager: importManager,
listRefreshService: listRefreshService
listRefreshService: listRefreshService,
jellyfinConnectionService: jellyfinConnectionService
)
}

Expand All @@ -54,7 +58,7 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
let viewModel = ItemListViewModel(
folderRelativePath: nil,
playerManager: self.playerManager,
networkClient: NetworkClient(),
singleFileDownloadService: self.singleFileDownloadService,
libraryService: self.libraryService,
playbackService: self.playbackService,
syncService: self.syncService,
Expand All @@ -70,6 +74,8 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
self.loadPlayer(relativePath)
case .showDocumentPicker:
self.showDocumentPicker()
case .showJellyfinDownloader:
self.showJellyfinDownloader()
case .showSearchList(let relativePath, let placeholderTitle):
self.showSearchList(at: relativePath, placeholderTitle: placeholderTitle)
case .showItemDetails(let item):
Expand Down Expand Up @@ -311,15 +317,6 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
lastItemListViewController.viewModel.viewDidAppear()
}

func handleDownloadAction(url: URL) {
guard
let libraryListViewController = flow.navigationController.viewControllers.first as? ItemListViewController
else { return }

libraryListViewController.setEditing(false, animated: false)
libraryListViewController.viewModel.handleDownload(url)
}

override func syncList() {
/// Process any deferred progress calculations for folders
if playbackService.processFoldersStaleProgress() {
Expand Down
11 changes: 9 additions & 2 deletions BookPlayer/Coordinators/MainCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ class MainCoordinator: NSObject {
var tabBarController: AppTabBarController?

let playerManager: PlayerManagerProtocol
let singleFileDownloadService: SingleFileDownloadService
let libraryService: LibraryServiceProtocol
let playbackService: PlaybackServiceProtocol
let accountService: AccountServiceProtocol
var syncService: SyncServiceProtocol
let watchConnectivityService: PhoneWatchConnectivityService
let jellyfinConnectionService: JellyfinConnectionService

let navigationController: UINavigationController
var libraryCoordinator: LibraryListCoordinator?
Expand All @@ -36,7 +38,9 @@ class MainCoordinator: NSObject {
self.syncService = coreServices.syncService
self.playbackService = coreServices.playbackService
self.playerManager = coreServices.playerManager
self.singleFileDownloadService = SingleFileDownloadService(networkClient: NetworkClient())
self.watchConnectivityService = coreServices.watchService
self.jellyfinConnectionService = JellyfinConnectionService(keychainService: KeychainService())

ThemeManager.shared.libraryService = libraryService

Expand Down Expand Up @@ -84,6 +88,7 @@ class MainCoordinator: NSObject {
let libraryCoordinator = LibraryListCoordinator(
flow: .pushFlow(navigationController: AppNavigationController.instantiate(from: .Main)),
playerManager: self.playerManager,
singleFileDownloadService: self.singleFileDownloadService,
libraryService: self.libraryService,
playbackService: self.playbackService,
syncService: syncService,
Expand All @@ -92,7 +97,8 @@ class MainCoordinator: NSObject {
playerManager: playerManager,
syncService: syncService
),
accountService: self.accountService
accountService: self.accountService,
jellyfinConnectionService: jellyfinConnectionService
)
playerManager.syncProgressDelegate = libraryCoordinator
self.libraryCoordinator = libraryCoordinator
Expand All @@ -117,7 +123,8 @@ class MainCoordinator: NSObject {
flow: .pushFlow(navigationController: AppNavigationController.instantiate(from: .Settings)),
libraryService: self.libraryService,
syncService: self.syncService,
accountService: self.accountService
accountService: self.accountService,
jellyfinConnectionService: jellyfinConnectionService
)
settingsCoordinator.tabBarController = tabBarController
settingsCoordinator.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import UIKit

protocol BPCoordinatorPresentationFlow {
public protocol BPCoordinatorPresentationFlow {
/// Navigation used for the flow of the coordinator
var navigationController: UINavigationController { get }
/// Start the flow with the specified starting screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import UIKit

/// Handle a modal presentation without a root navigation
class BPModalOnlyPresentationFlow: BPCoordinatorPresentationFlow {
public class BPModalOnlyPresentationFlow: BPCoordinatorPresentationFlow {
/// Not available in this flow
var navigationController: UINavigationController {
public var navigationController: UINavigationController {
fatalError("Navigation not available on this type of coordinator flow")
}

Expand Down
Loading

0 comments on commit 613071d

Please sign in to comment.