-
-
Notifications
You must be signed in to change notification settings - Fork 275
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
[Improvement] Async bottle loading #574
base: main
Are you sure you want to change the base?
Changes from all commits
fbdf086
0aedc68
bf0e8af
ee792f7
bcdae7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ import SwiftUI | |
import WhiskyKit | ||
|
||
struct ProgramsView: View { | ||
let bottle: Bottle | ||
@Binding var bottle: Bottle | ||
@State var programs: [Program] = [] | ||
@State var blocklist: [URL] = [] | ||
@State private var selectedPrograms = Set<Program>() | ||
|
@@ -30,12 +30,13 @@ struct ProgramsView: View { | |
@State var resortPrograms: Bool = false | ||
@State var isExpanded: Bool = true | ||
@State var isBlocklistExpanded: Bool = false | ||
@State var isLoadingPrograms: Bool = false | ||
@Binding var reloadStartMenu: Bool | ||
@Binding var path: NavigationPath | ||
|
||
var body: some View { | ||
Form { | ||
Section("program.title", isExpanded: $isExpanded) { | ||
Section(isExpanded: $isExpanded) { | ||
List($programs, id: \.self, selection: $selectedPrograms) { $program in | ||
ProgramItemView(program: program, | ||
resortPrograms: $resortPrograms, | ||
|
@@ -47,7 +48,18 @@ struct ProgramsView: View { | |
resortPrograms.toggle() | ||
} | ||
} | ||
} header: { | ||
HStack { | ||
Text("program.title") | ||
|
||
if isLoadingPrograms { | ||
ProgressView() | ||
.scaleEffect(0.5) | ||
.padding(.vertical, -16) | ||
} | ||
} | ||
} | ||
|
||
Section("program.blocklist", isExpanded: $isBlocklistExpanded) { | ||
List($blocklist, id: \.self, selection: $selectedBlockitems) { $blockedUrl in | ||
BlocklistItemView(blockedUrl: blockedUrl, | ||
|
@@ -68,26 +80,46 @@ struct ProgramsView: View { | |
.animation(.easeInOut(duration: 0.2), value: isBlocklistExpanded) | ||
.navigationTitle("tab.programs") | ||
.onAppear { | ||
programs = bottle.updateInstalledPrograms() | ||
blocklist = bottle.settings.blocklist | ||
sortPrograms() | ||
loadPrograms() | ||
} | ||
.onChange(of: resortPrograms) { | ||
sortPrograms() | ||
reloadStartMenu.toggle() | ||
} | ||
} | ||
|
||
func loadPrograms() { | ||
if bottle.programs.isEmpty { | ||
updatePrograms() | ||
return | ||
} | ||
|
||
programs = bottle.programs | ||
blocklist = bottle.settings.blocklist | ||
sortPrograms() | ||
} | ||
|
||
private func updatePrograms() { | ||
isLoadingPrograms = true | ||
|
||
DispatchQueue(label: "whisky.lock.queue").async { | ||
programs = bottle.updateInstalledPrograms() | ||
blocklist = bottle.settings.blocklist | ||
sortPrograms() | ||
isLoadingPrograms = false | ||
} | ||
} | ||
|
||
func sortPrograms() { | ||
var favourites = programs.filter { $0.pinned } | ||
var nonFavourites = programs.filter { !$0.pinned } | ||
favourites = favourites.sorted { $0.name < $1.name } | ||
nonFavourites = nonFavourites.sorted { $0.name < $1.name } | ||
programs.removeAll() | ||
programs.append(contentsOf: favourites) | ||
programs.append(contentsOf: nonFavourites) | ||
private func sortPrograms() { | ||
DispatchQueue(label: "whisky.lock.queue").async { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, we don't use GCD in Whisky, using structured concurrency would be preferable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opted for using a DispatchQueue in this specific scenario to ensure bottle loading operations get queued up in the same thread. Structured concurrency would not have this guarantee (at least as far as I know), and in general I avoided going down the route of rewriting Bottle to be an Actor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actor based approach might be preferred here. |
||
var favourites = programs.filter { $0.pinned } | ||
var nonFavourites = programs.filter { !$0.pinned } | ||
favourites = favourites.sorted { $0.name < $1.name } | ||
nonFavourites = nonFavourites.sorted { $0.name < $1.name } | ||
programs.removeAll() | ||
programs.append(contentsOf: favourites) | ||
programs.append(contentsOf: nonFavourites) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -113,8 +145,11 @@ struct ProgramItemView: View { | |
.buttonStyle(.plain) | ||
.foregroundColor(isPinned ? .accentColor : .secondary) | ||
.opacity(isPinned ? 1 : showButtons ? 1 : 0) | ||
|
||
Text(program.name) | ||
|
||
Spacer() | ||
|
||
if showButtons { | ||
if let peFile = program.peFile, | ||
let archString = peFile.architecture.toString() { | ||
|
@@ -126,6 +161,7 @@ struct ProgramItemView: View { | |
.stroke(.secondary) | ||
) | ||
} | ||
|
||
Button { | ||
path.append(program) | ||
} label: { | ||
|
@@ -134,6 +170,7 @@ struct ProgramItemView: View { | |
.buttonStyle(.plain) | ||
.foregroundStyle(.secondary) | ||
.help("program.config") | ||
|
||
Button { | ||
Task { | ||
await program.run() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this ZStack is needed. Why block the user from accessing loaded pins until they've all loaded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I see what you mean - yeah, maybe a less fancy but less obstructive way of displaying the indicator would be better indeed. I'll change that, thanks for the feedback :)