Skip to content
Draft
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
Binary file modified .DS_Store
Binary file not shown.
8 changes: 4 additions & 4 deletions Ruddarr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
79159D712B5955A800F7F997 /* DummyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79159D702B5955A800F7F997 /* DummyAPI.swift */; };
794BD7822B5ED48E003819AB /* Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794BD7812B5ED48E003819AB /* Binding.swift */; };
795B7AD72B5AFA8C00A13DB3 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795B7AD62B5AFA8C00A13DB3 /* AppError.swift */; };
798010BA2B61719C00BBC056 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798010B92B61719C00BBC056 /* Router.swift */; };
796C766B2B628E6000EF3DB8 /* SwitchToNewInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796C766A2B628E6000EF3DB8 /* SwitchToNewInstance.swift */; };
BB456D202B58B7E700C29B00 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB456D1F2B58B7E700C29B00 /* Network.swift */; };
BB456D232B58C71300C29B00 /* NoInternet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB456D222B58C71300C29B00 /* NoInternet.swift */; };
BB456D272B58E4B900C29B00 /* system-status.json in Resources */ = {isa = PBXBuildFile; fileRef = BB456D262B58E4B900C29B00 /* system-status.json */; };
Expand Down Expand Up @@ -49,7 +49,7 @@
79159D702B5955A800F7F997 /* DummyAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyAPI.swift; sourceTree = "<group>"; };
794BD7812B5ED48E003819AB /* Binding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Binding.swift; sourceTree = "<group>"; };
795B7AD62B5AFA8C00A13DB3 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
798010B92B61719C00BBC056 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
796C766A2B628E6000EF3DB8 /* SwitchToNewInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchToNewInstance.swift; sourceTree = "<group>"; };
BB456D1F2B58B7E700C29B00 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
BB456D222B58C71300C29B00 /* NoInternet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoInternet.swift; sourceTree = "<group>"; };
BB456D262B58E4B900C29B00 /* system-status.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "system-status.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -98,7 +98,6 @@
children = (
79159D6F2B59540000F7F997 /* API */,
79159D6B2B5953D700F7F997 /* Dependencies.swift */,
798010B92B61719C00BBC056 /* Router.swift */,
);
path = Dependencies;
sourceTree = "<group>";
Expand All @@ -121,6 +120,7 @@
BBC94DE22B5F3DE600504568 /* CloudStorage.swift */,
BBC94DE42B5F3E0B00504568 /* CloudStorageSync.swift */,
BBC94DE82B5F63A000504568 /* Telemetry.swift */,
796C766A2B628E6000EF3DB8 /* SwitchToNewInstance.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -321,10 +321,10 @@
BBC45B962B572A8600AB258F /* PreviewData.swift in Sources */,
795B7AD72B5AFA8C00A13DB3 /* AppError.swift in Sources */,
BB456D202B58B7E700C29B00 /* Network.swift in Sources */,
798010BA2B61719C00BBC056 /* Router.swift in Sources */,
BBE1E43F2B51F61700946222 /* MovieLookup.swift in Sources */,
79159D712B5955A800F7F997 /* DummyAPI.swift in Sources */,
BBF94F652B50C88300300EBA /* Instance.swift in Sources */,
796C766B2B628E6000EF3DB8 /* SwitchToNewInstance.swift in Sources */,
BBE8286A2B4F325400C1E1D9 /* ShowsView.swift in Sources */,
79159D6C2B5953D700F7F997 /* Dependencies.swift in Sources */,
BBF94F632B508F8B00300EBA /* MovieView.swift in Sources */,
Expand Down
1 change: 0 additions & 1 deletion Ruddarr/Dependencies/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import SwiftUI

struct Dependencies {
var api: API
@Bindable var router = Router.shared
}

extension Dependencies {
Expand Down
32 changes: 0 additions & 32 deletions Ruddarr/Dependencies/Router.swift

This file was deleted.

28 changes: 28 additions & 0 deletions Ruddarr/Utilities/SwitchToNewInstance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation
import SwiftUI

extension EnvironmentValues {
subscript<Key: EnvironmentKey>(key key: Key.Type = Key.self) -> Key where Key.Value == Key {
get { self[Key.self] }
set { self[Key.self] = newValue }
}
}

extension EnvironmentValues {
var switchToNewInstance: () -> Void {
get {
// we can make some of this syntax more tolerable but its extra work
let tabRouter = self[key: TabRouter.self]
let settingsRouter = self[key: SettingsView.Router.self]
return {
tabRouter.selectedTab = .settings
Task { @MainActor in
// delay is just for UX reasons (so user realizes where they went)
try await Task.sleep(until: .now + .seconds(0.1))
assert(settingsRouter.path.isEmpty) // FUTURE: make a decision on whether its safe to switch to newInstance regardless of the fact that settings tab already had active navigation
settingsRouter.path = .init([SettingsView.Path.createInstance])
}
}
}
}
}
81 changes: 71 additions & 10 deletions Ruddarr/Views/ContentView.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,80 @@
import SwiftUI

extension EnvironmentValues {
subscript<Key: EnvironmentKey>(key _: KeyPath<Key,Key> = \Key.self) -> Key.Value {
get { self[Key.self] }
set { self[Key.self] = newValue }
}
}
extension Environment where Value: EnvironmentKey, Value.Value == Value {
init() {
self.init(\.[key: \Value.self])
}
}

protocol EmptyInitilizable {
init()
}
protocol DefaultKey: EnvironmentKey, EmptyInitilizable {
}
fileprivate var singletonCache: [ObjectIdentifier: Any] = [:]

extension DefaultKey where Value == Self {
// unfortunately this generic context doesn't support stored static properties, so we need our own external cache (to make sure defaultValue is always same instance). Not a big deal.
static var defaultValue: Self {
singletonCache[ObjectIdentifier(Self.self)] as? Self ?? {
let instance = Self()
singletonCache[ObjectIdentifier(Self.self)] = instance
return instance
}()
}
}

@Observable final class TabRouter: DefaultKey {
var selectedTab: Tab = .movies
}

enum Tab: Hashable, CaseIterable, Identifiable {
var id: Self { self }

case movies
case shows
case settings

@ViewBuilder
var label: some View {
switch self {
case .movies:
Label("Movies", systemImage: "popcorn.fill")
case .shows:
Label("Shows", systemImage: "tv.inset.filled")
case .settings:
Label("Settings", systemImage: "gear")
}
}
}


struct ContentView: View {

@Environment() var tabRouter: TabRouter
@Environment() var moviesRouter: MoviesView.Router

@State private var columnVisibility: NavigationSplitViewVisibility = .detailOnly

var body: some View {
@Bindable var tabRouter = tabRouter
if UIDevice.current.userInterfaceIdiom == .pad {
NavigationSplitView(columnVisibility: $columnVisibility) {
List(selection: dependencies.$router.selectedTab.optional) {
List(selection: $tabRouter.selectedTab.optional) {
Text("Ruddarr")
.font(.title)
.fontWeight(.bold)
.padding(.bottom)

ForEach(Tab.allCases) { tab in
let button = Button {
dependencies.router.selectedTab = tab
tabRouter.selectedTab = tab
columnVisibility = .detailOnly
} label: {
tab.label
Expand All @@ -30,11 +90,11 @@ struct ContentView: View {
}
}
} detail: {
screen(for: dependencies.router.selectedTab)
screen(for: tabRouter.selectedTab)
}
} else {
TabView(selection: dependencies.$router.selectedTab.onSet {
if $0 == dependencies.router.selectedTab {
TabView(selection: $tabRouter.selectedTab.onSet {
if $0 == tabRouter.selectedTab {
pop(tab: $0)
}
}) {
Expand All @@ -50,9 +110,10 @@ struct ContentView: View {
func pop(tab: Tab) {
switch tab {
case .movies:
dependencies.router.moviesPath = .init()
moviesRouter.path = .init()
case .settings:
dependencies.router.settingsPath = .init()
break
// router.settingsPath = .init()
default:
break
}
Expand All @@ -63,9 +124,9 @@ struct ContentView: View {
switch tab {
case .movies:
MoviesView(
onSettingsLinkTapped: {
dependencies.router.selectedTab = .settings
}
// onSettingsLinkTapped: {
// router.selectedTab = .settings
// }
)
case .shows:
ShowsView()
Expand Down
8 changes: 4 additions & 4 deletions Ruddarr/Views/InstanceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,11 @@ extension ValidationError: LocalizedError {
}

#Preview {
dependencies.router.selectedTab = .settings
// dependencies.router.selectedTab = .settings

dependencies.router.settingsPath.append(
SettingsView.Path.createInstance
)
// dependencies.router.settingsPath.append(
// SettingsView.Path.createInstance
// )

return ContentView()
}
4 changes: 2 additions & 2 deletions Ruddarr/Views/MovieSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ struct MovieLookupSheet: View {
}

#Preview {
dependencies.router.selectedTab = .movies
dependencies.router.moviesPath.append(MoviesView.Path.search)
// dependencies.router.selectedTab = .movies
// dependencies.router.moviesPath.append(MoviesView.Path.search)

return ContentView()
}
8 changes: 4 additions & 4 deletions Ruddarr/Views/MovieView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ struct MovieView: View {
#Preview {
let movies: [Movie] = PreviewData.load(name: "movies")

dependencies.router.selectedTab = .movies
// dependencies.router.selectedTab = .movies

dependencies.router.moviesPath.append(
MoviesView.Path.movie(movies[2].id)
)
// dependencies.router.moviesPath.append(
// MoviesView.Path.movie(movies[2].id)
// )

return ContentView()
}
17 changes: 12 additions & 5 deletions Ruddarr/Views/MoviesView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import SwiftUI

struct MoviesView: View {

@Observable final class Router: DefaultKey {
static var singletonCache: MoviesView.Router?

var path: NavigationPath = .init()
}
@Environment() var router: Router
@Environment(\.switchToNewInstance) var switchToNewInstance
@State private var searchQuery = ""
@State private var searchPresented = false

Expand All @@ -20,14 +28,13 @@ struct MoviesView: View {
case movie(Movie.ID)
}

var onSettingsLinkTapped: () -> Void = { }

var body: some View {
@Bindable var router = router
let gridItemLayout = [
GridItem(.adaptive(minimum: 250), spacing: 15)
]

NavigationStack(path: dependencies.$router.moviesPath) {
NavigationStack(path: $router.path) {
Group {
if let radarrInstance {
ScrollView {
Expand Down Expand Up @@ -110,7 +117,7 @@ struct MoviesView: View {
description: Text("Connect a Radarr instance under [Settings](#view).")
)
.environment(\.openURL, .init { _ in
onSettingsLinkTapped()
switchToNewInstance()
return .handled
})
}
Expand All @@ -123,7 +130,7 @@ struct MoviesView: View {
).environment(\.openURL, .init { _ in
searchQuery = ""
searchPresented = false
dependencies.router.moviesPath.append(MoviesView.Path.search)
router.path.append(MoviesView.Path.search)
return .handled
})
}
Expand Down
17 changes: 11 additions & 6 deletions Ruddarr/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import SwiftUI
import Nuke

struct SettingsView: View {
@Observable final class Router: DefaultKey {
var path: NavigationPath = .init()
}
@Environment() var router: Router
private let log: Logger = logger("settings")

@CloudStorage("instances") private var instances: [Instance] = []
Expand All @@ -14,7 +18,8 @@ struct SettingsView: View {
}

var body: some View {
NavigationStack(path: dependencies.$router.settingsPath) {
@Bindable var router = router
NavigationStack(path: $router.path) {
List {
instanceSection
aboutSection
Expand Down Expand Up @@ -234,17 +239,17 @@ struct ThridPartyLibraries: View {
}

#Preview {
dependencies.router.selectedTab = .settings
// dependencies.router.selectedTab = .settings

return ContentView()
}

#Preview("Libraries") {
dependencies.router.selectedTab = .settings
// dependencies.router.selectedTab = .settings

dependencies.router.settingsPath.append(
SettingsView.Path.libraries
)
// dependencies.router.settingsPath.append(
// SettingsView.Path.libraries
// )

return ContentView()
}
5 changes: 2 additions & 3 deletions Ruddarr/Views/ShowsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ struct ShowsView: View {
}

#Preview {
dependencies.router.selectedTab = .shows

return ContentView()
// dependencies.router.selectedTab = .shows
ContentView().environment(\.[key: \TabRouter], .init())
}