Less obnoxious error handling

This commit is contained in:
Arkadiusz Fal 2022-12-16 09:35:10 +01:00
parent a9c8053474
commit 4330856c5e
10 changed files with 97 additions and 25 deletions

View File

@ -20,6 +20,8 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
@Published var isLoading = false @Published var isLoading = false
@Published var channels = [Channel]() @Published var channels = [Channel]()
@Published var error: RequestError?
var accounts: AccountsModel { .shared } var accounts: AccountsModel { .shared }
var resource: Resource? { var resource: Resource? {
@ -67,6 +69,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
self?.isLoading = false self?.isLoading = false
} }
.onSuccess { resource in .onSuccess { resource in
self.error = nil
if let channels: [Channel] = resource.typedContent() { if let channels: [Channel] = resource.typedContent() {
self.channels = channels self.channels = channels
channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) } channels.forEach { ChannelsCacheModel.shared.storeIfMissing($0) }
@ -75,9 +78,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
onSuccess() onSuccess()
} }
} }
.onFailure { _ in .onFailure { self.error = $0 }
self.channels = []
}
} }
} }

View File

@ -18,6 +18,8 @@ final class FeedModel: ObservableObject, CacheModel {
var storage: Storage<String, JSON>? var storage: Storage<String, JSON>?
@Published var error: RequestError?
private var backgroundContext = PersistenceController.shared.container.newBackgroundContext() private var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
var feed: Resource? { var feed: Resource? {
@ -79,6 +81,7 @@ final class FeedModel: ObservableObject, CacheModel {
onCompletion() onCompletion()
} }
.onSuccess { response in .onSuccess { response in
self.error = nil
if let videos: [Video] = response.typedContent() { if let videos: [Video] = response.typedContent() {
if paginating { if paginating {
self.videos.append(contentsOf: videos) self.videos.append(contentsOf: videos)
@ -89,9 +92,7 @@ final class FeedModel: ObservableObject, CacheModel {
} }
} }
} }
.onFailure { error in .onFailure { self.error = $0 }
NavigationModel.shared.presentAlert(title: "Could not refresh Subscriptions", message: error.userMessage)
}
} }
} }

View File

@ -1,4 +1,5 @@
import Foundation import Foundation
import Siesta
import SwiftUI import SwiftUI
final class NavigationModel: ObservableObject { final class NavigationModel: ObservableObject {
@ -257,6 +258,11 @@ final class NavigationModel: ObservableObject {
presentingAlert = true 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) { func presentAlert(_ alert: Alert) {
self.alert = alert self.alert = alert
presentingAlert = true presentingAlert = true

View File

@ -9,6 +9,7 @@ final class PlaylistsModel: ObservableObject {
@Published var isLoading = false @Published var isLoading = false
@Published var playlists = [Playlist]() @Published var playlists = [Playlist]()
@Published var reloadPlaylists = false @Published var reloadPlaylists = false
@Published var error: RequestError?
var accounts = AccountsModel.shared var accounts = AccountsModel.shared
@ -60,16 +61,14 @@ final class PlaylistsModel: ObservableObject {
self?.isLoading = false self?.isLoading = false
} }
.onSuccess { resource in .onSuccess { resource in
self.error = nil
if let playlists: [Playlist] = resource.typedContent() { if let playlists: [Playlist] = resource.typedContent() {
self.playlists = playlists self.playlists = playlists
PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists) PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists)
onSuccess() onSuccess()
} }
} }
.onFailure { error in .onFailure { self.error = $0 }
self.playlists = []
NavigationModel.shared.presentAlert(title: "Could not refresh Playlists", message: error.userMessage)
}
} }
} }

View File

@ -15,8 +15,9 @@ struct PlaylistsView: View {
@StateObject private var userPlaylist = Store<Playlist>() @StateObject private var userPlaylist = Store<Playlist>()
@ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var accounts = AccountsModel.shared
private var player = PlayerModel.shared
@ObservedObject private var model = PlaylistsModel.shared @ObservedObject private var model = PlaylistsModel.shared
private var player = PlayerModel.shared
private var cache = PlaylistsCacheModel.shared private var cache = PlaylistsCacheModel.shared
@Namespace private var focusNamespace @Namespace private var focusNamespace
@ -129,6 +130,10 @@ struct PlaylistsView: View {
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem {
RequestErrorButton(error: model.error)
}
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
playlistsMenu playlistsMenu
} }

View File

@ -1,4 +1,5 @@
import Defaults import Defaults
import Siesta
import SwiftUI import SwiftUI
struct SubscriptionsView: View { struct SubscriptionsView: View {
@ -11,6 +12,7 @@ struct SubscriptionsView: View {
@Default(.subscriptionsListingStyle) private var subscriptionsListingStyle @Default(.subscriptionsListingStyle) private var subscriptionsListingStyle
@ObservedObject private var feed = FeedModel.shared @ObservedObject private var feed = FeedModel.shared
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
var body: some View { var body: some View {
SignInRequiredView(title: "Subscriptions".localized()) { SignInRequiredView(title: "Subscriptions".localized()) {
@ -32,6 +34,10 @@ struct SubscriptionsView: View {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
subscriptionsMenu subscriptionsMenu
} }
ToolbarItem {
RequestErrorButton(error: requestError)
}
} }
#endif #endif
#if os(macOS) #if os(macOS)
@ -51,6 +57,10 @@ struct SubscriptionsView: View {
#endif #endif
} }
var requestError: RequestError? {
subscriptionsViewPage == .channels ? subscriptions.error : feed.error
}
#if os(iOS) #if os(iOS)
var subscriptionsMenu: some View { var subscriptionsMenu: some View {
Menu { Menu {

View File

@ -21,6 +21,8 @@ struct TrendingView: View {
ContentItem.array(of: store.collection) ContentItem.array(of: store.collection)
} }
@State private var error: RequestError?
init(_ videos: [Video] = [Video]()) { init(_ videos: [Video] = [Video]()) {
self.videos = videos self.videos = videos
} }
@ -52,6 +54,9 @@ struct TrendingView: View {
} }
.toolbar { .toolbar {
ToolbarItem {
RequestErrorButton(error: error)
}
#if os(macOS) #if os(macOS)
ToolbarItemGroup { ToolbarItemGroup {
if let favoriteItem { if let favoriteItem {
@ -68,15 +73,14 @@ struct TrendingView: View {
} }
.onChange(of: resource) { _ in .onChange(of: resource) { _ in
resource.load() resource.load()
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
updateFavoriteItem() updateFavoriteItem()
} }
.onAppear { .onAppear {
if videos.isEmpty { resource.loadIfNeeded()?
resource.addObserver(store) .onFailure { self.error = $0 }
resource.loadIfNeeded() .onSuccess { _ in self.error = nil }
} else {
store.replace(videos)
}
updateFavoriteItem() updateFavoriteItem()
} }
@ -95,6 +99,8 @@ struct TrendingView: View {
.background( .background(
Button("Refresh") { Button("Refresh") {
resource.load() resource.load()
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
.keyboardShortcut("r") .keyboardShortcut("r")
.opacity(0) .opacity(0)
@ -111,6 +117,8 @@ struct TrendingView: View {
.refreshable { .refreshable {
DispatchQueue.main.async { DispatchQueue.main.async {
resource.load() resource.load()
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@ -128,7 +136,9 @@ struct TrendingView: View {
} }
#else #else
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
resource.loadIfNeeded() resource.loadIfNeeded()?
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
#endif #endif
} }

View File

@ -7,6 +7,8 @@ struct PopularView: View {
@ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var accounts = AccountsModel.shared
@State private var error: RequestError?
@Default(.popularListingStyle) private var popularListingStyle @Default(.popularListingStyle) private var popularListingStyle
var resource: Resource? { var resource: Resource? {
@ -21,7 +23,9 @@ struct PopularView: View {
VerticalCells(items: videos) VerticalCells(items: videos)
.onAppear { .onAppear {
resource?.addObserver(store) resource?.addObserver(store)
resource?.loadIfNeeded() resource?.loadIfNeeded()?
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
.environment(\.listingStyle, popularListingStyle) .environment(\.listingStyle, popularListingStyle)
#if !os(tvOS) #if !os(tvOS)
@ -29,6 +33,8 @@ struct PopularView: View {
.background( .background(
Button("Refresh") { Button("Refresh") {
resource?.load() resource?.load()
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
.keyboardShortcut("r") .keyboardShortcut("r")
.opacity(0) .opacity(0)
@ -45,14 +51,15 @@ struct PopularView: View {
resource?.load().onCompletion { _ in resource?.load().onCompletion { _ in
refreshControl.endRefreshing() refreshControl.endRefreshing()
} }
.onFailure { error in .onFailure { self.error = $0 }
NavigationModel.shared.presentAlert(title: "Could not refresh Popular", message: error.userMessage) .onSuccess { _ in self.error = nil }
}
} }
.backport .backport
.refreshable { .refreshable {
DispatchQueue.main.async { DispatchQueue.main.async {
resource?.load() resource?.load()
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
} }
.navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode)
@ -65,7 +72,9 @@ struct PopularView: View {
} }
#else #else
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
resource?.loadIfNeeded() resource?.loadIfNeeded()?
.onFailure { self.error = $0 }
.onSuccess { _ in self.error = nil }
} }
#endif #endif
} }

View File

@ -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()
}
}

View File

@ -614,6 +614,9 @@
3784CDE227772EE40055BBF2 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784CDDE27772EE40055BBF2 /* Watch.swift */; }; 3784CDE227772EE40055BBF2 /* Watch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784CDDE27772EE40055BBF2 /* Watch.swift */; };
3784CDE327772EE40055BBF2 /* 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 */; }; 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 */; }; 3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; };
3788AC2826F6840700F6BAA9 /* 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 */; }; 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 = "<group>"; }; 3784B23A272894DA00B09468 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
3784B23C2728B85300B09468 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; }; 3784B23C2728B85300B09468 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; };
3784CDDE27772EE40055BBF2 /* Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = "<group>"; }; 3784CDDE27772EE40055BBF2 /* Watch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Watch.swift; sourceTree = "<group>"; };
3786D05D294C737300D23E82 /* RequestErrorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestErrorButton.swift; sourceTree = "<group>"; };
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.swift; sourceTree = "<group>"; }; 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.swift; sourceTree = "<group>"; };
378AE942274EF00A006A4EE1 /* Color+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Background.swift"; sourceTree = "<group>"; }; 378AE942274EF00A006A4EE1 /* Color+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Background.swift"; sourceTree = "<group>"; };
378E50FA26FE8B9F00F49626 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; }; 378E50FA26FE8B9F00F49626 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
@ -1818,6 +1822,7 @@
371AAE2826CEC7D900901972 /* Views */ = { 371AAE2826CEC7D900901972 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37635FE3291EA6CF00C11E79 /* AccentButton.swift */,
37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */, 37E6D79F2944CD3800550C3D /* CacheStatusHeader.swift */,
37FB285D272225E800A57617 /* ContentItemView.swift */, 37FB285D272225E800A57617 /* ContentItemView.swift */,
372CFD14285F2E2A00B0B54B /* ControlsBar.swift */, 372CFD14285F2E2A00B0B54B /* ControlsBar.swift */,
@ -1827,18 +1832,18 @@
371CC7732946963000979C1A /* ListingStyleButtons.swift */, 371CC7732946963000979C1A /* ListingStyleButtons.swift */,
37030FF627B0347C00ECDDAA /* MPVPlayerView.swift */, 37030FF627B0347C00ECDDAA /* MPVPlayerView.swift */,
37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */, 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */,
37635FE3291EA6CF00C11E79 /* AccentButton.swift */,
3763C988290C7A50004D3B5F /* OpenVideosView.swift */, 3763C988290C7A50004D3B5F /* OpenVideosView.swift */,
37FEF11227EFD8580033912F /* PlaceholderCell.swift */, 37FEF11227EFD8580033912F /* PlaceholderCell.swift */,
3710A55429488C7D006F8025 /* PlaceholderListItem.swift */,
3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */, 3769C02D2779F18600DDB3EA /* PlaceholderProgressView.swift */,
37AAF27D26737323007FC770 /* PopularView.swift */, 37AAF27D26737323007FC770 /* PopularView.swift */,
3786D05D294C737300D23E82 /* RequestErrorButton.swift */,
371CC76F29468BDC00979C1A /* SettingsButtons.swift */, 371CC76F29468BDC00979C1A /* SettingsButtons.swift */,
37F7D82B289EB05F00E2B3D0 /* SettingsPickerModifier.swift */, 37F7D82B289EB05F00E2B3D0 /* SettingsPickerModifier.swift */,
3784B23C2728B85300B09468 /* ShareButton.swift */, 3784B23C2728B85300B09468 /* ShareButton.swift */,
376B2E0626F920D600B1D64D /* SignInRequiredView.swift */, 376B2E0626F920D600B1D64D /* SignInRequiredView.swift */,
37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */,
37E70922271CD43000D34DDE /* WelcomeScreen.swift */, 37E70922271CD43000D34DDE /* WelcomeScreen.swift */,
3710A55429488C7D006F8025 /* PlaceholderListItem.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3059,6 +3064,7 @@
3776ADD6287381240078EBC4 /* Captions.swift in Sources */, 3776ADD6287381240078EBC4 /* Captions.swift in Sources */,
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */, 37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37C194C726F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
3786D05E294C737300D23E82 /* RequestErrorButton.swift in Sources */,
37484C1926FC837400287258 /* PlayerSettings.swift in Sources */, 37484C1926FC837400287258 /* PlayerSettings.swift in Sources */,
3711403F26B206A6005B3555 /* SearchModel.swift in Sources */, 3711403F26B206A6005B3555 /* SearchModel.swift in Sources */,
3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */, 3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */,
@ -3280,6 +3286,7 @@
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
37737786276F9858000521C1 /* Windows.swift in Sources */, 37737786276F9858000521C1 /* Windows.swift in Sources */,
37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */, 37BE0BDC26A2367F0092E2DB /* AppleAVPlayerView.swift in Sources */,
3786D05F294C737300D23E82 /* RequestErrorButton.swift in Sources */,
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */, 3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */, 37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */,
37FD77012932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */, 37FD77012932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */,
@ -3667,6 +3674,7 @@
37F5E8B8291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */, 37F5E8B8291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */,
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */, 37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */,
37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */, 37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */,
3786D060294C737300D23E82 /* RequestErrorButton.swift in Sources */,
37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */, 37BDFF1929487B99000C6404 /* PlaylistVideosView.swift in Sources */,
37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */, 374924DC2921050B0017D862 /* LocationsSettings.swift in Sources */,