From 4330856c5e6dede7d17d5d94a28eec8b9bfe4e1c Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 16 Dec 2022 09:35:10 +0100 Subject: [PATCH] Less obnoxious error handling --- Model/Cache/SubscribedChannelsModel.swift | 7 +++--- Model/FeedModel.swift | 7 +++--- Model/NavigationModel.swift | 6 +++++ Model/PlaylistsModel.swift | 7 +++--- Shared/Playlists/PlaylistsView.swift | 7 +++++- Shared/Subscriptions/SubscriptionsView.swift | 10 ++++++++ Shared/Trending/TrendingView.swift | 24 ++++++++++++++------ Shared/Views/PopularView.swift | 19 ++++++++++++---- Shared/Views/RequestErrorButton.swift | 23 +++++++++++++++++++ Yattee.xcodeproj/project.pbxproj | 12 ++++++++-- 10 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 Shared/Views/RequestErrorButton.swift diff --git a/Model/Cache/SubscribedChannelsModel.swift b/Model/Cache/SubscribedChannelsModel.swift index 4685aaad..61c20b29 100644 --- a/Model/Cache/SubscribedChannelsModel.swift +++ b/Model/Cache/SubscribedChannelsModel.swift @@ -20,6 +20,8 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel { @Published var isLoading = false @Published var channels = [Channel]() + @Published var error: RequestError? + var accounts: AccountsModel { .shared } var resource: Resource? { @@ -67,6 +69,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel { self?.isLoading = false } .onSuccess { resource in + self.error = nil if let channels: [Channel] = resource.typedContent() { self.channels = channels channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) } @@ -75,9 +78,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel { onSuccess() } } - .onFailure { _ in - self.channels = [] - } + .onFailure { self.error = $0 } } } diff --git a/Model/FeedModel.swift b/Model/FeedModel.swift index 02e1aa43..3e13b782 100644 --- a/Model/FeedModel.swift +++ b/Model/FeedModel.swift @@ -18,6 +18,8 @@ final class FeedModel: ObservableObject, CacheModel { var storage: Storage? + @Published var error: RequestError? + private var backgroundContext = PersistenceController.shared.container.newBackgroundContext() var feed: Resource? { @@ -79,6 +81,7 @@ final class FeedModel: ObservableObject, CacheModel { onCompletion() } .onSuccess { response in + self.error = nil if let videos: [Video] = response.typedContent() { if paginating { self.videos.append(contentsOf: videos) @@ -89,9 +92,7 @@ final class FeedModel: ObservableObject, CacheModel { } } } - .onFailure { error in - NavigationModel.shared.presentAlert(title: "Could not refresh Subscriptions", message: error.userMessage) - } + .onFailure { self.error = $0 } } } diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index 157cd389..bb91903c 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -1,4 +1,5 @@ import Foundation +import Siesta import SwiftUI final class NavigationModel: ObservableObject { @@ -257,6 +258,11 @@ final class NavigationModel: ObservableObject { presentingAlert = true } + func presentRequestErrorAlert(_ error: RequestError) { + let errorDescription = String(format: "Verify you have stable connection with the server you are using (%@)", AccountsModel.shared.current.instance.longDescription) + presentAlert(title: "Connection Error", message: "\(error.userMessage)\n\n\(errorDescription)") + } + func presentAlert(_ alert: Alert) { self.alert = alert presentingAlert = true diff --git a/Model/PlaylistsModel.swift b/Model/PlaylistsModel.swift index da84fa93..129ccff0 100644 --- a/Model/PlaylistsModel.swift +++ b/Model/PlaylistsModel.swift @@ -9,6 +9,7 @@ final class PlaylistsModel: ObservableObject { @Published var isLoading = false @Published var playlists = [Playlist]() @Published var reloadPlaylists = false + @Published var error: RequestError? var accounts = AccountsModel.shared @@ -60,16 +61,14 @@ final class PlaylistsModel: ObservableObject { self?.isLoading = false } .onSuccess { resource in + self.error = nil if let playlists: [Playlist] = resource.typedContent() { self.playlists = playlists PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists) onSuccess() } } - .onFailure { error in - self.playlists = [] - NavigationModel.shared.presentAlert(title: "Could not refresh Playlists", message: error.userMessage) - } + .onFailure { self.error = $0 } } } diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index 70b0d58d..f03ba9fc 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -15,8 +15,9 @@ struct PlaylistsView: View { @StateObject private var userPlaylist = Store() @ObservedObject private var accounts = AccountsModel.shared - private var player = PlayerModel.shared @ObservedObject private var model = PlaylistsModel.shared + + private var player = PlayerModel.shared private var cache = PlaylistsCacheModel.shared @Namespace private var focusNamespace @@ -129,6 +130,10 @@ struct PlaylistsView: View { } .navigationBarTitleDisplayMode(.inline) .toolbar { + ToolbarItem { + RequestErrorButton(error: model.error) + } + ToolbarItem(placement: .principal) { playlistsMenu } diff --git a/Shared/Subscriptions/SubscriptionsView.swift b/Shared/Subscriptions/SubscriptionsView.swift index 0ec8fb24..f95e2e70 100644 --- a/Shared/Subscriptions/SubscriptionsView.swift +++ b/Shared/Subscriptions/SubscriptionsView.swift @@ -1,4 +1,5 @@ import Defaults +import Siesta import SwiftUI struct SubscriptionsView: View { @@ -11,6 +12,7 @@ struct SubscriptionsView: View { @Default(.subscriptionsListingStyle) private var subscriptionsListingStyle @ObservedObject private var feed = FeedModel.shared + @ObservedObject private var subscriptions = SubscribedChannelsModel.shared var body: some View { SignInRequiredView(title: "Subscriptions".localized()) { @@ -32,6 +34,10 @@ struct SubscriptionsView: View { ToolbarItem(placement: .principal) { subscriptionsMenu } + + ToolbarItem { + RequestErrorButton(error: requestError) + } } #endif #if os(macOS) @@ -51,6 +57,10 @@ struct SubscriptionsView: View { #endif } + var requestError: RequestError? { + subscriptionsViewPage == .channels ? subscriptions.error : feed.error + } + #if os(iOS) var subscriptionsMenu: some View { Menu { diff --git a/Shared/Trending/TrendingView.swift b/Shared/Trending/TrendingView.swift index 677759cc..6cac0182 100644 --- a/Shared/Trending/TrendingView.swift +++ b/Shared/Trending/TrendingView.swift @@ -21,6 +21,8 @@ struct TrendingView: View { ContentItem.array(of: store.collection) } + @State private var error: RequestError? + init(_ videos: [Video] = [Video]()) { self.videos = videos } @@ -52,6 +54,9 @@ struct TrendingView: View { } .toolbar { + ToolbarItem { + RequestErrorButton(error: error) + } #if os(macOS) ToolbarItemGroup { if let favoriteItem { @@ -68,15 +73,14 @@ struct TrendingView: View { } .onChange(of: resource) { _ in resource.load() + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } updateFavoriteItem() } .onAppear { - if videos.isEmpty { - resource.addObserver(store) - resource.loadIfNeeded() - } else { - store.replace(videos) - } + resource.loadIfNeeded()? + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } updateFavoriteItem() } @@ -95,6 +99,8 @@ struct TrendingView: View { .background( Button("Refresh") { resource.load() + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } .keyboardShortcut("r") .opacity(0) @@ -111,6 +117,8 @@ struct TrendingView: View { .refreshable { DispatchQueue.main.async { resource.load() + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } } .navigationBarTitleDisplayMode(.inline) @@ -128,7 +136,9 @@ struct TrendingView: View { } #else .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in - resource.loadIfNeeded() + resource.loadIfNeeded()? + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } #endif } diff --git a/Shared/Views/PopularView.swift b/Shared/Views/PopularView.swift index eac1d1dc..4c6ae139 100644 --- a/Shared/Views/PopularView.swift +++ b/Shared/Views/PopularView.swift @@ -7,6 +7,8 @@ struct PopularView: View { @ObservedObject private var accounts = AccountsModel.shared + @State private var error: RequestError? + @Default(.popularListingStyle) private var popularListingStyle var resource: Resource? { @@ -21,7 +23,9 @@ struct PopularView: View { VerticalCells(items: videos) .onAppear { resource?.addObserver(store) - resource?.loadIfNeeded() + resource?.loadIfNeeded()? + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } .environment(\.listingStyle, popularListingStyle) #if !os(tvOS) @@ -29,6 +33,8 @@ struct PopularView: View { .background( Button("Refresh") { resource?.load() + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } .keyboardShortcut("r") .opacity(0) @@ -45,14 +51,15 @@ struct PopularView: View { resource?.load().onCompletion { _ in refreshControl.endRefreshing() } - .onFailure { error in - NavigationModel.shared.presentAlert(title: "Could not refresh Popular", message: error.userMessage) - } + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } .backport .refreshable { DispatchQueue.main.async { resource?.load() + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } } .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) @@ -65,7 +72,9 @@ struct PopularView: View { } #else .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in - resource?.loadIfNeeded() + resource?.loadIfNeeded()? + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } } #endif } diff --git a/Shared/Views/RequestErrorButton.swift b/Shared/Views/RequestErrorButton.swift new file mode 100644 index 00000000..d67d103f --- /dev/null +++ b/Shared/Views/RequestErrorButton.swift @@ -0,0 +1,23 @@ +import Siesta +import SwiftUI + +struct RequestErrorButton: View { + var error: RequestError? + + var body: some View { + if let error { + Button { + NavigationModel.shared.presentRequestErrorAlert(error) + } label: { + Label("Error", systemImage: "exclamationmark.circle.fill") + .foregroundColor(Color("AppRedColor")) + } + } + } +} + +struct RequestErrorButton_Previews: PreviewProvider { + static var previews: some View { + RequestErrorButton() + } +} diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 7a29d390..328d45da 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -614,6 +614,9 @@ 3784CDE227772EE40055BBF2 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784CDDE27772EE40055BBF2 /* Watch.swift */; }; 3784CDE327772EE40055BBF2 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784CDDE27772EE40055BBF2 /* Watch.swift */; }; 3784CDE427772EE40055BBF2 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784CDDE27772EE40055BBF2 /* Watch.swift */; }; + 3786D05E294C737300D23E82 /* RequestErrorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3786D05D294C737300D23E82 /* RequestErrorButton.swift */; }; + 3786D05F294C737300D23E82 /* RequestErrorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3786D05D294C737300D23E82 /* RequestErrorButton.swift */; }; + 3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3786D05D294C737300D23E82 /* RequestErrorButton.swift */; }; 3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; 3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; 3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; @@ -1317,6 +1320,7 @@ 3784B23A272894DA00B09468 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; 3784B23C2728B85300B09468 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = ""; }; 3784CDDE27772EE40055BBF2 /* Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = ""; }; + 3786D05D294C737300D23E82 /* RequestErrorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestErrorButton.swift; sourceTree = ""; }; 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.swift; sourceTree = ""; }; 378AE942274EF00A006A4EE1 /* Color+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Background.swift"; sourceTree = ""; }; 378E50FA26FE8B9F00F49626 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = ""; }; @@ -1818,6 +1822,7 @@ 371AAE2826CEC7D900901972 /* Views */ = { isa = PBXGroup; children = ( + 37635FE3291EA6CF00C11E79 /* AccentButton.swift */, 37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */, 37FB285D272225E800A57617 /* ContentItemView.swift */, 372CFD14285F2E2A00B0B54B /* ControlsBar.swift */, @@ -1827,18 +1832,18 @@ 371CC7732946963000979C1A /* ListingStyleButtons.swift */, 37030FF627B0347C00ECDDAA /* MPVPlayerView.swift */, 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */, - 37635FE3291EA6CF00C11E79 /* AccentButton.swift */, 3763C988290C7A50004D3B5F /* OpenVideosView.swift */, 37FEF11227EFD8580033912F /* PlaceholderCell.swift */, + 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, 37AAF27D26737323007FC770 /* PopularView.swift */, + 3786D05D294C737300D23E82 /* RequestErrorButton.swift */, 371CC76F29468BDC00979C1A /* SettingsButtons.swift */, 37F7D82B289EB05F00E2B3D0 /* SettingsPickerModifier.swift */, 3784B23C2728B85300B09468 /* ShareButton.swift */, 376B2E0626F920D600B1D64D /* SignInRequiredView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */, - 3710A55429488C7D006F8025 /* PlaceholderListItem.swift */, ); path = Views; sourceTree = ""; @@ -3059,6 +3064,7 @@ 3776ADD6287381240078EBC4 /* Captions.swift in Sources */, 37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */, + 3786D05E294C737300D23E82 /* RequestErrorButton.swift in Sources */, 37484C1926FC837400287258 /* PlayerSettings.swift in Sources */, 3711403F26B206A6005B3555 /* SearchModel.swift in Sources */, 3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */, @@ -3280,6 +3286,7 @@ 37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37737786276F9858000521C1 /* Windows.swift in Sources */, 37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */, + 3786D05F294C737300D23E82 /* RequestErrorButton.swift in Sources */, 3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */, 37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */, 37FD77012932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */, @@ -3667,6 +3674,7 @@ 37F5E8B8291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */, 37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */, 37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */, + 3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */, 37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */, 37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */,