Handles single and multiple sync failures

If a single sync failure is encountered a sheet is presented which allows the user to update their credentials.

If multiple sync failures are encountered an alert is shown listing the accounts which encountered errors. On iOS, this alert can take the user into Settings, but there is no obvious way to programatically pesent macOS preferences.
This commit is contained in:
Stuart Breckenridge 2020-07-25 16:40:04 +08:00
parent 75b9264d44
commit 673f0ce718
No known key found for this signature in database
GPG Key ID: 79BD673276AE83CE
12 changed files with 328 additions and 45 deletions

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
1748B4A724CADA17008F9850 /* AccountSyncError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1748B4A624CADA17008F9850 /* AccountSyncError.swift */; };
179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB7399814F6FB3247825C /* NewsBlurStory.swift */; };
179DB0B17A6C51B95ABC1741 /* NewsBlurStoryStatusChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB5B421C5433B45C5F13E /* NewsBlurStoryStatusChange.swift */; };
179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */; };
@ -267,6 +268,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
1748B4A624CADA17008F9850 /* AccountSyncError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSyncError.swift; sourceTree = "<group>"; };
179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurLoginResponse.swift; sourceTree = "<group>"; };
179DB1B909672E0E807B5E8C /* NewsBlurFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurFeed.swift; sourceTree = "<group>"; };
179DB3CBADAFCF5377DA3D02 /* NewsBlurFeedChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurFeedChange.swift; sourceTree = "<group>"; };
@ -774,6 +776,7 @@
51BB7B83233531BC008E8144 /* AccountBehaviors.swift */,
51E3EB40229AF61B00645299 /* AccountError.swift */,
846E77531F6F00E300A165E2 /* AccountManager.swift */,
1748B4A624CADA17008F9850 /* AccountSyncError.swift */,
5170743B232AEDB500A461A3 /* OPMLFile.swift */,
519E84A52433D49000D238B0 /* OPMLNormalizer.swift */,
84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */,
@ -1183,6 +1186,7 @@
9E7299D9235062A200DAEFB7 /* FeedlyResourceProviding.swift in Sources */,
51F6C591245DB302001E41CA /* CloudKitReceiveStatusOperation.swift in Sources */,
9E672394236F7CA0000BE141 /* FeedlyRefreshAccessTokenOperation.swift in Sources */,
1748B4A724CADA17008F9850 /* AccountSyncError.swift in Sources */,
514BF5202391B0DB00902FE8 /* SingleArticleFetcher.swift in Sources */,
9EC688EC232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift in Sources */,
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,

View File

@ -247,6 +247,32 @@ public final class AccountManager: UnreadCountProvider {
completion?()
}
}
public func refreshAll(completion: (() -> Void)? = nil) {
var syncErrors = [AccountSyncError]()
let group = DispatchGroup()
activeAccounts.forEach { account in
group.enter()
account.refreshAll() { result in
group.leave()
switch result {
case .success:
break
case .failure(let error):
syncErrors.append(AccountSyncError(account: account, error: error))
}
}
}
group.notify(queue: DispatchQueue.main) {
if syncErrors.count > 0 {
NotificationCenter.default.post(Notification(name: .AccountsDidFailToSyncWithErrors, object: syncErrors, userInfo: nil))
}
completion?()
}
}
public func syncArticleStatusAll(completion: (() -> Void)? = nil) {
let group = DispatchGroup()

View File

@ -0,0 +1,29 @@
//
// AccountSyncError.swift
// Account
//
// Created by Stuart Breckenridge on 24/7/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import os.log
public extension Notification.Name {
static let AccountsDidFailToSyncWithErrors = Notification.Name("AccountsDidFailToSyncWithErrors")
}
public struct AccountSyncError {
private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
public let account: Account
public let error: Error
init(account: Account, error: Error) {
self.account = account
self.error = error
os_log(.error, log: AccountSyncError.log, "%@", error.localizedDescription)
}
}

View File

@ -0,0 +1,167 @@
//
// FixAccountCredentialView.swift
// NetNewsWire
//
// Created by Stuart Breckenridge on 24/7/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
import Account
struct FixAccountCredentialView: View {
let accountSyncError: AccountSyncError
@Environment(\.presentationMode) var presentationMode
@StateObject private var editModel = EditAccountCredentialsModel()
var body: some View {
#if os(macOS)
MacForm
.onAppear {
editModel.retrieveCredentials(accountSyncError.account)
}
.onChange(of: editModel.accountCredentialsWereUpdated) { value in
if value == true {
presentationMode.wrappedValue.dismiss()
}
}
.alert(isPresented: $editModel.showError) {
Alert(title: Text("Error Adding Account"),
message: Text(editModel.error.description),
dismissButton: .default(Text("Dismiss"),
action: {
editModel.error = .none
}))
}
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
.padding()
#else
iOSForm
.onAppear {
editModel.retrieveCredentials(accountSyncError.account)
}
.onChange(of: editModel.accountCredentialsWereUpdated) { value in
if value == true {
presentationMode.wrappedValue.dismiss()
}
}
.alert(isPresented: $editModel.showError) {
Alert(title: Text("Error Adding Account"),
message: Text(editModel.error.description),
dismissButton: .default(Text("Dismiss"),
action: {
editModel.error = .none
}))
}
#endif
}
var MacForm: some View {
Form {
header
HStack(alignment: .center) {
VStack(alignment: .trailing, spacing: 12) {
Text("Username: ")
Text("Password: ")
if accountSyncError.account.type == .freshRSS {
Text("API URL: ")
}
}.frame(width: 75)
VStack(alignment: .leading, spacing: 12) {
accountFields
}
}
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
HStack{
if editModel.accountIsUpdatingCredentials {
ProgressView("Updating")
}
Spacer()
cancelButton
updateButton
}
}.frame(height: 220)
}
#if os(iOS)
var iOSForm: some View {
NavigationView {
List {
Section(header: header, content: {
accountFields
})
}
.listStyle(InsetGroupedListStyle())
.navigationBarItems(
leading:
cancelButton
, trailing:
HStack {
if editModel.accountIsUpdatingCredentials {
ProgressView()
.frame(width: 20 , height: 20)
.padding(.horizontal, 4)
}
updateButton
}
)
}
}
#endif
var header: some View {
HStack {
Spacer()
VStack {
Image(rsImage: accountSyncError.account.smallIcon!.image)
.resizable()
.frame(width: 30, height: 30)
Text(accountSyncError.account.nameForDisplay)
Text(accountSyncError.error.localizedDescription)
.multilineTextAlignment(.center)
.lineLimit(3)
.padding(.top, 4)
}
Spacer()
}.padding()
}
@ViewBuilder
var accountFields: some View {
TextField("Username", text: $editModel.userName)
SecureField("Password", text: $editModel.password)
if accountSyncError.account.type == .freshRSS {
TextField("API URL", text: $editModel.apiUrl)
}
}
@ViewBuilder
var updateButton: some View {
if accountSyncError.account.type != .freshRSS {
Button("Update", action: {
editModel.updateAccountCredentials(accountSyncError.account)
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0)
} else {
Button("Update", action: {
editModel.updateAccountCredentials(accountSyncError.account)
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0 || editModel.apiUrl.count == 0)
}
}
var cancelButton: some View {
Button("Cancel", action: {
presentationMode.wrappedValue.dismiss()
})
}
}

View File

@ -21,8 +21,8 @@ final class SceneModel: ObservableObject {
@Published var extractorButtonState: ArticleExtractorButtonState?
@Published var openInBrowserButtonState: Bool?
@Published var shareButtonState: Bool?
@Published var accountErrorMessage = ""
@Published var accountSyncErrors: [AccountSyncError] = []
var selectedArticles: [Article] {
timelineModel.selectedArticles
@ -48,6 +48,7 @@ final class SceneModel: ObservableObject {
self.articleIconSchemeHandler = ArticleIconSchemeHandler(sceneModel: self)
self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!)
subscribeToAccountSyncErrors()
subscribeToToolbarChangeEvents()
}
@ -146,6 +147,16 @@ private extension SceneModel {
}.store(in: &cancellables)
}
func subscribeToAccountSyncErrors() {
NotificationCenter.default.publisher(for: .AccountsDidFailToSyncWithErrors)
.sink { [weak self] notification in
guard let errors = notification.object as? [AccountSyncError] else {
return
}
self?.accountSyncErrors = errors
}.store(in: &cancellables)
}
// MARK: Button State Updates
func updateNextUnreadButtonState(accountManager: AccountManager) {

View File

@ -14,8 +14,8 @@ struct SceneNavigationView: View {
@StateObject private var sceneModel = SceneModel()
@State private var showSheet = false
@State private var showShareSheet = false
@State private var showRefreshError = false
@State private var sheetToShow: ToolbarSheets = .none
@State private var sheetToShow: SidebarSheets = .none
@State private var showAccountSyncErrorAlert = false // multiple sync errors
#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@ -44,31 +44,58 @@ struct SceneNavigationView: View {
.onAppear {
sceneModel.startup()
}
.sheet(isPresented: $showSheet, onDismiss: { sheetToShow = .none }) {
if sheetToShow == .web {
AddWebFeedView()
}
if sheetToShow == .folder {
AddFolderView()
}
}
.onChange(of: sheetToShow) { value in
value != .none ? (showSheet = true) : (showSheet = false)
}
.onChange(of: showRefreshError) { value in
if !value {
sceneModel.accountErrorMessage = ""
.onReceive(sceneModel.$accountSyncErrors) { errors in
if errors.count == 0 {
showAccountSyncErrorAlert = false
} else {
if errors.count > 1 {
showAccountSyncErrorAlert = true
} else {
sheetToShow = .fixCredentials
}
}
}
.onReceive(sceneModel.$accountErrorMessage) { message in
if !message.isEmpty {
showRefreshError = true
}
}
.alert(isPresented: $showRefreshError) {
Alert(title: Text("Account Error"), message: Text(verbatim: sceneModel.accountErrorMessage), dismissButton: .default(Text("OK")))
.sheet(isPresented: $showSheet,
onDismiss: {
sheetToShow = .none
sceneModel.accountSyncErrors = []
}) {
if sheetToShow == .web {
AddWebFeedView()
}
if sheetToShow == .folder {
AddFolderView()
}
#if os(iOS)
if sheetToShow == .settings {
SettingsView()
}
#endif
if sheetToShow == .fixCredentials {
FixAccountCredentialView(accountSyncError: sceneModel.accountSyncErrors[0])
}
}
.alert(isPresented: $showAccountSyncErrorAlert, content: {
#if os(macOS)
return Alert(title: Text("Account Sync Error"),
message: Text("The following accounts failed to sync: ") + Text(sceneModel.accountSyncErrors.map({ $0.account.nameForDisplay }).joined(separator: ", ")) + Text(". You can update credentials in Preferences"),
dismissButton: .default(Text("Dismiss")))
#else
return Alert(title: Text("Account Sync Error"),
message: Text("The following accounts failed to sync: ") + Text(sceneModel.accountSyncErrors.map({ $0.account.nameForDisplay }).joined(separator: ", ")) + Text(". You can update credentials in Settings"),
primaryButton: .default(Text("Show Settings"), action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
sheetToShow = .settings
})
}),
secondaryButton: .cancel(Text("Dismiss")))
#endif
})
.toolbar {
#if os(macOS)
@ -84,7 +111,10 @@ struct SceneNavigationView: View {
}
ToolbarItem {
Button {
AccountManager.shared.refreshAll(errorHandler: handleRefreshError)
// AccountManager.shared.refreshAll(errorHandler: handleRefreshError)
AccountManager.shared.refreshAll(completion: nil)
} label: {
AppAssets.refreshImage
}

View File

@ -8,14 +8,14 @@
import Foundation
enum ToolbarSheets {
case none, web, twitter, reddit, folder, settings
enum SidebarSheets {
case none, web, twitter, reddit, folder, settings, fixCredentials
}
class SidebarToolbarModel: ObservableObject {
@Published var showSheet: Bool = false
@Published var sheetToShow: ToolbarSheets = .none {
@Published var sheetToShow: SidebarSheets = .none {
didSet {
sheetToShow != .none ? (showSheet = true) : (showSheet = false)
}

View File

@ -26,6 +26,7 @@ struct SidebarView: View {
@State private var scrollOffset: CGFloat = 0
@State var pulling: Bool = false
@State var refreshing: Bool = false
@ViewBuilder var body: some View {
#if os(macOS)
@ -110,7 +111,7 @@ struct SidebarView: View {
// Crossing the threshold on the way down, we start the refresh process
if !pulling && (scrollOffset > threshold && previousScrollOffset <= threshold) {
pulling = true
AccountManager.shared.refreshAll(errorHandler: handleRefreshError)
AccountManager.shared.refreshAll()
}
// Crossing the threshold on the way UP, we end the refresh
@ -123,10 +124,6 @@ struct SidebarView: View {
}
}
func handleRefreshError(_ error: Error) {
sceneModel.accountErrorMessage = error.localizedDescription
}
struct RefreshFixedView: View {
var body: some View {
GeometryReader { proxy in

View File

@ -162,7 +162,9 @@ extension EditAccountCredentialsModel {
accountIsUpdatingCredentials = true
let updateAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
updateAccount.delegate = self
#if os(macOS)
updateAccount.presentationAnchor = NSApplication.shared.windows.last
#endif
MainThreadOperationQueue.shared.add(updateAccount)
}

View File

@ -83,7 +83,6 @@ struct EditAccountCredentialsView: View {
}
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
.padding()
}
}

View File

@ -8,6 +8,10 @@
/* Begin PBXBuildFile section */
1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */; };
171BCB8C24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; };
171BCB8D24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */; };
171BCBAF24CBBFD8006E22D9 /* EditAccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */; };
171BCBB024CBBFFD006E22D9 /* AccountUpdateErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */; };
172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; };
172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; };
172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; };
@ -1817,6 +1821,7 @@
/* Begin PBXFileReference section */
1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = "<group>"; };
171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixAccountCredentialView.swift; sourceTree = "<group>"; };
172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModifier.swift; sourceTree = "<group>"; };
@ -2555,6 +2560,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
171BCBB124CBD569006E22D9 /* Account Management */ = {
isa = PBXGroup;
children = (
171BCB8B24CB08A3006E22D9 /* FixAccountCredentialView.swift */,
);
path = "Account Management ";
sourceTree = "<group>";
};
172199EB24AB228E00A31D04 /* Settings */ = {
isa = PBXGroup;
children = (
@ -3106,6 +3119,7 @@
51E499FB24A9135A00B667CB /* Sidebar */,
514E6C0424AD2B0400AC6F6E /* SwiftUI Extensions */,
51919FCB24AB855000541E64 /* Timeline */,
171BCBB124CBD569006E22D9 /* Account Management */,
);
path = Shared;
sourceTree = "<group>";
@ -4317,46 +4331,46 @@
TargetAttributes = {
51314636235A7BBE00387FDC = {
CreatedOnToolsVersion = 11.2;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
LastSwiftMigration = 1120;
ProvisioningStyle = Automatic;
};
513C5CE5232571C2003D4054 = {
CreatedOnToolsVersion = 11.0;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
518B2ED12351B3DD00400001 = {
CreatedOnToolsVersion = 11.2;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
TestTargetID = 840D617B2029031C009BC708;
};
51C0513C24A77DF800194D5E = {
CreatedOnToolsVersion = 12.0;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
51C0514324A77DF800194D5E = {
CreatedOnToolsVersion = 12.0;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
6581C73220CED60000F4AD34 = {
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
65ED3FA2235DEF6C0081F399 = {
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
65ED4090235DEF770081F399 = {
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
};
840D617B2029031C009BC708 = {
CreatedOnToolsVersion = 9.3;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
@ -4366,7 +4380,7 @@
};
849C645F1ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.HardenedRuntime = {
@ -4376,7 +4390,7 @@
};
849C64701ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = SHJK2V3AJG;
DevelopmentTeam = FQLBNX3GP7;
ProvisioningStyle = Automatic;
TestTargetID = 849C645F1ED37A5D003D8FC0;
};
@ -5185,8 +5199,10 @@
51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */,
51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */,
51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */,
171BCB8C24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */,
51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */,
51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */,
171BCBAF24CBBFD8006E22D9 /* EditAccountCredentialsModel.swift in Sources */,
51B80EDB24BD225200C6C32D /* OpenInSafariActivity.swift in Sources */,
172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */,
65CBAD5A24AE03C20006DD91 /* ColorPaletteContainerView.swift in Sources */,
@ -5263,6 +5279,7 @@
6591727F24B5D19500B638E8 /* SettingsDetailAccountView.swift in Sources */,
51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */,
65ACE48824B48020003AE06A /* SettingsLocalAccountView.swift in Sources */,
171BCBB024CBBFFD006E22D9 /* AccountUpdateErrors.swift in Sources */,
17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */,
51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */,
51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */,
@ -5368,6 +5385,7 @@
1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */,
51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */,
1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */,
171BCB8D24CB08A3006E22D9 /* FixAccountCredentialView.swift in Sources */,
1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */,
51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */,
51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */,

View File

@ -73,8 +73,8 @@ class AccountRefreshTimer {
lastTimedRefresh = Date()
update()
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
//AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
AccountManager.shared.refreshAll(completion: nil)
}
}