diff --git a/AltStore/Browse/FeaturedViewController.swift b/AltStore/Browse/FeaturedViewController.swift index 6634aaff..0e905a99 100644 --- a/AltStore/Browse/FeaturedViewController.swift +++ b/AltStore/Browse/FeaturedViewController.swift @@ -348,11 +348,6 @@ private extension FeaturedViewController // Instead, sort by StoreApp.installedApp.storeApp.source.sourceIdentifier, which will be either nil OR source ID. NSSortDescriptor(keyPath: \StoreApp.installedApp?.storeApp?.sourceIdentifier, ascending: true), - // Show featured apps first. - // Sorting by StoreApp.featuringSource crashes because Source does not respond to compare: - // Instead, sort by StoreApp.featuringSource.identifier, which will be either nil OR source ID. - NSSortDescriptor(keyPath: \StoreApp.featuringSource?.identifier, ascending: false), - // Randomize order within sections. NSSortDescriptor(keyPath: \StoreApp.featuredSortID, ascending: true), diff --git a/AltStoreCore/Model/DatabaseManager.swift b/AltStoreCore/Model/DatabaseManager.swift index 9a72ea53..50d6ac26 100644 --- a/AltStoreCore/Model/DatabaseManager.swift +++ b/AltStoreCore/Model/DatabaseManager.swift @@ -188,11 +188,15 @@ public extension DatabaseManager { // Randomize source order let fetchRequest = Source.fetchRequest() - let sources = try context.fetch(fetchRequest) + fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(Source._apps)] + let sources = try context.fetch(fetchRequest) for source in sources { source.featuredSortID = UUID().uuidString + + // Randomize app order + self.updateAppFeaturedSortIDs(for: source) } try context.save() @@ -201,23 +205,43 @@ public extension DatabaseManager { Logger.main.error("Failed to update source order. \(error.localizedDescription, privacy: .public)") } - - do - { - // Randomize app order - let fetchRequest = StoreApp.fetchRequest() - let apps = try context.fetch(fetchRequest) - - for app in apps - { - app.featuredSortID = UUID().uuidString - } - - try context.save() + } + } + + private func updateAppFeaturedSortIDs(for source: Source) + { + let featuredAppSlots = 5 + + // Filter out already installed apps. + var featuredApps = Set(source.effectiveFeaturedApps.filter { $0.installedApp == nil }.prefix(featuredAppSlots)) // Take just first 5 featured apps + var otherApps = source.apps.filter { $0.installedApp == nil && !featuredApps.contains($0) } + + let remainingSlots = featuredAppSlots - featuredApps.count + + // Fill in remaining featured app slots with random uninstalled apps. + for _ in 0 ..< remainingSlots + { + guard let index = otherApps.indices.randomElement() else { + // No remaining apps, so stop filling in slots. + break } - catch + + let randomApp = otherApps[index] + + featuredApps.insert(randomApp) + otherApps.remove(at: index) + } + + for storeApp in source.apps + { + if featuredApps.contains(storeApp) { - Logger.main.error("Failed to update app order. \(error.localizedDescription, privacy: .public)") + storeApp.featuredSortID = UUID().uuidString + } + else + { + // Prepend "_" to ensure it's sorted after featuredApps. + storeApp.featuredSortID = "_" + UUID().uuidString } } }