Settings for iOS/macOS

This commit is contained in:
Arkadiusz Fal
2021-09-25 10:18:22 +02:00
parent 433725c5e8
commit a7da3b9468
64 changed files with 1998 additions and 665 deletions

View File

@@ -4,8 +4,11 @@ import SwiftUI
struct ChannelVideosView: View {
let channel: Channel
@EnvironmentObject<NavigationState> private var navigationState
@EnvironmentObject<Subscriptions> private var subscriptions
@StateObject private var store = Store<Channel>()
@EnvironmentObject<InvidiousAPI> private var api
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@Environment(\.inNavigationView) private var inNavigationView
@@ -14,16 +17,8 @@ struct ChannelVideosView: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
@ObservedObject private var store = Store<Channel>()
@Namespace private var focusNamespace
init(_ channel: Channel) {
self.channel = channel
resource.addObserver(store)
}
var body: some View {
VStack {
#if os(tvOS)
@@ -55,12 +50,11 @@ struct ChannelVideosView: View {
#endif
#if !os(tvOS)
.toolbar {
ToolbarItem(placement: subscriptionToolbarItemPlacement) {
ToolbarItem {
HStack {
if let channel = store.item, let subscribers = channel.subscriptionsString {
Text("**\(subscribers)** subscribers")
.foregroundColor(.secondary)
}
Text("**\(store.item?.subscriptionsString ?? "loading")** subscribers")
.foregroundColor(.secondary)
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
subscriptionToggleButton
}
@@ -77,13 +71,19 @@ struct ChannelVideosView: View {
#endif
.modifier(UnsubscribeAlertModifier())
.onAppear {
resource.loadIfNeeded()
if store.item.isNil {
resource.addObserver(store)
resource.load()
}
}
.navigationTitle(navigationTitle)
}
var resource: Resource {
InvidiousAPI.shared.channel(channel.id)
let resource = api.channel(channel.id)
resource.addObserver(store)
return resource
}
#if !os(tvOS)
@@ -94,7 +94,7 @@ struct ChannelVideosView: View {
}
#endif
return .status
return .automatic
}
#endif
@@ -102,12 +102,12 @@ struct ChannelVideosView: View {
Group {
if subscriptions.isSubscribing(channel.id) {
Button("Unsubscribe") {
navigationState.presentUnsubscribeAlert(channel)
navigation.presentUnsubscribeAlert(channel)
}
} else {
Button("Subscribe") {
subscriptions.subscribe(channel.id) {
navigationState.sidebarSectionChanged.toggle()
navigation.sidebarSectionChanged.toggle()
}
}
}

View File

@@ -2,21 +2,22 @@ import Siesta
import SwiftUI
struct PopularView: View {
@ObservedObject private var store = Store<[Video]>()
@StateObject private var store = Store<[Video]>()
var resource = InvidiousAPI.shared.popular
@EnvironmentObject<InvidiousAPI> private var api
init() {
resource.addObserver(store)
var resource: Resource {
api.popular
}
var body: some View {
VideosView(videos: store.collection)
.onAppear {
resource.addObserver(store)
resource.loadIfNeeded()
}
#if !os(tvOS)
.navigationTitle("Popular")
#endif
.onAppear {
resource.loadIfNeeded()
}
}
}

View File

@@ -8,7 +8,7 @@ struct SearchView: View {
@Default(.searchDuration) private var searchDuration
@EnvironmentObject<Recents> private var recents
@EnvironmentObject<SearchState> private var state
@EnvironmentObject<SearchModel> private var state
@Environment(\.navigationStyle) private var navigationStyle
@@ -85,7 +85,7 @@ struct SearchView: View {
#endif
}
}
.opacity(recentsChanged ? 1 : 1)
.redrawOn(change: recentsChanged)
clearAllButton
}

View File

@@ -0,0 +1,73 @@
import Defaults
import SwiftUI
struct SignInRequiredView<Content: View>: View {
let title: String
let content: Content
@EnvironmentObject<InvidiousAPI> private var api
@Default(.instances) private var instances
@EnvironmentObject<NavigationModel> private var navigation
init(title: String, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
Group {
if api.signedIn {
content
} else {
prompt
}
}
#if !os(tvOS)
.navigationTitle(title)
#endif
}
var prompt: some View {
VStack(spacing: 30) {
Text("Sign In Required")
.font(.title2.bold())
Group {
if instances.isEmpty {
Text("You need to create an instance and accounts\nto access **\(title)** section")
} else {
Text("You need to select an account\nto access **\(title)** section")
}
}
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
.font(.title3)
.padding(.vertical)
if instances.isEmpty {
openSettingsButton
}
}
}
var openSettingsButton: some View {
Button(action: {
#if os(macOS)
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
#else
navigation.presentingSettings = true
#endif
}) {
Text("Open Settings")
}
.buttonStyle(.borderedProminent)
}
}
struct SignInRequiredView_Previews: PreviewProvider {
static var previews: some View {
SignInRequiredView(title: "Subscriptions") {
Text("Only when signed in")
}
}
}

View File

@@ -1,30 +1,46 @@
import Siesta
import SwiftUI
struct SubscriptionsView: View {
@ObservedObject private var store = Store<[Video]>()
@StateObject private var store = Store<[Video]>()
var resource = InvidiousAPI.shared.feed
@EnvironmentObject<InvidiousAPI> private var api
init() {
resource.addObserver(store)
var feed: Resource {
api.feed
}
var body: some View {
VideosView(videos: store.collection)
.onAppear {
if let home = InvidiousAPI.shared.home.loadIfNeeded() {
home.onSuccess { _ in
resource.loadIfNeeded()
}
} else {
resource.loadIfNeeded()
SignInRequiredView(title: "Subscriptions") {
VideosView(videos: store.collection)
.onAppear {
loadResources()
}
.onChange(of: api.account) { _ in
loadResources(force: true)
}
.onChange(of: feed) { _ in
loadResources(force: true)
}
}
.refreshable {
loadResources(force: true)
}
}
fileprivate func loadResources(force: Bool = false) {
feed.addObserver(store)
if let request = force ? api.home.load() : api.home.loadIfNeeded() {
request.onSuccess { _ in
loadFeed(force: force)
}
.refreshable {
resource.load()
}
#if !os(tvOS)
.navigationTitle("Subscriptions")
#endif
} else {
loadFeed(force: force)
}
}
fileprivate func loadFeed(force: Bool = false) {
_ = force ? feed.load() : feed.loadIfNeeded()
}
}

View File

@@ -2,25 +2,23 @@ import Defaults
import SwiftUI
struct VideoContextMenuView: View {
@EnvironmentObject<NavigationState> private var navigationState
@EnvironmentObject<InvidiousAPI> private var api
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<Recents> private var recents
@EnvironmentObject<Subscriptions> private var subscriptions
@EnvironmentObject<SubscriptionsModel> private var subscriptions
let video: Video
@Default(.showingAddToPlaylist) var showingAddToPlaylist
@Default(.videoIDToAddToPlaylist) var videoIDToAddToPlaylist
@State private var subscribed = false
var body: some View {
Section {
openChannelButton
subscriptionButton
.opacity(subscribed ? 1 : 1)
if navigationState.tabSelection == .playlists {
if navigation.tabSelection == .playlists {
removeFromPlaylistButton
} else {
addToPlaylistButton
@@ -32,9 +30,9 @@ struct VideoContextMenuView: View {
Button("\(video.author) Channel") {
let recent = RecentItem(from: video.channel)
recents.open(recent)
navigationState.tabSelection = .recentlyOpened(recent.tag)
navigationState.isChannelOpen = true
navigationState.sidebarSectionChanged.toggle()
navigation.tabSelection = .recentlyOpened(recent.tag)
navigation.isChannelOpen = true
navigation.sidebarSectionChanged.toggle()
}
}
@@ -45,13 +43,13 @@ struct VideoContextMenuView: View {
#if os(tvOS)
subscriptions.unsubscribe(video.channel.id)
#else
navigationState.presentUnsubscribeAlert(video.channel)
navigation.presentUnsubscribeAlert(video.channel)
#endif
}
} else {
Button("Subscribe") {
subscriptions.subscribe(video.channel.id) {
navigationState.sidebarSectionChanged.toggle()
navigation.sidebarSectionChanged.toggle()
}
}
}
@@ -67,9 +65,9 @@ struct VideoContextMenuView: View {
var removeFromPlaylistButton: some View {
Button("Remove from playlist", role: .destructive) {
let resource = InvidiousAPI.shared.playlistVideo(Defaults[.selectedPlaylistID]!, video.indexID!)
let resource = api.playlistVideo(Defaults[.selectedPlaylistID]!, video.indexID!)
resource.request(.delete).onSuccess { _ in
InvidiousAPI.shared.playlists.load()
api.playlists.load()
}
}
}