@@ -21,7 +21,8 @@ struct SearchInstallSection: View {
2121 @Environment ( \. colorScheme) var colorScheme
2222 @State private var searchQuery : String = " "
2323 @State private var searchType : HomebrewSearchType = . installed
24- @State private var collapsedCategories : Set < String > = [ ]
24+ @State private var installedCollapsedCategories : Set < String > = [ ]
25+ @State private var availableCollapsedCategories : Set < String > = [ ]
2526 @State private var updatingPackages : Set < String > = [ ]
2627 @State private var isUpdatingAll : Bool = false
2728 @AppStorage ( " settings.interface.scrollIndicators " ) private var scrollIndicators : Bool = false
@@ -155,11 +156,19 @@ struct SearchInstallSection: View {
155156 }
156157
157158
158- private func toggleCategoryCollapse( for category: String ) {
159- if collapsedCategories. contains ( category) {
160- collapsedCategories. remove ( category)
159+ private func toggleCategoryCollapse( for category: String , tab: HomebrewSearchType ) {
160+ if tab == . installed {
161+ if installedCollapsedCategories. contains ( category) {
162+ installedCollapsedCategories. remove ( category)
163+ } else {
164+ installedCollapsedCategories. insert ( category)
165+ }
161166 } else {
162- collapsedCategories. insert ( category)
167+ if availableCollapsedCategories. contains ( category) {
168+ availableCollapsedCategories. remove ( category)
169+ } else {
170+ availableCollapsedCategories. insert ( category)
171+ }
163172 }
164173 }
165174
@@ -276,27 +285,29 @@ struct SearchInstallSection: View {
276285 let outdatedPackages = brewManager. installedByCategory [ . outdated] ?? [ ]
277286 let filteredOutdated = searchQuery. isEmpty ? outdatedPackages : outdatedPackages. filter { matchesSearchQuery ( $0, query: searchQuery) }
278287
279- InstalledCategoryView (
280- category: . outdated,
281- packages: filteredOutdated,
282- isLoading: brewManager. isLoadingOutdated,
283- collapsed: filteredOutdated. isEmpty || collapsedCategories. contains ( " Outdated " ) ,
284- onToggle: {
285- guard !filteredOutdated. isEmpty && !brewManager. isLoadingOutdated else { return }
286- withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
287- toggleCategoryCollapse ( for: " Outdated " )
288- }
289- } ,
290- isFirst: true ,
291- onPackageSelected: onPackageSelected,
292- updatingPackages: updatingPackages,
293- brewManager: brewManager,
294- onUpdateAll: filteredOutdated. count > 1 ? {
295- updateAllOutdated ( packages: filteredOutdated)
296- } : nil ,
297- colorScheme: colorScheme,
298- showOnlyInstalledOnRequest: $showOnlyInstalledOnRequest
299- )
288+ if !filteredOutdated. isEmpty {
289+ InstalledCategoryView (
290+ category: . outdated,
291+ packages: filteredOutdated,
292+ isLoading: brewManager. isLoadingOutdated,
293+ collapsed: installedCollapsedCategories. contains ( " Outdated " ) ,
294+ onToggle: {
295+ guard !filteredOutdated. isEmpty && !brewManager. isLoadingOutdated else { return }
296+ withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
297+ toggleCategoryCollapse ( for: " Outdated " , tab: . installed)
298+ }
299+ } ,
300+ isFirst: true ,
301+ onPackageSelected: onPackageSelected,
302+ updatingPackages: updatingPackages,
303+ brewManager: brewManager,
304+ onUpdateAll: filteredOutdated. count > 1 ? {
305+ updateAllOutdated ( packages: filteredOutdated)
306+ } : nil ,
307+ colorScheme: colorScheme,
308+ showOnlyInstalledOnRequest: $showOnlyInstalledOnRequest
309+ )
310+ }
300311
301312 // Formulae category
302313 let formulaePackages = brewManager. installedByCategory [ . formulae] ?? [ ]
@@ -309,11 +320,11 @@ struct SearchInstallSection: View {
309320 category: . formulae,
310321 packages: filteredFormulae,
311322 isLoading: brewManager. isLoadingPackages,
312- collapsed: filteredFormulae . isEmpty || collapsedCategories . contains ( " Formulae " ) ,
323+ collapsed: installedCollapsedCategories . contains ( " Formulae " ) ,
313324 onToggle: {
314325 guard !filteredFormulae. isEmpty && !brewManager. isLoadingPackages else { return }
315326 withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
316- toggleCategoryCollapse ( for: " Formulae " )
327+ toggleCategoryCollapse ( for: " Formulae " , tab : . installed )
317328 }
318329 } ,
319330 isFirst: false ,
@@ -333,11 +344,11 @@ struct SearchInstallSection: View {
333344 category: . casks,
334345 packages: filteredCasks,
335346 isLoading: brewManager. isLoadingPackages,
336- collapsed: filteredCasks . isEmpty || collapsedCategories . contains ( " Casks " ) ,
347+ collapsed: installedCollapsedCategories . contains ( " Casks " ) ,
337348 onToggle: {
338349 guard !filteredCasks. isEmpty && !brewManager. isLoadingPackages else { return }
339350 withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
340- toggleCategoryCollapse ( for: " Casks " )
351+ toggleCategoryCollapse ( for: " Casks " , tab : . installed )
341352 }
342353 } ,
343354 isFirst: false ,
@@ -358,10 +369,10 @@ struct SearchInstallSection: View {
358369 AvailableCategoryView (
359370 category: . formulae,
360371 packages: filteredFormulae,
361- collapsed: collapsedCategories . contains ( " Formulae " ) ,
372+ collapsed: availableCollapsedCategories . contains ( " Formulae " ) ,
362373 onToggle: {
363374 withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
364- toggleCategoryCollapse ( for: " Formulae " )
375+ toggleCategoryCollapse ( for: " Formulae " , tab : . available )
365376 }
366377 } ,
367378 isFirst: true ,
@@ -380,10 +391,10 @@ struct SearchInstallSection: View {
380391 AvailableCategoryView (
381392 category: . casks,
382393 packages: filteredCasks,
383- collapsed: collapsedCategories . contains ( " Casks " ) ,
394+ collapsed: availableCollapsedCategories . contains ( " Casks " ) ,
384395 onToggle: {
385396 withAnimation ( . easeInOut( duration: animationEnabled ? 0.3 : 0 ) ) {
386- toggleCategoryCollapse ( for: " Casks " )
397+ toggleCategoryCollapse ( for: " Casks " , tab : . available )
387398 }
388399 } ,
389400 isFirst: false ,
@@ -730,9 +741,9 @@ struct SearchResultRowView: View {
730741 . alert ( " Install \( result. name) ? " , isPresented: $showInstallAlert) {
731742 Button ( " Cancel " , role: . cancel) { }
732743 Button ( " Install " ) {
733- Task { @ MainActor in
734- isInstalling = true
735- defer { isInstalling = false }
744+ Task {
745+ await MainActor . run { isInstalling = true }
746+ defer { Task { @ MainActor in isInstalling = false } }
736747
737748 do {
738749 try await HomebrewController . shared. installPackage ( name: result. name, cask: isCask)
@@ -753,17 +764,19 @@ struct SearchResultRowView: View {
753764 . alert ( " Update \( result. displayName ?? result. name) ? " , isPresented: $showUpdateAlert) {
754765 Button ( " Cancel " , role: . cancel) { }
755766 Button ( " Update " ) {
756- Task { @ MainActor in
757- isInstalling = true
758- defer { isInstalling = false }
767+ Task {
768+ await MainActor . run { isInstalling = true }
769+ defer { Task { @ MainActor in isInstalling = false } }
759770
760771 do {
761772 try await HomebrewController . shared. upgradePackage ( name: result. name)
762773
763774 // Remove from outdated map immediately after successful update
764775 let shortName = result. name. components ( separatedBy: " / " ) . last ?? result. name
765- brewManager. outdatedPackagesMap. removeValue ( forKey: result. name)
766- brewManager. outdatedPackagesMap. removeValue ( forKey: shortName)
776+ await MainActor . run {
777+ brewManager. outdatedPackagesMap. removeValue ( forKey: result. name)
778+ brewManager. outdatedPackagesMap. removeValue ( forKey: shortName)
779+ }
767780
768781 await brewManager. loadInstalledPackages ( )
769782
@@ -787,20 +800,22 @@ struct SearchResultRowView: View {
787800 . alert ( " Uninstall \( result. displayName ?? result. name) ? " , isPresented: $showUninstallAlert) {
788801 Button ( " Cancel " , role: . cancel) { }
789802 Button ( " Uninstall " , role: . destructive) {
790- Task { @ MainActor in
791- isUninstalling = true
792- defer { isUninstalling = false }
803+ Task {
804+ await MainActor . run { isUninstalling = true }
805+ defer { Task { @ MainActor in isUninstalling = false } }
793806
794807 do {
795808 try await HomebrewUninstaller . shared. uninstallPackage ( name: result. name, cask: isCask, zap: true )
796809
797810 // Remove from installed lists instead of full refresh
798811 let shortName = result. name. components ( separatedBy: " / " ) . last ?? result. name
799812 if isCask {
800- brewManager. installedCasks. removeAll { $0. name == result. name || $0. name == shortName }
813+ await MainActor . run {
814+ brewManager. installedCasks. removeAll { $0. name == result. name || $0. name == shortName }
815+ }
801816
802817 // Refresh AppState.sortedApps to remove uninstalled app (casks only)
803- let folderPaths = FolderSettingsManager . shared. folderPaths
818+ let folderPaths = await MainActor . run { FolderSettingsManager . shared. folderPaths }
804819
805820 // Optimized: Only flush bundle for the uninstalled app (if still exists)
806821 if let matchingApp = findAppByCask ( result. name) {
@@ -812,13 +827,19 @@ struct SearchResultRowView: View {
812827 invalidateCaskLookupCache ( )
813828 await loadAppsAsync ( folderPaths: folderPaths)
814829 } else {
815- brewManager. installedFormulae. removeAll { $0. name == result. name || $0. name == shortName }
830+ await MainActor . run {
831+ brewManager. installedFormulae. removeAll { $0. name == result. name || $0. name == shortName }
832+ }
833+ }
834+ await MainActor . run {
835+ brewManager. outdatedPackagesMap. removeValue ( forKey: result. name)
836+ brewManager. outdatedPackagesMap. removeValue ( forKey: shortName)
816837 }
817- brewManager. outdatedPackagesMap. removeValue ( forKey: result. name)
818- brewManager. outdatedPackagesMap. removeValue ( forKey: shortName)
819838
820839 // Refresh categorized view to update UI (for both casks and formulae)
821- brewManager. updateInstalledCategories ( )
840+ await MainActor . run {
841+ brewManager. updateInstalledCategories ( )
842+ }
822843 } catch {
823844 printOS ( " Error uninstalling package \( result. name) : \( error) " )
824845 }
@@ -2434,9 +2455,9 @@ struct InstallButtonSection: View {
24342455 . alert ( " Install \( packageName) ? " , isPresented: $showInstallAlert) {
24352456 Button ( " Cancel " , role: . cancel) { }
24362457 Button ( " Install " ) {
2437- Task { @ MainActor in
2438- isInstalling = true
2439- defer { isInstalling = false }
2458+ Task {
2459+ await MainActor . run { isInstalling = true }
2460+ defer { Task { @ MainActor in isInstalling = false } }
24402461
24412462 do {
24422463 try await HomebrewController . shared. installPackage ( name: packageName, cask: isCask)
0 commit comments