Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow automated fetching of synced bookmarks' favicons #2163

Merged
merged 83 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
1a82668
[WIP] Support for form factor specific favorites
ayoy Sep 8, 2023
3522cc7
Add favorites display mode switcher to settings
ayoy Sep 10, 2023
8cd0f21
Update BSK ref
ayoy Sep 10, 2023
997e861
Reload Home view collection view when favorites display mode changes
ayoy Sep 10, 2023
bc9d688
Apply most recent designs to favorites display mode settings screen
ayoy Sep 11, 2023
e00562c
Implement migration to form-factor-specific favorites
ayoy Sep 11, 2023
0ee52ce
Reload widgets on update to favorites display mode
ayoy Sep 11, 2023
a3420f6
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 14, 2023
698d23a
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 14, 2023
557cf9a
Add Sync options to Sync view
ayoy Sep 14, 2023
8586145
Revert changes to Settings.storyboard
ayoy Sep 14, 2023
754fe12
Revert changes to SettingsViewController
ayoy Sep 14, 2023
a1f1f33
Add FavoritesDisplayModeSyncHandler
ayoy Sep 15, 2023
c6ee017
Update BSK ref
ayoy Sep 15, 2023
52d6d69
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 18, 2023
30815c8
Update BSK ref
ayoy Sep 18, 2023
21be09d
Fix unit tests to compile and pass
ayoy Sep 19, 2023
d9288ca
Update BSK ref
ayoy Sep 20, 2023
baafc69
Implement handling favorites after disabling Sync
ayoy Sep 20, 2023
2f55c18
Make app responsive to favorites display mode changes
ayoy Sep 20, 2023
8f58d3b
Properly share favorites display mode setting with the Widget
ayoy Sep 20, 2023
a701352
Update BSK ref
ayoy Sep 21, 2023
0da5324
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 21, 2023
9297e19
Update BSK ref
ayoy Sep 21, 2023
f5fafe4
Update BSK ref
ayoy Sep 21, 2023
e70d33e
Fix tests compilation
ayoy Sep 21, 2023
3e123f5
Remove unnecessary changes
ayoy Sep 21, 2023
b331786
Address review feedback, part 1
ayoy Sep 21, 2023
d9b7b8b
Use FavoritesDisplayModeSyncHandlerBase
ayoy Sep 21, 2023
46c2b80
Use prepareLegacyFoldersStructure for legacy migration
ayoy Sep 21, 2023
160e49e
Initialize EmailProtectionSyncHandler in SyncSettingsAdapter
ayoy Sep 21, 2023
3581ee0
Fire pixel before crashing when migration to FFS favorites fails'
ayoy Sep 21, 2023
e6caba6
Handle favorites after disabling sync as part of 'cleaning up databas…
ayoy Sep 21, 2023
380cc92
Parametrize BookmarksExporter with favorites display mode
ayoy Sep 21, 2023
297a170
Parametrize AddOrEditBookmarkViewController with appSettings
ayoy Sep 21, 2023
451d815
Use favorites display mode in BookmarkFoldersTableViewController
ayoy Sep 21, 2023
da018f4
Parametrize appSettings in more places
ayoy Sep 21, 2023
26db43b
Use favoritesDisplayMode in BookmarksDataSource
ayoy Sep 21, 2023
8cd0a4e
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Sep 21, 2023
38f7738
Update bookmark delete undo for FFS favorites
ayoy Sep 21, 2023
253cadc
Prepare legacy folders structure also in another place in LegacyBookm…
ayoy Sep 21, 2023
eebdd48
Update BSK ref
ayoy Sep 23, 2023
4d3c3d6
Refresh bookmarks views on display mode change
bwaresiak Sep 26, 2023
77cb4c0
new sync flow (#2071)
SabrinaTardio Oct 11, 2023
afa6102
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 11, 2023
1c278a5
Update BSK ref
ayoy Oct 11, 2023
9f2fb8a
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 13, 2023
bb2834c
Update BSK ref
ayoy Oct 13, 2023
dc23f73
Sync limit exceeded (#2105)
SabrinaTardio Oct 20, 2023
707b6c6
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Oct 29, 2023
2afb0ef
Fetch favicons for bookmarks received in sync
ayoy Oct 18, 2023
0286b89
Replace syncDidUpdateData with syncDidFinish
ayoy Oct 30, 2023
c6ac996
Add hasFavicon
ayoy Oct 31, 2023
dcd4704
Reset bookmarks sync timestamp after migrating to FFS favorites (#2091)
ayoy Nov 3, 2023
6f155ce
Add a setting for favicons fetching and only do it while the setting …
ayoy Nov 6, 2023
efbb57b
Merge branch 'develop' into dominik/sync-ffs-favorites
ayoy Nov 7, 2023
7fae63c
Merge branch 'dominik/sync-ffs-favorites' into dominik/sync-favicons-…
ayoy Nov 7, 2023
5b10e75
Update SyncBookmarksAdapter to use FaviconsFetcherInput
ayoy Nov 8, 2023
e597505
Add support for initial favicons fetch
ayoy Nov 8, 2023
708b3f4
Add in-context favicons fetcher onboarding
ayoy Nov 9, 2023
a581ea6
Cancel favicons fetching when the app is put to background
ayoy Nov 9, 2023
5d3835a
Implement final UI for the in-context favicons fetcher onboarding
ayoy Nov 10, 2023
db2f7e0
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 10, 2023
f1444e6
Fix SwiftLint warnings
ayoy Nov 10, 2023
bf8c64e
Remove unused import
ayoy Nov 10, 2023
acce293
Add BookmarksFaviconsFetcherErrorHandler and handle failable initiali…
ayoy Nov 13, 2023
0dfea71
Update BSK ref
ayoy Nov 14, 2023
81e27cd
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 14, 2023
87950d4
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 14, 2023
0cbe870
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 14, 2023
ba02602
Display favicons fetcher onboarding only when there's no other view c…
ayoy Nov 14, 2023
d5882b4
Don't quit the app when favicons fetcher can't be instantiated
ayoy Nov 15, 2023
5f58676
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 15, 2023
6ee083a
Reset default internal user flag
ayoy Nov 16, 2023
a2af0f6
Make SwiftLint happy
ayoy Nov 16, 2023
d9c2311
Fix tests compilation
ayoy Nov 16, 2023
6d916d5
Update BSK ref
ayoy Nov 17, 2023
d86798a
Update BSK ref
ayoy Nov 20, 2023
afb5d11
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 23, 2023
23e03fd
Update design and copy for the favicons fetcher onboarding dialog
ayoy Nov 28, 2023
397863e
Merge branch 'develop' into dominik/sync-favicons-fetching
ayoy Nov 29, 2023
5356638
Throw an error when Application Support directory is inaccessible
ayoy Nov 29, 2023
f46b1c4
Update BSK to 87.0.0
ayoy Nov 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ extension Pixel {
case bookmarksCleanupFailed
case bookmarksCleanupAttemptedWhileSyncWasEnabled
case favoritesCleanupFailed
case bookmarksFaviconsFetcherStateStoreInitializationFailed
case bookmarksFaviconsFetcherFailed

case credentialsDatabaseCleanupFailed
case credentialsCleanupAttemptedWhileSyncWasEnabled
Expand Down Expand Up @@ -990,6 +992,8 @@ extension Pixel.Event {
case .bookmarksCleanupFailed: return "m_d_bookmarks_cleanup_failed"
case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "m_d_bookmarks_cleanup_attempted_while_sync_was_enabled"
case .favoritesCleanupFailed: return "m_d_favorites_cleanup_failed"
case .bookmarksFaviconsFetcherStateStoreInitializationFailed: return "m_d_bookmarks_favicons_fetcher_state_store_initialization_failed"
case .bookmarksFaviconsFetcherFailed: return "m_d_bookmarks_favicons_fetcher_failed"

case .credentialsDatabaseCleanupFailed: return "m_d_credentials_database_cleanup_failed_2"
case .credentialsCleanupAttemptedWhileSyncWasEnabled: return "m_d_credentials_cleanup_attempted_while_sync_was_enabled"
Expand Down
135 changes: 124 additions & 11 deletions Core/SyncBookmarksAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,40 @@ public protocol FavoritesDisplayModeStoring: AnyObject {
var favoritesDisplayMode: FavoritesDisplayMode { get set }
}

public class BookmarksFaviconsFetcherErrorHandler: EventMapping<BookmarksFaviconsFetcherError> {

public init() {
super.init { event, _, _, _ in
Pixel.fire(pixel: .bookmarksFaviconsFetcherFailed, error: event.underlyingError)
}
}

override init(mapping: @escaping EventMapping<BookmarksFaviconsFetcherError>.Mapping) {
fatalError("Use init()")
}
}

public enum SyncBookmarksAdapterError: CustomNSError {
case unableToAccessFaviconsFetcherStateStoreDirectory

public static let errorDomain: String = "SyncBookmarksAdapterError"

public var errorCode: Int {
switch self {
case .unableToAccessFaviconsFetcherStateStoreDirectory:
return 1
}
}
}

public final class SyncBookmarksAdapter {

public static let syncBookmarksPausedStateChanged = Notification.Name("com.duckduckgo.app.SyncPausedStateChanged")
public static let bookmarksSyncLimitReached = Notification.Name("com.duckduckgo.app.SyncBookmarksLimitReached")

public private(set) var provider: BookmarksProvider?
public let databaseCleaner: BookmarkDatabaseCleaner
public let syncDidCompletePublisher: AnyPublisher<Void, Never>
public let widgetRefreshCancellable: AnyCancellable
public static let syncBookmarksPausedStateChanged = Notification.Name("com.duckduckgo.app.SyncPausedStateChanged")
public static let bookmarksSyncLimitReached = Notification.Name("com.duckduckgo.app.SyncBookmarksLimitReached")

public var shouldResetBookmarksSyncTimestamp: Bool = false {
willSet {
Expand All @@ -55,6 +81,22 @@ public final class SyncBookmarksAdapter {
@UserDefaultsWrapper(key: .syncBookmarksPausedErrorDisplayed, defaultValue: false)
static private var didShowBookmarksSyncPausedError: Bool

@Published
public var isFaviconsFetchingEnabled: Bool = UserDefaultsWrapper(key: .syncAutomaticallyFetchFavicons, defaultValue: false).wrappedValue {
didSet {
var udWrapper = UserDefaultsWrapper(key: .syncAutomaticallyFetchFavicons, defaultValue: false)
udWrapper.wrappedValue = isFaviconsFetchingEnabled
if isFaviconsFetchingEnabled {
faviconsFetcher?.initializeFetcherState()
} else {
faviconsFetcher?.cancelOngoingFetchingIfNeeded()
}
}
}

@UserDefaultsWrapper(key: .syncIsEligibleForFaviconsFetcherOnboarding, defaultValue: false)
public var isEligibleForFaviconsFetcherOnboarding: Bool

public init(database: CoreDataDatabase, favoritesDisplayModeStorage: FavoritesDisplayModeStoring) {
self.database = database
self.favoritesDisplayModeStorage = favoritesDisplayModeStorage
Expand All @@ -74,6 +116,7 @@ public final class SyncBookmarksAdapter {
if shouldEnable {
databaseCleaner.scheduleRegularCleaning()
handleFavoritesAfterDisablingSync()
isFaviconsFetchingEnabled = false
} else {
databaseCleaner.cancelCleaningSchedule()
}
Expand All @@ -84,19 +127,62 @@ public final class SyncBookmarksAdapter {
return
}

let faviconsFetcher = setUpFaviconsFetcher()

let provider = BookmarksProvider(
database: database,
metadataStore: metadataStore,
syncDidUpdateData: { [syncDidCompleteSubject] in
syncDidCompleteSubject.send()
syncDidUpdateData: { [weak self] in
self?.syncDidCompleteSubject.send()
Self.isSyncBookmarksPaused = false
Self.didShowBookmarksSyncPausedError = false
},
syncDidFinish: { [weak self] faviconsFetcherInput in
if let faviconsFetcher, self?.isFaviconsFetchingEnabled == true {
if let faviconsFetcherInput {
faviconsFetcher.updateBookmarkIDs(
modified: faviconsFetcherInput.modifiedBookmarksUUIDs,
deleted: faviconsFetcherInput.deletedBookmarksUUIDs
)
}
faviconsFetcher.startFetching()
}
}
)
if shouldResetBookmarksSyncTimestamp {
provider.lastSyncTimestamp = nil
}

bindSyncErrorPublisher(provider)

self.provider = provider
self.faviconsFetcher = faviconsFetcher
}

private func setUpFaviconsFetcher() -> BookmarksFaviconsFetcher? {
let stateStore: BookmarksFaviconsFetcherStateStore
do {
guard let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw SyncBookmarksAdapterError.unableToAccessFaviconsFetcherStateStoreDirectory
}
stateStore = try BookmarksFaviconsFetcherStateStore(applicationSupportURL: url)
} catch {
Pixel.fire(pixel: .bookmarksFaviconsFetcherStateStoreInitializationFailed, error: error)
os_log(.error, log: .syncLog, "Failed to initialize BookmarksFaviconsFetcherStateStore: %{public}s", String(reflecting: error))
return nil
}

return BookmarksFaviconsFetcher(
database: database,
stateStore: stateStore,
fetcher: FaviconFetcher(),
faviconStore: Favicons.shared,
errorEvents: BookmarksFaviconsFetcherErrorHandler(),
log: .syncLog
)
}

private func bindSyncErrorPublisher(_ provider: BookmarksProvider) {
syncErrorCancellable = provider.syncErrorPublisher
.sink { error in
switch error {
Expand Down Expand Up @@ -126,15 +212,31 @@ public final class SyncBookmarksAdapter {
}
os_log(.error, log: OSLog.syncLog, "Bookmarks Sync error: %{public}s", String(reflecting: error))
}

self.provider = provider
}

static private func notifyBookmarksSyncLimitReached() {
if !Self.didShowBookmarksSyncPausedError {
NotificationCenter.default.post(name: Self.bookmarksSyncLimitReached, object: nil)
Self.didShowBookmarksSyncPausedError = true
public func cancelFaviconsFetching(_ application: UIApplication) {
guard let faviconsFetcher else {
return
}
if faviconsFetcher.isFetchingInProgress == true {
os_log(.debug, log: .syncLog, "Favicons Fetching is in progress. Starting background task to allow it to gracefully complete.")

var taskID: UIBackgroundTaskIdentifier!
taskID = application.beginBackgroundTask(withName: "Cancelled Favicons Fetching Completion Task") {
os_log(.debug, log: .syncLog, "Forcing background task completion")
application.endBackgroundTask(taskID)
}
faviconsFetchingDidFinishCancellable?.cancel()
faviconsFetchingDidFinishCancellable = faviconsFetcher.$isFetchingInProgress.dropFirst().filter { !$0 }
.prefix(1)
.receive(on: DispatchQueue.main)
.sink { _ in
os_log(.debug, log: .syncLog, "Ending background task")
application.endBackgroundTask(taskID)
}
}

faviconsFetcher.cancelOngoingFetchingIfNeeded()
}

private func handleFavoritesAfterDisablingSync() {
Expand All @@ -158,8 +260,19 @@ public final class SyncBookmarksAdapter {
}
}

static private func notifyBookmarksSyncLimitReached() {
if !Self.didShowBookmarksSyncPausedError {
NotificationCenter.default.post(name: Self.bookmarksSyncLimitReached, object: nil)
Self.didShowBookmarksSyncPausedError = true
}
}

private var syncDidCompleteSubject = PassthroughSubject<Void, Never>()
private var syncErrorCancellable: AnyCancellable?

private let database: CoreDataDatabase
private let favoritesDisplayModeStorage: FavoritesDisplayModeStoring
private var faviconsFetcher: BookmarksFaviconsFetcher?
private var faviconsFetchingDidFinishCancellable: AnyCancellable?
private var widgetRefreshCancellable: AnyCancellable?
}
1 change: 1 addition & 0 deletions Core/SyncCredentialsAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public final class SyncCredentialsAdapter {
NotificationCenter.default.post(name: syncCredentialsPausedStateChanged, object: nil)
}
}

@UserDefaultsWrapper(key: .syncCredentialsPausedErrorDisplayed, defaultValue: false)
static private var didShowCredentialsSyncPausedError: Bool

Expand Down
4 changes: 4 additions & 0 deletions Core/UserDefaultsPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public struct UserDefaultsWrapper<T> {
case syncCredentialsPaused = "com.duckduckgo.ios.sync-credentialsPaused"
case syncBookmarksPausedErrorDisplayed = "com.duckduckgo.ios.sync-bookmarksPausedErrorDisplayed"
case syncCredentialsPausedErrorDisplayed = "com.duckduckgo.ios.sync-credentialsPausedErrorDisplayed"
case syncAutomaticallyFetchFavicons = "com.duckduckgo.ios.sync-automatically-fetch-favicons"
case syncIsFaviconsFetcherEnabled = "com.duckduckgo.ios.sync-is-favicons-fetcher-enabled"
case syncIsEligibleForFaviconsFetcherOnboarding = "com.duckduckgo.ios.sync-is-eligible-for-favicons-fetcher-onboarding"
case syncDidPresentFaviconsFetcherOnboarding = "com.duckduckgo.ios.sync-did-present-favicons-fetcher-onboarding"

case networkProtectionDebugOptionAlwaysOnDisabled = "com.duckduckgo.network-protection.always-on.disabled"
case networkProtectionWaitlistTermsAndConditionsAccepted = "com.duckduckgo.ios.vpn.terms-and-conditions-accepted"
Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
3760DFED299315EF0045A446 /* Waitlist in Frameworks */ = {isa = PBXBuildFile; productRef = 3760DFEC299315EF0045A446 /* Waitlist */; };
377D80222AB48554002AF251 /* FavoritesDisplayModeSyncHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D80212AB48554002AF251 /* FavoritesDisplayModeSyncHandler.swift */; };
379E877429E97C8D001C8BB0 /* BookmarksCleanupErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379E877329E97C8D001C8BB0 /* BookmarksCleanupErrorHandling.swift */; };
37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A6A8FD2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift */; };
37CBCA9E2A8A659C0050218F /* SyncSettingsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CBCA9D2A8A659C0050218F /* SyncSettingsAdapter.swift */; };
37CEFCAC2A673B90001EF741 /* CredentialsCleanupErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CEFCAB2A673B90001EF741 /* CredentialsCleanupErrorHandling.swift */; };
37DF000A29F9C416002B7D3E /* SyncMetadataDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DF000929F9C416002B7D3E /* SyncMetadataDatabase.swift */; };
Expand Down Expand Up @@ -1291,6 +1292,7 @@
37445F962A155F7C0029F789 /* SyncDataProviders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncDataProviders.swift; sourceTree = "<group>"; };
377D80212AB48554002AF251 /* FavoritesDisplayModeSyncHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesDisplayModeSyncHandler.swift; sourceTree = "<group>"; };
379E877329E97C8D001C8BB0 /* BookmarksCleanupErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCleanupErrorHandling.swift; sourceTree = "<group>"; };
37A6A8FD2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconsFetcherOnboarding.swift; sourceTree = "<group>"; };
37CBCA9D2A8A659C0050218F /* SyncSettingsAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncSettingsAdapter.swift; sourceTree = "<group>"; };
37CEFCAB2A673B90001EF741 /* CredentialsCleanupErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsCleanupErrorHandling.swift; sourceTree = "<group>"; };
37DF000929F9C416002B7D3E /* SyncMetadataDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncMetadataDatabase.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4076,6 +4078,7 @@
85F98F8C296F0ED100742F4A /* Sync */ = {
isa = PBXGroup;
children = (
37A6A8FD2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift */,
377D80202AB4853A002AF251 /* SettingSyncHandlers */,
85F98F97296F4CB100742F4A /* SyncAssets.xcassets */,
85F0E97229952D7A003D5181 /* DuckDuckGo Recovery Document.pdf */,
Expand Down Expand Up @@ -6466,6 +6469,7 @@
85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */,
98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */,
C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */,
37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */,
1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */,
02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */,
F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */,
Expand Down Expand Up @@ -9203,7 +9207,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 86.1.0;
version = 87.0.0;
};
};
C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit",
"state": {
"branch": null,
"revision": "1400c9ca17dd770d6eb708288f97c9aab749d0ef",
"version": "86.1.0"
"revision": "f1ae021c12c2afe3ade7ec0e41712725c03c3da4",
"version": "87.0.0"
}
},
{
Expand Down Expand Up @@ -156,7 +156,7 @@
},
{
"package": "TrackerRadarKit",
"repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit",
"repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit.git",
"state": {
"branch": null,
"revision": "4684440d03304e7638a2c8086895367e90987463",
Expand Down
1 change: 1 addition & 0 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
lastBackgroundDate = Date()
AppDependencyProvider.shared.autofillLoginSession.endSession()
suspendSync()
syncDataProviders.bookmarksAdapter.cancelFaviconsFetching(application)
}

private func suspendSync() {
Expand Down
7 changes: 6 additions & 1 deletion DuckDuckGo/BookmarksDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Core
class BookmarksDataSource: NSObject, UITableViewDataSource {

let viewModel: BookmarkListInteracting
var onFaviconMissing: ((String) -> Void)?

var isEmpty: Bool {
viewModel.bookmarks.isEmpty
Expand All @@ -50,7 +51,11 @@ class BookmarksDataSource: NSObject, UITableViewDataSource {
return cell
} else {
let cell = BookmarksViewControllerCellFactory.makeBookmarkCell(tableView, forIndexPath: indexPath)
cell.faviconImageView.loadFavicon(forDomain: bookmark.urlObject?.host, usingCache: .fireproof)
cell.faviconImageView.loadFavicon(forDomain: bookmark.urlObject?.host, usingCache: .fireproof) { [weak self] _, isFake in
if isFake, let host = bookmark.urlObject?.host {
self?.onFaviconMissing?(host)
}
}
cell.titleLabel.text = bookmark.title
cell.favoriteImageViewContainer.isHidden = !bookmark.isFavorite(on: viewModel.favoritesDisplayMode.displayedFolder)
return cell
Expand Down
11 changes: 10 additions & 1 deletion DuckDuckGo/BookmarksViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ class BookmarksViewController: UIViewController, UITableViewDelegate {
fileprivate let viewModel: BookmarkListInteracting

fileprivate lazy var dataSource: BookmarksDataSource = {
return BookmarksDataSource(viewModel: viewModel)
let dataSource = BookmarksDataSource(viewModel: viewModel)
dataSource.onFaviconMissing = { [weak self] _ in
guard let self else {
return
}
self.faviconsFetcherOnboarding.presentOnboardingIfNeeded(from: self)
}
return dataSource
}()

var searchDataSource: SearchBookmarksDataSource
Expand Down Expand Up @@ -833,6 +840,8 @@ class BookmarksViewController: UIViewController, UITableViewDelegate {
}
}

private(set) lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding =
.init(syncService: syncService, syncBookmarksAdapter: syncDataProviders.bookmarksAdapter)
}

extension BookmarksViewController: UISearchBarDelegate {
Expand Down
32 changes: 32 additions & 0 deletions DuckDuckGo/Favicons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// limitations under the License.
//

import Bookmarks
import Common
import Kingfisher
import UIKit
Expand Down Expand Up @@ -481,4 +482,35 @@ public class Favicons {
}

}

extension Favicons: Bookmarks.FaviconStoring {

public func hasFavicon(for domain: String) -> Bool {
guard let targetCache = Favicons.Constants.caches[.fireproof],
let resource = defaultResource(forDomain: domain)
else {
return false
}

return targetCache.isCached(forKey: resource.cacheKey)
}

public func storeFavicon(_ imageData: Data, with url: URL?, for documentURL: URL) async throws {

guard let domain = documentURL.host,
let options = kfOptions(forDomain: domain, withURL: documentURL, usingCache: .fireproof),
let resource = defaultResource(forDomain: domain),
let targetCache = Favicons.Constants.caches[.fireproof],
let image = UIImage(data: imageData)
else {
return
}

Task {
let image = self.scaleDownIfNeeded(image: image, toFit: Constants.maxFaviconSize)
targetCache.store(image, forKey: resource.cacheKey, options: .init(options))
WidgetCenter.shared.reloadAllTimelines()
}
}
}
// swiftlint:enable type_body_length file_length
Loading