Skip to content
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

Start work on offline download support #1065

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
7 changes: 5 additions & 2 deletions Shared/Coordinators/DownloadListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
//

#if os(iOS)
import Factory
import Foundation
import Stinsen
import SwiftUI

final class DownloadListCoordinator: NavigationCoordinatable {
@Injected(Container.downloadManager)
private var downloadManager

let stack = NavigationStack(initial: \DownloadListCoordinator.start)

Expand All @@ -20,13 +23,13 @@ final class DownloadListCoordinator: NavigationCoordinatable {
@Route(.modal)
var downloadTask = makeDownloadTask

func makeDownloadTask(downloadTask: DownloadTask) -> NavigationViewCoordinator<DownloadTaskCoordinator> {
func makeDownloadTask(downloadTask: DownloadEntity) -> NavigationViewCoordinator<DownloadTaskCoordinator> {
NavigationViewCoordinator(DownloadTaskCoordinator(downloadTask: downloadTask))
}

@ViewBuilder
private func makeStart() -> DownloadListView {
DownloadListView(viewModel: .init())
DownloadListView(viewModel: .init(), downloadManager: downloadManager)
}
}
#endif
4 changes: 2 additions & 2 deletions Shared/Coordinators/DownloadTaskCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ final class DownloadTaskCoordinator: NavigationCoordinatable {
@Root
var start = makeStart

let downloadTask: DownloadTask
let downloadTask: DownloadEntity

init(downloadTask: DownloadTask) {
init(downloadTask: DownloadEntity) {
self.downloadTask = downloadTask
}

Expand Down
9 changes: 9 additions & 0 deletions Shared/Coordinators/File.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//

import Foundation
2 changes: 1 addition & 1 deletion Shared/Coordinators/ItemCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ final class ItemCoordinator: NavigationCoordinatable {
}

#if os(iOS)
func makeDownloadTask(downloadTask: DownloadTask) -> NavigationViewCoordinator<DownloadTaskCoordinator> {
func makeDownloadTask(downloadTask: DownloadEntity) -> NavigationViewCoordinator<DownloadTaskCoordinator> {
NavigationViewCoordinator(DownloadTaskCoordinator(downloadTask: downloadTask))
}
#endif
Expand Down
17 changes: 17 additions & 0 deletions Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import SwiftUI

final class MainCoordinator: NavigationCoordinatable {

@Default(.Experimental.offlineMode)
private var offlineMode

@Injected(LogManager.service)
private var logger

Expand All @@ -31,6 +34,8 @@ final class MainCoordinator: NavigationCoordinatable {
@Root
var mainTab = makeMainTab
@Root
var offlineView = makeOffline
@Root
var selectUser = makeSelectUser
@Root
var serverCheck = makeServerCheck
Expand All @@ -50,6 +55,14 @@ final class MainCoordinator: NavigationCoordinatable {
do {
try await SwiftfinStore.setupDataStack()

if offlineMode {
await MainActor.run {
withAnimation(.linear(duration: 0.1)) {
let _ = root(\.offlineView)
}
}
return
}
if UserSession.current() != nil, !Defaults[.signOutOnClose] {
await MainActor.run {
withAnimation(.linear(duration: 0.1)) {
Expand Down Expand Up @@ -138,6 +151,10 @@ final class MainCoordinator: NavigationCoordinatable {
MainTabCoordinator()
}

func makeOffline() -> NavigationViewCoordinator<OfflineCoordinator> {
NavigationViewCoordinator(OfflineCoordinator())
}

func makeSelectUser() -> NavigationViewCoordinator<SelectUserCoordinator> {
NavigationViewCoordinator(SelectUserCoordinator())
}
Expand Down
41 changes: 36 additions & 5 deletions Shared/Coordinators/MainCoordinator/iOSMainTabCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,35 @@
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//

import Defaults
import Foundation
import Stinsen
import SwiftUI

final class MainTabCoordinator: TabCoordinatable {

var child = TabChild(startingItems: [
\MainTabCoordinator.home,
\MainTabCoordinator.search,
\MainTabCoordinator.media,
])
var child: TabChild

init() {
self.child = TabChild(startingItems: [
\MainTabCoordinator.home,
\MainTabCoordinator.search,
\MainTabCoordinator.media,
])
if Defaults[.Experimental.downloads] {
self.child = TabChild(startingItems: [
\MainTabCoordinator.home,
\MainTabCoordinator.search,
\MainTabCoordinator.media,
\MainTabCoordinator.downloads,
])
}
}

@Route(tabItem: makeHomeTab, onTapped: onHomeTapped)
var home = makeHome
@Route(tabItem: makeDownloadsTab, onTapped: onDownloadsTapped)
var downloads = makeDownloads
@Route(tabItem: makeSearchTab, onTapped: onSearchTapped)
var search = makeSearch
@Route(tabItem: makeMediaTab, onTapped: onMediaTapped)
Expand All @@ -41,6 +56,22 @@ final class MainTabCoordinator: TabCoordinatable {
L10n.home.text
}

@ViewBuilder
func makeDownloadsTab(isActive: Bool) -> some View {
Image(systemName: "square.and.arrow.down.fill")
L10n.downloads.text
}

func makeDownloads() -> NavigationViewCoordinator<OfflineCoordinator> {
NavigationViewCoordinator(OfflineCoordinator())
}

func onDownloadsTapped(isRepeat: Bool, coordinator: NavigationViewCoordinator<OfflineCoordinator>) {
if isRepeat {
coordinator.child.popToRoot()
}
}

func makeSearch() -> NavigationViewCoordinator<SearchCoordinator> {
NavigationViewCoordinator(SearchCoordinator())
}
Expand Down
40 changes: 40 additions & 0 deletions Shared/Coordinators/OfflineCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//

import Foundation
import JellyfinAPI
import Stinsen
import SwiftUI

final class OfflineCoordinator: NavigationCoordinatable {

let stack = NavigationStack(initial: \OfflineCoordinator.start)

@Root
var start = makeStart

@Route(.push)
var item = makeItem
@Route(.push)
var library = makeLibrary

var viewModel = OfflineViewModel()

func makeItem(item: BaseItemDto) -> OfflineItemCoordinator {
OfflineItemCoordinator(item: item, viewModel: viewModel)
}

func makeLibrary(viewModel: PagingLibraryViewModel<BaseItemDto>) -> LibraryCoordinator<BaseItemDto> {
LibraryCoordinator(viewModel: viewModel)
}

@ViewBuilder
func makeStart() -> some View {
OfflineView(viewModel: viewModel)
}
}
66 changes: 66 additions & 0 deletions Shared/Coordinators/OfflineItemCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//

import Foundation
import JellyfinAPI
import Stinsen
import SwiftUI

final class OfflineItemCoordinator: NavigationCoordinatable {

let stack = NavigationStack(initial: \OfflineItemCoordinator.start)

@Root
var start = makeStart
@Route(.push)
var item = makeItem
@Route(.push)
var library = makeLibrary

@Route(.modal)
var itemOverview = makeItemOverview
@Route(.modal)
var mediaSourceInfo = makeMediaSourceInfo
@Route(.modal)
var downloadTask = makeDownloadTask

private let itemDto: BaseItemDto
private let viewModel: OfflineViewModel

init(item: BaseItemDto, viewModel: OfflineViewModel) {
self.itemDto = item
self.viewModel = viewModel
}

func makeItem(item: BaseItemDto) -> OfflineItemCoordinator {
OfflineItemCoordinator(item: item, viewModel: viewModel)
}

func makeLibrary(viewModel: PagingLibraryViewModel<BaseItemDto>) -> OfflineLibraryCoordinator<BaseItemDto> {
OfflineLibraryCoordinator(viewModel: viewModel, offlineViewModel: self.viewModel)
}

func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
ItemOverviewView(item: item)
}
}

func makeMediaSourceInfo(source: MediaSourceInfo) -> NavigationViewCoordinator<MediaSourceInfoCoordinator> {
NavigationViewCoordinator(MediaSourceInfoCoordinator(mediaSourceInfo: source))
}

func makeDownloadTask(downloadTask: DownloadEntity) -> NavigationViewCoordinator<DownloadTaskCoordinator> {
NavigationViewCoordinator(DownloadTaskCoordinator(downloadTask: downloadTask))
}

@ViewBuilder
func makeStart() -> some View {
OfflineItemView(item: itemDto, offlineModel: viewModel)
}
}
70 changes: 70 additions & 0 deletions Shared/Coordinators/OfflineLibraryCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
//

import Defaults
import Foundation
import JellyfinAPI
import Stinsen
import SwiftUI

final class OfflineLibraryCoordinator<Element: Poster>: NavigationCoordinatable {

let stack = NavigationStack(initial: \OfflineLibraryCoordinator.start)

@Root
var start = makeStart

#if os(tvOS)
@Route(.modal)
var item = makeItem
@Route(.push)
var library = makeLibrary
#else
@Route(.push)
var item = makeItem
@Route(.push)
var library = makeLibrary
@Route(.modal)
var filter = makeFilter
#endif

private let viewModel: PagingLibraryViewModel<Element>
private let offlineViewModel: OfflineViewModel

init(viewModel: PagingLibraryViewModel<Element>, offlineViewModel: OfflineViewModel) {
self.viewModel = viewModel
self.offlineViewModel = offlineViewModel
}

@ViewBuilder
func makeStart() -> some View {
PagingLibraryView(viewModel: viewModel)
}

#if os(tvOS)
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
NavigationViewCoordinator(ItemCoordinator(item: item))
}

func makeLibrary(viewModel: PagingLibraryViewModel<BaseItemDto>) -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
NavigationViewCoordinator(LibraryCoordinator<BaseItemDto>(viewModel: viewModel))
}
#else
func makeItem(item: BaseItemDto) -> OfflineItemCoordinator {
OfflineItemCoordinator(item: item, viewModel: offlineViewModel)
}

func makeLibrary(viewModel: PagingLibraryViewModel<BaseItemDto>) -> LibraryCoordinator<BaseItemDto> {
LibraryCoordinator<BaseItemDto>(viewModel: viewModel)
}

func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
}
#endif
}
7 changes: 7 additions & 0 deletions Shared/Extensions/Int.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ extension FixedWidthInteger {
.appending(minutesText)
.appending(secondsText)
}

var sizeLable: String {
let bcf = ByteCountFormatter()
bcf.allowedUnits = [.useMB, .useGB]
bcf.countStyle = .file
return bcf.string(fromByteCount: Int64(self))
}
}

extension Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ extension BaseItemDto: Poster {
case .episode:
if Defaults[.Customization.Episodes.useSeriesLandscapeBackdrop] {
[
imageSource(.primary, maxWidth: maxWidth),
seriesImageSource(.thumb, maxWidth: maxWidth),
seriesImageSource(.backdrop, maxWidth: maxWidth),
imageSource(.primary, maxWidth: maxWidth),
]
} else {
[imageSource(.primary, maxWidth: maxWidth)]
Expand Down
Loading