Files

155 lines
4.8 KiB
Swift

import GRDBQuery
import SwiftUI
/// The main application view
struct AppView: View {
/// Write access to the database
@Environment(\.appDatabase) private var appDatabase
/// The `players` property is automatically updated when the database changes
@Query(PlayerRequest(ordering: .byScore)) private var players: [Player]
/// We'll need to leave edit mode in several occasions.
@State private var editMode = EditMode.inactive
/// Tracks the presentation of the player creation sheet.
@State private var newPlayerIsPresented = false
// If you want to define the query on initialization, you will prefer:
//
// @Query<PlayerRequest> private var players: [Player]
//
// init(initialOrdering: PlayerRequest.Ordering) {
// _players = Query(PlayerRequest(ordering: initialOrdering))
// }
var body: some View {
NavigationView {
PlayerList(players: players)
.navigationBarTitle(Text("\(players.count) Players"))
.navigationBarItems(
leading: HStack {
EditButton()
newPlayerButton
},
trailing: ToggleOrderingButton(
ordering: $players.ordering,
willChange: {
// onChange(of: $players.wrappedValue.ordering)
// is not able to leave the editing mode during
// the animation of the list content.
// Workaround: stop editing before the ordering
// is changed, and the list content is updated.
stopEditing()
}))
.toolbar { toolbarContent }
.onChange(of: players) {
if players.isEmpty {
stopEditing()
}
}
.environment(\.editMode, $editMode)
}
}
private var toolbarContent: some ToolbarContent {
ToolbarItemGroup(placement: .bottomBar) {
Button {
// Don't stopEditing() here because this is
// performed `onChange(of: players)`
Task {
try? await appDatabase.deleteAllPlayers()
}
} label: {
Image(systemName: "trash").imageScale(.large)
}
Spacer()
Button {
stopEditing()
Task {
try? await appDatabase.refreshPlayers()
}
} label: {
Image(systemName: "arrow.clockwise").imageScale(.large)
}
Spacer()
Button {
stopEditing()
// Perform 50 refreshes in parallel
Task {
try? await withThrowingTaskGroup(of: Void.self) { group in
for _ in 0..<50 {
_ = group.addTaskUnlessCancelled {
try await appDatabase.refreshPlayers()
}
}
try await group.waitForAll()
}
}
} label: {
Image(systemName: "tornado").imageScale(.large)
}
}
}
/// The button that presents the player creation sheet.
private var newPlayerButton: some View {
Button {
stopEditing()
newPlayerIsPresented = true
} label: {
Image(systemName: "plus")
}
.accessibility(label: Text("New Player"))
.sheet(isPresented: $newPlayerIsPresented) {
PlayerCreationView()
}
}
private func stopEditing() {
withAnimation {
editMode = .inactive
}
}
}
private struct ToggleOrderingButton: View {
@Binding var ordering: PlayerRequest.Ordering
let willChange: () -> Void
var body: some View {
switch ordering {
case .byName:
Button {
willChange()
ordering = .byScore
} label: {
Label("Name", systemImage: "arrowtriangle.up.fill").labelStyle(.titleAndIcon)
}
case .byScore:
Button {
willChange()
ordering = .byName
} label: {
Label("Score", systemImage: "arrowtriangle.down.fill").labelStyle(.titleAndIcon)
}
}
}
}
// MARK: - Previews
#Preview("Empty") {
// Preview the default, empty database
AppView()
}
#Preview("Populated") {
// Preview a database of random players
AppView().appDatabase(.random())
}