Skip to content

Commit

Permalink
Merge pull request #84 from milanvarady/Improve-search
Browse files Browse the repository at this point in the history
Rework search with Ifrit
  • Loading branch information
milanvarady authored Jan 6, 2025
2 parents 6a33f52 + 8dd3806 commit 580e27f
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 191 deletions.
46 changes: 25 additions & 21 deletions Applite.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
4125BB8A29539907000FBD25 /* PlaceholderAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4125BB8929539907000FBD25 /* PlaceholderAppView.swift */; };
4126353E2A77C6EF00155034 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4126353D2A77C6EF00155034 /* ArrayExtension.swift */; };
412635442A77FB1600155034 /* BrewInstallationProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 412635432A77FB1600155034 /* BrewInstallationProgress.swift */; };
4129FFD92A7A613E00CFE392 /* Fuse in Frameworks */ = {isa = PBXBuildFile; productRef = 4129FFD82A7A613E00CFE392 /* Fuse */; };
413E60B72BBAE5E000978F6A /* NetworkProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413E60B62BBAE5E000978F6A /* NetworkProxyManager.swift */; };
413E60C02BBF0E5C00978F6A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 413E60BF2BBF0E5C00978F6A /* Kingfisher */; };
413E60C22BBFF98A00978F6A /* AppIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413E60C12BBFF98A00978F6A /* AppIconView.swift */; };
Expand Down Expand Up @@ -77,7 +76,6 @@
419256452D1E0A7000D9EF10 /* BrewPathSelectorView+CustomPathOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256442D1E0A7000D9EF10 /* BrewPathSelectorView+CustomPathOption.swift */; };
419256472D1E0B0900D9EF10 /* BrewPathSelectorView+GetPathDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256462D1E0B0900D9EF10 /* BrewPathSelectorView+GetPathDescription.swift */; };
4192564B2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192564A2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift */; };
4192564D2D1E0BDD00D9EF10 /* DownloadView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192564C2D1E0BDD00D9EF10 /* DownloadView+Search.swift */; };
4192564F2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192564E2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift */; };
419256522D1E0D0500D9EF10 /* DiscoverSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256512D1E0D0500D9EF10 /* DiscoverSectionView.swift */; };
419256552D1E0E2500D9EF10 /* DiscoverSectionView+CategoryHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256542D1E0E2500D9EF10 /* DiscoverSectionView+CategoryHeader.swift */; };
Expand Down Expand Up @@ -111,6 +109,9 @@
419256B42D26B8A200D9EF10 /* CaskAdditionalInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256B32D26B8A200D9EF10 /* CaskAdditionalInfo.swift */; };
419256B62D26BBBD00D9EF10 /* AppView+GetInfoButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256B52D26BBBD00D9EF10 /* AppView+GetInfoButton.swift */; };
419256B92D26C02E00D9EF10 /* CaskInfoWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256B82D26C02E00D9EF10 /* CaskInfoWindowView.swift */; };
419256BC2D2724C200D9EF10 /* Ifrit in Frameworks */ = {isa = PBXBuildFile; productRef = 419256BB2D2724C200D9EF10 /* Ifrit */; };
419256BE2D27253D00D9EF10 /* Cask+Searchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256BD2D27253D00D9EF10 /* Cask+Searchable.swift */; };
419256C02D273B2800D9EF10 /* SearchableCaskCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256BF2D273B2800D9EF10 /* SearchableCaskCollection.swift */; };
419506A42964A27F00FE5802 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419506A32964A27F00FE5802 /* SetupView.swift */; };
419506A62964A5EF00FE5802 /* BrewPathSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419506A52964A5EF00FE5802 /* BrewPathSelectorView.swift */; };
4196C8F528F9CB2600EADDDA /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4196C8F428F9CB2600EADDDA /* DiscoverView.swift */; };
Expand Down Expand Up @@ -192,7 +193,6 @@
419256442D1E0A7000D9EF10 /* BrewPathSelectorView+CustomPathOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewPathSelectorView+CustomPathOption.swift"; sourceTree = "<group>"; };
419256462D1E0B0900D9EF10 /* BrewPathSelectorView+GetPathDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewPathSelectorView+GetPathDescription.swift"; sourceTree = "<group>"; };
4192564A2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DownloadView+NoSearchResults.swift"; sourceTree = "<group>"; };
4192564C2D1E0BDD00D9EF10 /* DownloadView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DownloadView+Search.swift"; sourceTree = "<group>"; };
4192564E2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DownloadView+SortingOptions.swift"; sourceTree = "<group>"; };
419256512D1E0D0500D9EF10 /* DiscoverSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverSectionView.swift; sourceTree = "<group>"; };
419256542D1E0E2500D9EF10 /* DiscoverSectionView+CategoryHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoverSectionView+CategoryHeader.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -225,6 +225,8 @@
419256B32D26B8A200D9EF10 /* CaskAdditionalInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaskAdditionalInfo.swift; sourceTree = "<group>"; };
419256B52D26BBBD00D9EF10 /* AppView+GetInfoButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppView+GetInfoButton.swift"; sourceTree = "<group>"; };
419256B82D26C02E00D9EF10 /* CaskInfoWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaskInfoWindowView.swift; sourceTree = "<group>"; };
419256BD2D27253D00D9EF10 /* Cask+Searchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cask+Searchable.swift"; sourceTree = "<group>"; };
419256BF2D273B2800D9EF10 /* SearchableCaskCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchableCaskCollection.swift; sourceTree = "<group>"; };
419506A32964A27F00FE5802 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
419506A52964A5EF00FE5802 /* BrewPathSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPathSelectorView.swift; sourceTree = "<group>"; };
419506A729696A5300FE5802 /* Applite-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Applite-Info.plist"; sourceTree = SOURCE_ROOT; };
Expand All @@ -246,8 +248,8 @@
41C8FA292A7A598B000BB9A2 /* Sparkle in Frameworks */,
419256B22D26033400D9EF10 /* DebouncedOnChange in Frameworks */,
4189CE392937CD41009C836D /* Shimmer in Frameworks */,
419256BC2D2724C200D9EF10 /* Ifrit in Frameworks */,
418E9EF42AACD9C000046A58 /* CircularProgress in Frameworks */,
4129FFD92A7A613E00CFE392 /* Fuse in Frameworks */,
413E60C02BBF0E5C00978F6A /* Kingfisher in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -401,6 +403,7 @@
4191392B29159B5C00F1D75D /* CaskDTO.swift */,
4166EE7C28F73B2300CE305A /* BrewAnalytics.swift */,
419256B32D26B8A200D9EF10 /* CaskAdditionalInfo.swift */,
419256BF2D273B2800D9EF10 /* SearchableCaskCollection.swift */,
);
path = "Cask Models";
sourceTree = "<group>";
Expand Down Expand Up @@ -534,7 +537,6 @@
children = (
4196C8F828F9CDF700EADDDA /* DownloadView.swift */,
4192564A2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift */,
4192564C2D1E0BDD00D9EF10 /* DownloadView+Search.swift */,
4192564E2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift */,
);
path = Download;
Expand Down Expand Up @@ -586,6 +588,7 @@
children = (
418F332328EC8BA10023D76F /* Cask.swift */,
4192566D2D1F293700D9EF10 /* Cask+LaunchApp.swift */,
419256BD2D27253D00D9EF10 /* Cask+Searchable.swift */,
);
path = Cask;
sourceTree = "<group>";
Expand Down Expand Up @@ -688,10 +691,10 @@
packageProductDependencies = (
4189CE382937CD41009C836D /* Shimmer */,
41C8FA282A7A598B000BB9A2 /* Sparkle */,
4129FFD82A7A613E00CFE392 /* Fuse */,
418E9EF32AACD9C000046A58 /* CircularProgress */,
413E60BF2BBF0E5C00978F6A /* Kingfisher */,
419256B12D26033400D9EF10 /* DebouncedOnChange */,
419256BB2D2724C200D9EF10 /* Ifrit */,
);
productName = Applite;
productReference = 414074F128DF53E80073EB22 /* Applite.app */;
Expand Down Expand Up @@ -728,10 +731,10 @@
packageReferences = (
4189CE372937CD41009C836D /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */,
41C8FA272A7A598B000BB9A2 /* XCRemoteSwiftPackageReference "Sparkle" */,
4129FFD72A7A613E00CFE392 /* XCRemoteSwiftPackageReference "fuse-swift" */,
418E9EF22AACD9C000046A58 /* XCRemoteSwiftPackageReference "CircularProgressSwiftUI" */,
413E60BE2BBF0E5C00978F6A /* XCRemoteSwiftPackageReference "Kingfisher" */,
419256B02D26033400D9EF10 /* XCRemoteSwiftPackageReference "DebouncedOnChange" */,
419256BA2D2724C200D9EF10 /* XCRemoteSwiftPackageReference "Ifrit" */,
);
productRefGroup = 414074F228DF53E80073EB22 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -764,11 +767,11 @@
files = (
4196C90028F9E1F400EADDDA /* InstalledView.swift in Sources */,
415563A42A98C54300AE2F2E /* AppdirSelectorView.swift in Sources */,
4192564D2D1E0BDD00D9EF10 /* DownloadView+Search.swift in Sources */,
4140750528DF5FA60073EB22 /* AppView.swift in Sources */,
418F332428EC8BA10023D76F /* Cask.swift in Sources */,
4126353E2A77C6EF00155034 /* ArrayExtension.swift in Sources */,
4192564B2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift in Sources */,
419256BE2D27253D00D9EF10 /* Cask+Searchable.swift in Sources */,
419256432D1E0A0200D9EF10 /* BrewPathSelectorView+PathOption.swift in Sources */,
4192564F2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift in Sources */,
419256122D1DDBD400D9EF10 /* AlertManager.swift in Sources */,
Expand Down Expand Up @@ -865,6 +868,7 @@
419256162D1DEA0A00D9EF10 /* AppView+IconAndDescriptionView.swift in Sources */,
419256942D24255000D9EF10 /* CaskLoadError.swift in Sources */,
419256A62D25D00200D9EF10 /* AppMigrationView+ImportView.swift in Sources */,
419256C02D273B2800D9EF10 /* SearchableCaskCollection.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1086,14 +1090,6 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
4129FFD72A7A613E00CFE392 /* XCRemoteSwiftPackageReference "fuse-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/krisk/fuse-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
413E60BE2BBF0E5C00978F6A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
Expand Down Expand Up @@ -1126,6 +1122,14 @@
minimumVersion = 2.0.0;
};
};
419256BA2D2724C200D9EF10 /* XCRemoteSwiftPackageReference "Ifrit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ukushu/Ifrit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
41C8FA272A7A598B000BB9A2 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
Expand All @@ -1137,11 +1141,6 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
4129FFD82A7A613E00CFE392 /* Fuse */ = {
isa = XCSwiftPackageProductDependency;
package = 4129FFD72A7A613E00CFE392 /* XCRemoteSwiftPackageReference "fuse-swift" */;
productName = Fuse;
};
413E60BF2BBF0E5C00978F6A /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 413E60BE2BBF0E5C00978F6A /* XCRemoteSwiftPackageReference "Kingfisher" */;
Expand All @@ -1162,6 +1161,11 @@
package = 419256B02D26033400D9EF10 /* XCRemoteSwiftPackageReference "DebouncedOnChange" */;
productName = DebouncedOnChange;
};
419256BB2D2724C200D9EF10 /* Ifrit */ = {
isa = XCSwiftPackageProductDependency;
package = 419256BA2D2724C200D9EF10 /* XCRemoteSwiftPackageReference "Ifrit" */;
productName = Ifrit;
};
41C8FA282A7A598B000BB9A2 /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = 41C8FA272A7A598B000BB9A2 /* XCRemoteSwiftPackageReference "Sparkle" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ extension CaskManager {

// Update state
cask.isInstalled = true
self.installedCasks.insert(cask)
self.installedCasks.addCask(cask)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ extension CaskManager {

// Set casks reseve capacity for better performance
await self.casks.reserveCapacity(try caskInfo.count)
await self.allCasks.setReserveCapacity(try caskInfo.count)

// Casks by category
var categoryDict: [CategoryId: [Cask]] = [:]
Expand All @@ -172,8 +173,11 @@ extension CaskManager {

casks[cask.id] = cask

// Add to searchable collections
self.allCasks.addCask(cask)

if isInstalled {
self.installedCasks.insert(cask)
self.installedCasks.addCask(cask)
}

// Add to category if needed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ extension CaskManager {

self.outdatedCasks.removeAll()

var outdatedCasks: [Cask] = []

for caskID in outdatedCaskIDs {
if let cask = self.casks[caskID] {
self.outdatedCasks.insert(cask)
outdatedCasks.append(cask)
}
}

self.outdatedCasks.defineCasks(outdatedCasks)

Self.logger.info("Outdated apps refreshed")
}
}
8 changes: 5 additions & 3 deletions Applite/Model/Cask Models/Cask Manager/CaskManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ final class CaskManager: ObservableObject {
@Published var casks: [CaskId: Cask] = [:]
/// All currently running brew tasks
@Published var activeTasks: [BrewTask] = []
@Published var installedCasks: Set<Cask> = []
@Published var outdatedCasks: Set<Cask> = []

@Published var alert = AlertManager()

// Searchble cask collections
let allCasks = SearchableCaskCollection()
let installedCasks = SearchableCaskCollection()
let outdatedCasks = SearchableCaskCollection()

// Precompiled cask category dicts
var categories: [CategoryViewModel] = []

Expand Down
19 changes: 19 additions & 0 deletions Applite/Model/Cask Models/Cask/Cask+Searchable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Cask+Searchable.swift
// Applite
//
// Created by Milán Várady on 2025.01.02.
//

import Foundation
import Ifrit

extension Cask: Searchable {
nonisolated var weightedSearchProperties: [FuseProp] {
return [
FuseProp(self.info.name, weight: 1),
FuseProp(self.info.id, weight: 1),
FuseProp(self.info.description, weight: 0.3)
]
}
}
73 changes: 73 additions & 0 deletions Applite/Model/Cask Models/SearchableCaskCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// SearchableCaskCollection.swift
// Applite
//
// Created by Milán Várady on 2025.01.02.
//

import Foundation
import Ifrit

@MainActor
class SearchableCaskCollection: ObservableObject {
@Published private(set) var casks: [Cask] = []
@Published private(set) var casksMatchingSearch: [Cask] = []

init(casks: [Cask] = []) {
self.defineCasks(casks)
}

func defineCasks(_ casks: [Cask]) {
self.casks = casks
self.casksMatchingSearch = casks
}

func addCask(_ cask: Cask) {
self.casks.append(cask)
}

func remove(_ cask: Cask) {
self.casks.removeAll(where: { $0 == cask })
self.casksMatchingSearch.removeAll(where: { $0 == cask })
}

func removeAll() {
self.casks.removeAll()
self.casksMatchingSearch.removeAll()
}

func search(query: String, diffScroreThreshold: Double = 0.2, limitResults: Int = 20) async {
guard !query.isEmpty else {
self.casksMatchingSearch = self.casks
return
}

let fuse = Fuse()
let searchResults = await fuse.search(query, in: self.casks, by: \Cask.weightedSearchProperties)

var matchedCasks: [Cask] = []

for result in searchResults {
guard result.diffScore <= diffScroreThreshold else {
break
}

guard matchedCasks.count <= limitResults else {
break
}

matchedCasks.append(self.casks[result.index])
}

self.casksMatchingSearch = matchedCasks
}

func filterSearch(by filter: ([Cask]) -> [Cask]) {
self.casksMatchingSearch = filter(casksMatchingSearch)
}

func setReserveCapacity(_ capacity: Int) {
self.casks.reserveCapacity(capacity)
self.casksMatchingSearch.reserveCapacity(capacity)
}
}
43 changes: 25 additions & 18 deletions Applite/Views/Content View/ContentView+DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,20 @@ extension ContentView {
switch selection {
case .home:
if !brokenInstall {
DownloadView(navigationSelection: $selection, searchText: $searchTextSubmitted)
DownloadView(
navigationSelection: $selection,
searchText: $searchText,
caskCollection: caskManager.allCasks
)
} else {
// Broken install
VStack(alignment: .center) {
Text(DependencyManager.brokenPathOrIstallMessage)

Button {
Task {
await loadCasks()
}
} label: {
Label("Retry load", systemImage: "arrow.clockwise.circle")
}
.controlSize(.large)
}
.frame(maxWidth: 600)
brokenInstallView
}

case .updates:
UpdateView()
UpdateView(caskCollection: caskManager.outdatedCasks)

case .installed:
InstalledView()
InstalledView(caskCollection: caskManager.installedCasks)

case .activeTasks:
ActiveTasksView()
Expand All @@ -50,4 +41,20 @@ extension ContentView {
BrewManagementView(modifyingBrew: $modifyingBrew)
}
}

var brokenInstallView: some View {
VStack(alignment: .center) {
Text(DependencyManager.brokenPathOrIstallMessage)

Button {
Task {
await loadCasks()
}
} label: {
Label("Retry load", systemImage: "arrow.clockwise.circle")
}
.controlSize(.large)
}
.frame(maxWidth: 600)
}
}
Loading

0 comments on commit 580e27f

Please sign in to comment.