From 24c2be02bb6a9ef395b1366a879e3e3d70b34c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Tue, 3 Oct 2023 00:39:16 +0000 Subject: [PATCH] ui: Improve UX around clearing cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing ------- PASS Device: iPhone 14 Pro simulator iOS: Tested on iOS 17.0 and 16.4 Steps: 1. Go to appearance settings 2. Enable animations. Shows confirmation dialog. PASS 3. Click cancel. Setting is toggled back. PASS 4. Enable animations again. This time click "OK". Setting stays at what was set, and cache is visibly cleared. PASS 5. Restart app. Changes are persistent. PASS 6. Disable animations. Dialog appears like before. PASS 7. Cancel. Toggles back as expected. PASS 8. Disable animations again. This time click "OK". Cache is cleared. PASS 7. Restart app. Changes are persistent. PASS 9. Click on "clear cache". Confirmation dialog appears. PASS 10. Cancel action. We do not see cache being cleared. PASS 11. Click on "clear cache" and click "OK" this time. 12. We can see the cache being visibly cleared. It shows a loading spinner and "clearing cache", and then we see a checkmark icon with a "cache cleared" indicator. We cannot click the button again for now. PASS 13. Go to home view, scroll through some views, then come back to the setting. Clear cache button is visible again. Closes: https://github.com/damus-io/damus/issues/1301 Changelog-Changed: Improve UX around clearing cache Signed-off-by: Daniel D’Aquino Signed-off-by: William Casarin --- damus/Views/ConfigView.swift | 20 +++- .../Settings/AppearanceSettingsView.swift | 101 ++++++++++++++++-- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift index 8ed45dd43..4a51208ae 100644 --- a/damus/Views/ConfigView.swift +++ b/damus/Views/ConfigView.swift @@ -175,8 +175,22 @@ func handle_string_amount(new_value: String) -> Int? { return amt } -func clear_kingfisher_cache() -> Void { +func clear_kingfisher_cache(completion: (() -> Void)? = nil) { KingfisherManager.shared.cache.clearMemoryCache() - KingfisherManager.shared.cache.clearDiskCache() - KingfisherManager.shared.cache.cleanExpiredDiskCache() + + let group = DispatchGroup() + + group.enter() + KingfisherManager.shared.cache.clearDiskCache { + group.leave() + } + + group.enter() + KingfisherManager.shared.cache.cleanExpiredDiskCache { + group.leave() + } + + group.notify(queue: .main) { + completion?() + } } diff --git a/damus/Views/Settings/AppearanceSettingsView.swift b/damus/Views/Settings/AppearanceSettingsView.swift index 70f073b27..593176328 100644 --- a/damus/Views/Settings/AppearanceSettingsView.swift +++ b/damus/Views/Settings/AppearanceSettingsView.swift @@ -7,6 +7,15 @@ import SwiftUI +fileprivate let CACHE_CLEAR_BUTTON_RESET_TIME_IN_SECONDS: Double = 60 +fileprivate let MINIMUM_CACHE_CLEAR_BUTTON_DELAY_IN_SECONDS: Double = 1 + +/// A simple type to keep track of the cache clearing state +fileprivate enum CacheClearingState { + case not_cleared + case clearing + case cleared +} struct ResizedEventPreview: View { let damus_state: DamusState @@ -21,6 +30,11 @@ struct AppearanceSettingsView: View { let damus_state: DamusState @ObservedObject var settings: UserSettingsStore @Environment(\.dismiss) var dismiss + @State fileprivate var cache_clearing_state: CacheClearingState = .not_cleared + @State var showing_cache_clear_alert: Bool = false + + @State var showing_enable_animation_alert: Bool = false + @State var enable_animation_toggle_is_user_initiated: Bool = true var FontSize: some View { VStack(alignment: .leading) { @@ -63,11 +77,7 @@ struct AppearanceSettingsView: View { // MARK: - Images Section(NSLocalizedString("Images", comment: "Section title for images configuration.")) { - Toggle(NSLocalizedString("Animations", comment: "Toggle to enable or disable image animation"), isOn: $settings.enable_animation) - .toggleStyle(.switch) - .onChange(of: settings.enable_animation) { _ in - clear_kingfisher_cache() - } + self.EnableAnimationsToggle Toggle(NSLocalizedString("Always show images", comment: "Setting to always show and never blur images"), isOn: $settings.always_show_images) .toggleStyle(.switch) @@ -79,9 +89,7 @@ struct AppearanceSettingsView: View { } } - Button(NSLocalizedString("Clear Cache", comment: "Button to clear image cache.")) { - clear_kingfisher_cache() - } + self.ClearCacheButton } // MARK: - Content filters and moderation @@ -100,6 +108,83 @@ struct AppearanceSettingsView: View { dismiss() } } + + func clear_cache_button_action() { + cache_clearing_state = .clearing + + let group = DispatchGroup() + + group.enter() + clear_kingfisher_cache(completion: { + group.leave() + }) + + // Make clear cache button take at least a second or so to avoid issues with labor perception bias (https://growth.design/case-studies/labor-perception-bias) + group.enter() + DispatchQueue.main.asyncAfter(deadline: .now() + MINIMUM_CACHE_CLEAR_BUTTON_DELAY_IN_SECONDS) { + group.leave() + } + + group.notify(queue: .main) { + cache_clearing_state = .cleared + DispatchQueue.main.asyncAfter(deadline: .now() + CACHE_CLEAR_BUTTON_RESET_TIME_IN_SECONDS) { + cache_clearing_state = .not_cleared + } + } + } + + var EnableAnimationsToggle: some View { + Toggle(NSLocalizedString("Animations", comment: "Toggle to enable or disable image animation"), isOn: $settings.enable_animation) + .toggleStyle(.switch) + .onChange(of: settings.enable_animation) { _ in + if self.enable_animation_toggle_is_user_initiated { + self.showing_enable_animation_alert = true + } + else { + self.enable_animation_toggle_is_user_initiated = true + } + } + .alert(isPresented: $showing_enable_animation_alert) { + Alert(title: Text(NSLocalizedString("Confirmation", comment: "Confirmation dialog title")), + message: Text(NSLocalizedString("Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?", comment: "Message explaining consequences of changing the 'enable animation' setting")), + primaryButton: .default(Text(NSLocalizedString("OK", comment: "Button label indicating user wants to proceed."))) { + self.clear_cache_button_action() + }, + secondaryButton: .cancel() { + // Toggle back if user cancels action + self.enable_animation_toggle_is_user_initiated = false + settings.enable_animation.toggle() + } + ) + } + } + + var ClearCacheButton: some View { + Button(action: { self.showing_cache_clear_alert = true }, label: { + HStack(spacing: 6) { + switch cache_clearing_state { + case .not_cleared: + Text(NSLocalizedString("Clear Cache", comment: "Button to clear image cache.")) + case .clearing: + ProgressView() + Text(NSLocalizedString("Clearing Cache", comment: "Loading message indicating that the cache is being cleared.")) + case .cleared: + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + Text(NSLocalizedString("Cache has been cleared", comment: "Message indicating that the cache was successfully cleared.")) + } + } + }) + .disabled(self.cache_clearing_state != .not_cleared) + .alert(isPresented: $showing_cache_clear_alert) { + Alert(title: Text(NSLocalizedString("Confirmation", comment: "Confirmation dialog title")), + message: Text(NSLocalizedString("Are you sure you want to clear the cache? This will free space, but images may take longer to load again.", comment: "Message explaining what it means to clear the cache, asking if user wants to proceed.")), + primaryButton: .default(Text(NSLocalizedString("OK", comment: "Button label indicating user wants to proceed."))) { + self.clear_cache_button_action() + }, + secondaryButton: .cancel()) + } + } }