diff --git a/Applite.xcodeproj/project.pbxproj b/Applite.xcodeproj/project.pbxproj index a42a1ed..4792683 100644 --- a/Applite.xcodeproj/project.pbxproj +++ b/Applite.xcodeproj/project.pbxproj @@ -24,6 +24,11 @@ 413F77A52972B2E70053349A /* DependencyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F77A42972B2E70053349A /* DependencyManager.swift */; }; 413F87242D32D2B100D4BE10 /* IfritStatic in Frameworks */ = {isa = PBXBuildFile; productRef = 413F87232D32D2B100D4BE10 /* IfritStatic */; }; 413F87262D33048800D4BE10 /* InfoPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F87252D33048800D4BE10 /* InfoPopup.swift */; }; + 413F87282D33DEAD00D4BE10 /* ContentView+SearchFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F87272D33DEAD00D4BE10 /* ContentView+SearchFunctions.swift */; }; + 413F872C2D33E19800D4BE10 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F872B2D33E19800D4BE10 /* HomeView.swift */; }; + 413F872E2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F872D2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift */; }; + 413F87302D33E29A00D4BE10 /* HomeView+SortingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F872F2D33E29A00D4BE10 /* HomeView+SortingOptions.swift */; }; + 413F87322D33E30400D4BE10 /* SortingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F87312D33E30400D4BE10 /* SortingOptions.swift */; }; 414074F528DF53E80073EB22 /* AppliteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414074F428DF53E80073EB22 /* AppliteApp.swift */; }; 414074F728DF53E80073EB22 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414074F628DF53E80073EB22 /* ContentView.swift */; }; 414074F928DF53EB0073EB22 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 414074F828DF53EB0073EB22 /* Assets.xcassets */; }; @@ -80,8 +85,6 @@ 419256432D1E0A0200D9EF10 /* BrewPathSelectorView+PathOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256422D1E0A0200D9EF10 /* BrewPathSelectorView+PathOption.swift */; }; 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 */; }; - 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 */; }; 419256572D1E0E5F00D9EF10 /* DiscoverSectionView+AppRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256562D1E0E5F00D9EF10 /* DiscoverSectionView+AppRow.swift */; }; @@ -119,7 +122,6 @@ 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 */; }; - 4196C8F928F9CDF700EADDDA /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4196C8F828F9CDF700EADDDA /* DownloadView.swift */; }; 4196C8FE28F9E13600EADDDA /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4196C8FD28F9E13600EADDDA /* UpdateView.swift */; }; 4196C90028F9E1F400EADDDA /* InstalledView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4196C8FF28F9E1F400EADDDA /* InstalledView.swift */; }; 41B731392A879353008BF6B9 /* ActiveTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41B731382A879353008BF6B9 /* ActiveTasksView.swift */; }; @@ -160,6 +162,11 @@ 413E60C12BBFF98A00978F6A /* AppIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconView.swift; sourceTree = ""; }; 413F77A42972B2E70053349A /* DependencyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyManager.swift; sourceTree = ""; }; 413F87252D33048800D4BE10 /* InfoPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoPopup.swift; sourceTree = ""; }; + 413F87272D33DEAD00D4BE10 /* ContentView+SearchFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContentView+SearchFunctions.swift"; sourceTree = ""; }; + 413F872B2D33E19800D4BE10 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + 413F872D2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+NoSearchResults.swift"; sourceTree = ""; }; + 413F872F2D33E29A00D4BE10 /* HomeView+SortingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+SortingOptions.swift"; sourceTree = ""; }; + 413F87312D33E30400D4BE10 /* SortingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortingOptions.swift; sourceTree = ""; }; 414074F128DF53E80073EB22 /* Applite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Applite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 414074F428DF53E80073EB22 /* AppliteApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppliteApp.swift; sourceTree = ""; }; 414074F628DF53E80073EB22 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -216,8 +223,6 @@ 419256422D1E0A0200D9EF10 /* BrewPathSelectorView+PathOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewPathSelectorView+PathOption.swift"; sourceTree = ""; }; 419256442D1E0A7000D9EF10 /* BrewPathSelectorView+CustomPathOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewPathSelectorView+CustomPathOption.swift"; sourceTree = ""; }; 419256462D1E0B0900D9EF10 /* BrewPathSelectorView+GetPathDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrewPathSelectorView+GetPathDescription.swift"; sourceTree = ""; }; - 4192564A2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DownloadView+NoSearchResults.swift"; sourceTree = ""; }; - 4192564E2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DownloadView+SortingOptions.swift"; sourceTree = ""; }; 419256512D1E0D0500D9EF10 /* DiscoverSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverSectionView.swift; sourceTree = ""; }; 419256542D1E0E2500D9EF10 /* DiscoverSectionView+CategoryHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoverSectionView+CategoryHeader.swift"; sourceTree = ""; }; 419256562D1E0E5F00D9EF10 /* DiscoverSectionView+AppRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscoverSectionView+AppRow.swift"; sourceTree = ""; }; @@ -255,7 +260,6 @@ 419506A52964A5EF00FE5802 /* BrewPathSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPathSelectorView.swift; sourceTree = ""; }; 419506A729696A5300FE5802 /* Applite-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Applite-Info.plist"; sourceTree = SOURCE_ROOT; }; 4196C8F428F9CB2600EADDDA /* DiscoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverView.swift; sourceTree = ""; }; - 4196C8F828F9CDF700EADDDA /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = ""; }; 4196C8FD28F9E13600EADDDA /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; 4196C8FF28F9E1F400EADDDA /* InstalledView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledView.swift; sourceTree = ""; }; 4196C90128FAF57A00EADDDA /* AppliteDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppliteDebug.entitlements; sourceTree = ""; }; @@ -352,6 +356,16 @@ path = "Network Proxy"; sourceTree = ""; }; + 413F872A2D33E18800D4BE10 /* Home */ = { + isa = PBXGroup; + children = ( + 413F872B2D33E19800D4BE10 /* HomeView.swift */, + 413F872D2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift */, + 413F872F2D33E29A00D4BE10 /* HomeView+SortingOptions.swift */, + ); + path = Home; + sourceTree = ""; + }; 414074E828DF53E80073EB22 = { isa = PBXGroup; children = ( @@ -454,6 +468,7 @@ 41483CCB29101C7D00BB10C2 /* Categories */, 4104D7442A8FC53200F84F9B /* Preferences */, 419256892D22D67400D9EF10 /* SidebarItem.swift */, + 413F87312D33E30400D4BE10 /* SortingOptions.swift */, ); path = Model; sourceTree = ""; @@ -538,6 +553,7 @@ 4192563B2D1DF3C900D9EF10 /* ContentView+SidebarViews.swift */, 4192563D2D1DF3E900D9EF10 /* ContentView+DetailView.swift */, 4192563F2D1DF41300D9EF10 /* ContentView+LoadCasks.swift */, + 413F87272D33DEAD00D4BE10 /* ContentView+SearchFunctions.swift */, ); path = "Content View"; sourceTree = ""; @@ -553,16 +569,6 @@ path = "Brew Path Selector"; sourceTree = ""; }; - 419256482D1E0B6A00D9EF10 /* Download */ = { - isa = PBXGroup; - children = ( - 4196C8F828F9CDF700EADDDA /* DownloadView.swift */, - 4192564A2D1E0B9B00D9EF10 /* DownloadView+NoSearchResults.swift */, - 4192564E2D1E0C1E00D9EF10 /* DownloadView+SortingOptions.swift */, - ); - path = Download; - sourceTree = ""; - }; 419256502D1E0CE000D9EF10 /* Discover */ = { isa = PBXGroup; children = ( @@ -677,7 +683,7 @@ 4196C8F728F9CB5200EADDDA /* Detail Views */ = { isa = PBXGroup; children = ( - 419256482D1E0B6A00D9EF10 /* Download */, + 413F872A2D33E18800D4BE10 /* Home */, 419256502D1E0CE000D9EF10 /* Discover */, 4192565C2D1E153D00D9EF10 /* Update */, 4196C8FF28F9E1F400EADDDA /* InstalledView.swift */, @@ -793,6 +799,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 413F87322D33E30400D4BE10 /* SortingOptions.swift in Sources */, 4196C90028F9E1F400EADDDA /* InstalledView.swift in Sources */, 415563A42A98C54300AE2F2E /* AppdirSelectorView.swift in Sources */, 4140750528DF5FA60073EB22 /* AppView.swift in Sources */, @@ -800,10 +807,8 @@ 413F87262D33048800D4BE10 /* InfoPopup.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 */, 4166EE7028F5D4C900CE305A /* Commands.swift in Sources */, 419256312D1DF2B600D9EF10 /* SettingsView+GeneralSettings.swift in Sources */, @@ -812,15 +817,18 @@ 4104D7432A8FC52C00F84F9B /* Preferences.swift in Sources */, 419256682D1E18D100D9EF10 /* AlertManagerViewModifier.swift in Sources */, 414074F728DF53E80073EB22 /* ContentView.swift in Sources */, + 413F87302D33E29A00D4BE10 /* HomeView+SortingOptions.swift in Sources */, 419256392D1DF35C00D9EF10 /* SettingsView+Uninstaller.swift in Sources */, 419256AF2D25F68700D9EF10 /* AppDelegate.swift in Sources */, 419256472D1E0B0900D9EF10 /* BrewPathSelectorView+GetPathDescription.swift in Sources */, + 413F87282D33DEAD00D4BE10 /* ContentView+SearchFunctions.swift in Sources */, 41DF006429EAA094004EB7AE /* SendNotification.swift in Sources */, 419256622D1E15EA00D9EF10 /* UpdateView+UpdateUnavailable.swift in Sources */, 41857B752912D94A004A1894 /* CategoryView.swift in Sources */, 4196C8F528F9CB2600EADDDA /* DiscoverView.swift in Sources */, 4192561C2D1DEB7E00D9EF10 /* AppView+DownloadButton.swift in Sources */, 419256B92D26C02E00D9EF10 /* CaskInfoWindowView.swift in Sources */, + 413F872C2D33E19800D4BE10 /* HomeView.swift in Sources */, 419256232D1DF14C00D9EF10 /* SetupView+PageControllerButtons.swift in Sources */, 4125BB8A29539907000FBD25 /* PlaceholderAppView.swift in Sources */, 419256182D1DEA4400D9EF10 /* AppView+ActionsView.swift in Sources */, @@ -838,6 +846,7 @@ 4192568F2D22DC9E00D9EF10 /* FontExtension.swift in Sources */, 419506A42964A27F00FE5802 /* SetupView.swift in Sources */, 4192561E2D1DEBE700D9EF10 /* AppView+UninstallButton.swift in Sources */, + 413F872E2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift in Sources */, 4192569B2D24335900D9EF10 /* CaskTaskError.swift in Sources */, 419256B42D26B8A200D9EF10 /* CaskAdditionalInfo.swift in Sources */, 41524B99295E352200D0046A /* SettingsView.swift in Sources */, @@ -887,7 +896,6 @@ 4192562E2D1DF22500D9EF10 /* SetupView+AllSet.swift in Sources */, 4192568C2D22D7FC00D9EF10 /* AppMigrationView.swift in Sources */, 413E60B72BBAE5E000978F6A /* NetworkProxyManager.swift in Sources */, - 4196C8F928F9CDF700EADDDA /* DownloadView.swift in Sources */, 4120AB682A755B5A00F68EFE /* CheckForUpdatesView.swift in Sources */, 415135662D32C4570025DB70 /* Cask+Equtable.swift in Sources */, 415135682D32C4720025DB70 /* Cask+Hashable.swift in Sources */, diff --git a/Applite/Model/SortingOptions.swift b/Applite/Model/SortingOptions.swift new file mode 100644 index 0000000..0a1c4bc --- /dev/null +++ b/Applite/Model/SortingOptions.swift @@ -0,0 +1,24 @@ +// +// SortingOptions.swift +// Applite +// +// Created by Milán Várady on 2025.01.12. +// + +import SwiftUI + +enum SortingOptions: String, CaseIterable, Identifiable { + case mostDownloaded + case bestMatch + case aToZ + + var id: SortingOptions { self } + + var description: LocalizedStringKey { + switch self { + case .mostDownloaded: return "Most downloaded (default)" + case .bestMatch: return "Best match" + case .aToZ: return "A to Z" + } + } +} diff --git a/Applite/Views/App Views/AppGridView.swift b/Applite/Views/App Views/AppGridView.swift index dda04f0..0ccab88 100755 --- a/Applite/Views/App Views/AppGridView.swift +++ b/Applite/Views/App Views/AppGridView.swift @@ -21,17 +21,20 @@ struct AppGridView: View { ] var body: some View { - LazyVGrid(columns: columns, spacing: 20) { - if appRole == .installed { - AppliteAppView() - } - - ForEach(casks) { cask in - // Filter out self - if cask.info.token != "applite" { - AppView(cask: cask, role: appRole) + ScrollView { + LazyVGrid(columns: columns, spacing: 20) { + if appRole == .installed { + AppliteAppView() + } + + ForEach(casks) { cask in + // Filter out self + if cask.info.token != "applite" { + AppView(cask: cask, role: appRole) + } } } + .padding() } } } diff --git a/Applite/Views/Content View/ContentView+DetailView.swift b/Applite/Views/Content View/ContentView+DetailView.swift index 5b41072..500f2f5 100644 --- a/Applite/Views/Content View/ContentView+DetailView.swift +++ b/Applite/Views/Content View/ContentView+DetailView.swift @@ -13,9 +13,10 @@ extension ContentView { switch selection { case .home: if !brokenInstall { - DownloadView( + HomeView( navigationSelection: $selection, - searchText: $searchText, + searchText: $searchInput, + showSearchResults: $showSearchResults, caskCollection: caskManager.allCasks ) } else { @@ -45,7 +46,7 @@ extension ContentView { } } - var brokenInstallView: some View { + private var brokenInstallView: some View { VStack(alignment: .center) { Text(DependencyManager.brokenPathOrIstallMessage) diff --git a/Applite/Views/Content View/ContentView+SearchFunctions.swift b/Applite/Views/Content View/ContentView+SearchFunctions.swift new file mode 100644 index 0000000..40ddb38 --- /dev/null +++ b/Applite/Views/Content View/ContentView+SearchFunctions.swift @@ -0,0 +1,39 @@ +// +// ContentView+SearchFunctions.swift +// Applite +// +// Created by Milán Várady on 2025.01.12. +// + +import SwiftUI + +extension ContentView { + func searchAndSort() async { + await caskManager.allCasks.search(query: searchInput, diffScroreThreshold: 0.3, limitResults: 25) + if hideUnpopularApps { await filterUnpopular() } + await sortCasks(ignoreBestMatch: true) + } + + func filterUnpopular(threshold: Int = 500) async { + caskManager.allCasks.filterSearch { casks in + casks.filter { $0.downloadsIn365days > threshold } + } + } + + func sortCasks(ignoreBestMatch: Bool) async { + switch sortBy { + case .bestMatch: + if !ignoreBestMatch { + await caskManager.allCasks.search(query: searchInput) + } + case .aToZ: + caskManager.allCasks.filterSearch { casks in + casks.sorted { $0.info.name < $1.info.name } + } + case .mostDownloaded: + caskManager.allCasks.filterSearch { casks in + casks.sorted { $0.downloadsIn365days > $1.downloadsIn365days } + } + } + } +} diff --git a/Applite/Views/Content View/ContentView.swift b/Applite/Views/Content View/ContentView.swift index 5f7cfd6..e6fe1bf 100755 --- a/Applite/Views/Content View/ContentView.swift +++ b/Applite/Views/Content View/ContentView.swift @@ -14,17 +14,21 @@ struct ContentView: View { /// Currently selected tab in the sidebar @State var selection: SidebarItem = .home - /// App search query - @State var searchText = "" - @State var searchInput = "" - @StateObject var loadAlert = AlertManager() @State var brokenInstall = false /// If true the sidebar is disabled @State var modifyingBrew = false - + + /// App search query + @State var searchInput = "" + @State var showSearchResults = false + + // Sorting options + @AppStorage("searchSortOption") var sortBy = SortingOptions.mostDownloaded + @AppStorage("hideUnpopularApps") var hideUnpopularApps = false + let logger = Logger() var body: some View { @@ -38,20 +42,42 @@ struct ContentView: View { .task { await loadCasks() } - // App search + // MARK: - Search .searchable(text: $searchInput, placement: .sidebar) // Submit search .onSubmit(of: .search) { - if !searchInput.isEmpty && selection != .home { - selection = .home - } + Task { + await searchAndSort() + + if !searchInput.isEmpty { + showSearchResults = true - searchText = searchInput + if selection != .home { + selection = .home + } + } + } } // Clear search .onChange(of: searchInput) { newValue in - if newValue.isEmpty { - searchText.removeAll() + // Limit search characters + searchInput = String(searchInput.prefix(30)) + + if searchInput.isEmpty { + showSearchResults = false + } + } + // Apply sorting options + .task(id: sortBy) { + // Refilter if sorting options change + await sortCasks(ignoreBestMatch: false) + } + // Apply filter option + .task(id: hideUnpopularApps) { + if hideUnpopularApps { + await filterUnpopular() + } else { + await caskManager.allCasks.search(query: searchInput) } } // Load failure alert diff --git a/Applite/Views/Detail Views/ActiveTasksView.swift b/Applite/Views/Detail Views/ActiveTasksView.swift index 71ca252..5b3c867 100644 --- a/Applite/Views/Detail Views/ActiveTasksView.swift +++ b/Applite/Views/Detail Views/ActiveTasksView.swift @@ -11,19 +11,17 @@ struct ActiveTasksView: View { @EnvironmentObject var caskManager: CaskManager var body: some View { - ScrollView { - VStack { - if caskManager.activeTasks.isEmpty { - Text("No Active Tasks", comment: "No active tasks available message") - .font(.title) - } else { - AppGridView(casks: caskManager.activeTasks.map { $0.cask }, appRole: .update) - } - - Spacer() + VStack { + if caskManager.activeTasks.isEmpty { + Text("No Active Tasks", comment: "No active tasks available message") + .font(.title) + } else { + AppGridView(casks: caskManager.activeTasks.map { $0.cask }, appRole: .update) } - .padding() + + Spacer() } + .padding() } } diff --git a/Applite/Views/Detail Views/App Migration/AppMigrationView.swift b/Applite/Views/Detail Views/App Migration/AppMigrationView.swift index 4b87285..d6f866b 100644 --- a/Applite/Views/Detail Views/App Migration/AppMigrationView.swift +++ b/Applite/Views/Detail Views/App Migration/AppMigrationView.swift @@ -30,6 +30,7 @@ struct AppMigrationView: View { Spacer() } .frame(maxWidth: width) + .padding() } } diff --git a/Applite/Views/Detail Views/CategoryView.swift b/Applite/Views/Detail Views/CategoryView.swift index 6bb3990..b0b23b1 100755 --- a/Applite/Views/Detail Views/CategoryView.swift +++ b/Applite/Views/Detail Views/CategoryView.swift @@ -24,10 +24,7 @@ struct CategoryView: View { .padding() // Apps - ScrollView { - AppGridView(casks: category.casks, appRole: .installAndManage) - .padding() - } + AppGridView(casks: category.casks, appRole: .installAndManage) } } } diff --git a/Applite/Views/Detail Views/Discover/DiscoverView.swift b/Applite/Views/Detail Views/Discover/DiscoverView.swift index e300b6f..0e24dbb 100755 --- a/Applite/Views/Detail Views/Discover/DiscoverView.swift +++ b/Applite/Views/Detail Views/Discover/DiscoverView.swift @@ -15,19 +15,21 @@ struct DiscoverView: View { @State var currentPage: Float = 0 var body: some View { - LazyVStack(alignment: .leading) { - Text("Discover", comment: "Discover view title") - .font(.appliteLargeTitle) - .padding(.bottom) - - ForEach(caskManager.categories) { category in - DiscoverSectionView(category: category, navigationSelection: $navigationSelection) - - Divider() - .padding(.vertical, 20) + ScrollView { + LazyVStack(alignment: .leading) { + Text("Discover", comment: "Discover view title") + .font(.appliteLargeTitle) + .padding(.bottom) + + ForEach(caskManager.categories) { category in + DiscoverSectionView(category: category, navigationSelection: $navigationSelection) + + Divider() + .padding(.vertical, 20) + } } + .padding() } - .padding() } } diff --git a/Applite/Views/Detail Views/Download/DownloadView+NoSearchResults.swift b/Applite/Views/Detail Views/Download/DownloadView+NoSearchResults.swift deleted file mode 100644 index 2af3198..0000000 --- a/Applite/Views/Detail Views/Download/DownloadView+NoSearchResults.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// DownloadView+NoSearchResults.swift -// Applite -// -// Created by Milán Várady on 2024.12.26. -// - -import SwiftUI - -extension DownloadView { - var noSearchResults: some View { - VStack { - HStack { - Image(systemName: "magnifyingglass") - .font(.appliteMediumTitle) - - Text( - "\"\(searchText)\" didn't match any app. Either it's not available in the Homebrew catalog or you misspelled it.", - comment: "Empty search results message" - ) - .font(.system(size: 20)) - } - - .padding(.bottom) - - // Turn of filtering - if hideUnpopularApps { - Button { - hideUnpopularApps = false - } label: { - Label("Turn off few downloads filter", systemImage: "slider.horizontal.2.square.on.square") - } - .controlSize(.large) - .help("Apps with few downloads are hidden, consider turning off this filter") - } - } - } -} diff --git a/Applite/Views/Detail Views/Download/DownloadView+SortingOptions.swift b/Applite/Views/Detail Views/Download/DownloadView+SortingOptions.swift deleted file mode 100644 index 196246a..0000000 --- a/Applite/Views/Detail Views/Download/DownloadView+SortingOptions.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// DownloadView+SortingOptions.swift -// Applite -// -// Created by Milán Várady on 2024.12.26. -// - -import SwiftUI - -extension DownloadView { - var sortingOptions: some ToolbarContent { - ToolbarItem { - Menu { - Picker("Sort By", selection: $sortBy) { - ForEach(SortingOptions.allCases) { option in - Text(option.description) - .tag(option) - } - } - .pickerStyle(.inline) - - Toggle(isOn: $hideUnpopularApps) { - Text("Hide apps with few downloads", comment: "Few downloads search filter") - } - } label: { - Label("Search Sorting Options", systemImage: "slider.horizontal.3") - .labelStyle(.titleAndIcon) - } - } - } -} diff --git a/Applite/Views/Detail Views/Download/DownloadView.swift b/Applite/Views/Detail Views/Download/DownloadView.swift deleted file mode 100755 index 900be9c..0000000 --- a/Applite/Views/Detail Views/Download/DownloadView.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// DownloadView.swift -// Applite -// -// Created by Milán Várady on 2022. 10. 14.. -// - -import SwiftUI -import DebouncedOnChange - -/// Download section. Either dispays the `DiscoverView` or search results -struct DownloadView: View { - @Binding var navigationSelection: SidebarItem - @Binding var searchText: String - @ObservedObject var caskCollection: SearchableCaskCollection - - @State var searchInProgress = false - - // Sorting options - @AppStorage("searchSortOption") var sortBy = SortingOptions.mostDownloaded - @AppStorage("hideUnpopularApps") var hideUnpopularApps = false - - enum SortingOptions: String, CaseIterable, Identifiable { - case mostDownloaded - case bestMatch - case aToZ - - var id: SortingOptions { self } - - var description: LocalizedStringKey { - switch self { - case .mostDownloaded: return "Most downloaded (default)" - case .bestMatch: return "Best match" - case .aToZ: return "A to Z" - } - } - } - - var body: some View { - ScrollView { - if searchText.isEmpty { - DiscoverView(navigationSelection: $navigationSelection) - } else { - if searchInProgress { - ProgressView("Searching...") - .padding(.top, 60) - } else { - AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .installAndManage) - .padding() - - // If search result is empty - if caskCollection.casksMatchingSearch.isEmpty { - noSearchResults - .frame(maxWidth: 800) - .padding() - } - } - } - } - .task { - if !searchText.isEmpty { - await searchAndSort() - } - } - .task(id: searchText) { - await searchAndSort() - } - // Apply sorting options - .task(id: sortBy) { - // Refilter if sorting options change - await sortCasks(ignoreBestMatch: false) - } - // Apply filter option - .task(id: hideUnpopularApps) { - if hideUnpopularApps { - await filterUnpopular() - } else { - await caskCollection.search(query: searchText) - } - } - .toolbar { - sortingOptions - } - } - - private func searchAndSort() async { - searchInProgress = true - - await caskCollection.search(query: searchText, diffScroreThreshold: 0.3, limitResults: 25) - if hideUnpopularApps { await filterUnpopular() } - await sortCasks(ignoreBestMatch: true) - - searchInProgress = false - } - - private func filterUnpopular(threshold: Int = 500) async { - caskCollection.filterSearch { casks in - casks.filter { $0.downloadsIn365days > threshold } - } - } - - private func sortCasks(ignoreBestMatch: Bool) async { - switch sortBy { - case .bestMatch: - if !ignoreBestMatch { - await caskCollection.search(query: searchText) - } - case .aToZ: - caskCollection.filterSearch { casks in - casks.sorted { $0.info.name < $1.info.name } - } - case .mostDownloaded: - caskCollection.filterSearch { casks in - casks.sorted { $0.downloadsIn365days > $1.downloadsIn365days } - } - } - } -} - -struct DownloadView_Previews: PreviewProvider { - static var previews: some View { - DownloadView( - navigationSelection: .constant(.home), - searchText: .constant(""), - caskCollection: .init(casks: Array(repeating: .dummy, count: 8)) - ) - } -} diff --git a/Applite/Views/Detail Views/Home/HomeView+NoSearchResults.swift b/Applite/Views/Detail Views/Home/HomeView+NoSearchResults.swift new file mode 100644 index 0000000..2a740b6 --- /dev/null +++ b/Applite/Views/Detail Views/Home/HomeView+NoSearchResults.swift @@ -0,0 +1,49 @@ +// +// HomeView+NoSearchResults.swift +// Applite +// +// Created by Milán Várady on 2025.01.12. +// + +import SwiftUI + +extension HomeView { + struct NoSearchResults: View { + @Binding var searchText: String + @AppStorage("hideUnpopularApps") var hideUnpopularApps = false + + var body: some View { + VStack { + VStack { + HStack { + Image(systemName: "magnifyingglass") + .font(.appliteMediumTitle) + + Text( + "\"\(searchText)\" didn't match any app. Either it's not available in the Homebrew catalog or you misspelled it.", + comment: "Empty search results message" + ) + .font(.system(size: 20)) + } + + .padding(.bottom) + + // Turn of filtering + if hideUnpopularApps { + Button { + hideUnpopularApps = false + } label: { + Label("Turn off few downloads filter", systemImage: "slider.horizontal.2.square.on.square") + } + .controlSize(.large) + .help("Apps with few downloads are hidden, consider turning off this filter") + } + } + .padding(.vertical, 50) + + Spacer() + } + .frame(maxWidth: 600) + } + } +} diff --git a/Applite/Views/Detail Views/Home/HomeView+SortingOptions.swift b/Applite/Views/Detail Views/Home/HomeView+SortingOptions.swift new file mode 100644 index 0000000..c105797 --- /dev/null +++ b/Applite/Views/Detail Views/Home/HomeView+SortingOptions.swift @@ -0,0 +1,37 @@ +// +// HomeView+SortingOptions.swift +// Applite +// +// Created by Milán Várady on 2025.01.12. +// + +import SwiftUI + +extension HomeView { + struct SortingOptionsToolbar: ToolbarContent { + // Sorting options + @AppStorage("searchSortOption") var sortBy = SortingOptions.mostDownloaded + @AppStorage("hideUnpopularApps") var hideUnpopularApps = false + + var body: some ToolbarContent { + ToolbarItem { + Menu { + Picker("Sort By", selection: $sortBy) { + ForEach(SortingOptions.allCases) { option in + Text(option.description) + .tag(option) + } + } + .pickerStyle(.inline) + + Toggle(isOn: $hideUnpopularApps) { + Text("Hide apps with few downloads", comment: "Few downloads search filter") + } + } label: { + Label("Search Sorting Options", systemImage: "slider.horizontal.3") + .labelStyle(.titleAndIcon) + } + } + } + } +} diff --git a/Applite/Views/Detail Views/Home/HomeView.swift b/Applite/Views/Detail Views/Home/HomeView.swift new file mode 100644 index 0000000..3044a3d --- /dev/null +++ b/Applite/Views/Detail Views/Home/HomeView.swift @@ -0,0 +1,32 @@ +// +// HomeView.swift +// Applite +// +// Created by Milán Várady on 2025.01.12. +// + +import SwiftUI + +struct HomeView: View { + @Binding var navigationSelection: SidebarItem + @Binding var searchText: String + @Binding var showSearchResults: Bool + @ObservedObject var caskCollection: SearchableCaskCollection + + var body: some View { + VStack { + if showSearchResults { + if caskCollection.casksMatchingSearch.isEmpty { + NoSearchResults(searchText: $searchText) + } else { + AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .installAndManage) + } + } else { + DiscoverView(navigationSelection: $navigationSelection) + } + } + .toolbar { + SortingOptionsToolbar() + } + } +} diff --git a/Applite/Views/Detail Views/InstalledView.swift b/Applite/Views/Detail Views/InstalledView.swift index 01d45c3..c336d83 100755 --- a/Applite/Views/Detail Views/InstalledView.swift +++ b/Applite/Views/Detail Views/InstalledView.swift @@ -16,10 +16,7 @@ struct InstalledView: View { var body: some View { VStack { - ScrollView { - AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .installed) - .padding() - } + AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .installed) } .searchable(text: $searchText, placement: .toolbar) .task(id: searchText, debounceTime: .seconds(0.2)) { diff --git a/Applite/Views/Detail Views/TapView.swift b/Applite/Views/Detail Views/TapView.swift index cbd67c3..e1508dc 100644 --- a/Applite/Views/Detail Views/TapView.swift +++ b/Applite/Views/Detail Views/TapView.swift @@ -23,9 +23,7 @@ struct TapView: View { .padding() // Apps - ScrollView { - TapAppGridView(caskCollection: tap.caskCollection) - } + TapAppGridView(caskCollection: tap.caskCollection) } } @@ -35,7 +33,6 @@ struct TapView: View { var body: some View { AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .installAndManage) - .padding() .searchable(text: $searchText, placement: .toolbar) .task(id: searchText, debounceTime: .seconds(0.2)) { await caskCollection.search(query: searchText) diff --git a/Applite/Views/Detail Views/Update/UpdateView+UpdateUnavailable.swift b/Applite/Views/Detail Views/Update/UpdateView+UpdateUnavailable.swift index d4708b2..ee1d8a1 100644 --- a/Applite/Views/Detail Views/Update/UpdateView+UpdateUnavailable.swift +++ b/Applite/Views/Detail Views/Update/UpdateView+UpdateUnavailable.swift @@ -10,12 +10,8 @@ import SwiftUI extension UpdateView { var updateUnavailable: some View { VStack { - Spacer() - Text("No Updates Available", comment: "Update view no updates available") .font(.title) - - Spacer() } } } diff --git a/Applite/Views/Detail Views/Update/UpdateView.swift b/Applite/Views/Detail Views/Update/UpdateView.swift index faa6200..e320c58 100755 --- a/Applite/Views/Detail Views/Update/UpdateView.swift +++ b/Applite/Views/Detail Views/Update/UpdateView.swift @@ -22,15 +22,22 @@ struct UpdateView: View { @StateObject var loadAlert = AlertManager() var body: some View { - ScrollView { - // App grid - AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .update) - .padding() - - if caskCollection.casksMatchingSearch.count > 1 { - updateAllButton - } else { + VStack { + if caskCollection.casks.isEmpty { updateUnavailable + .padding(.vertical) + + Spacer() + } else { + // App grid + AppGridView(casks: caskCollection.casksMatchingSearch, appRole: .update) + .overlay(alignment: .bottom) { + if caskCollection.casksMatchingSearch.count > 1 { + updateAllButton + .shadow(radius: 8) + .padding(.vertical) + } + } } } .searchable(text: $searchText, placement: .toolbar) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 9356cd4..cf4061e 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -4403,17 +4403,6 @@ } } }, - "Searching..." : { - "comment" : "App search in progress", - "localizations" : { - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keresés…" - } - } - } - }, "See All" : { "comment" : "See all apps in category button", "localizations" : {