mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-11 00:17:28 +00:00
Some checks are pending
Keyman Build Summary / Summarize build status checks (push) Waiting to run
promote delete package code to main view to avoid race condition
171 lines
6.6 KiB
Swift
171 lines
6.6 KiB
Swift
/*
|
|
* Keyman is copyright (C) SIL Global. MIT License.
|
|
*
|
|
* Created by Gabriel Schantz on 2026-07-27
|
|
*
|
|
* Displays an array of Keyman Packages as disclosure groups
|
|
* If the argument isSingleKeyboardPackage is true, displays information in a format suited to a single keyboard,
|
|
* otherwise displays information in a format suited to a multi-keyboard package
|
|
*/
|
|
|
|
import SwiftUI
|
|
import Combine
|
|
import KeymanSettings
|
|
|
|
public struct PackageRowView: View {
|
|
|
|
@EnvironmentObject var settings: SettingsContainer
|
|
// used to identify the selected KeymanPackage for the delete package alert
|
|
|
|
@Environment(\.colorScheme) var colorScheme
|
|
var canvasColor: Color {
|
|
colorScheme == .dark ? Color(white: 0.12) : Color(white: 0.94)
|
|
}
|
|
var cardColor: Color {
|
|
colorScheme == .dark ? Color(white: 0.20) : Color(.white)
|
|
}
|
|
|
|
// could be settings.singleKeyboardPackages or settings.multiKeyboardPackages
|
|
let packages: [KeymanPackage]
|
|
// a boolean for whether or not a package contains multiple keyboards
|
|
let isSingleKeyboardPackage: Bool
|
|
// binded to the shared state variable in the parent view
|
|
@Binding var expandedPackageID: UUID?
|
|
@Binding var idToDelete: UUID?
|
|
// closure passed from the parent view
|
|
let showHelpTab: (URL) -> Void
|
|
|
|
|
|
init(packages: [KeymanPackage], isSingleKeyboardPackage: Bool, expandedPackageID: Binding<UUID?>, idToDelete: Binding<UUID?>, showHelpTab: @escaping (URL) -> Void) {
|
|
self.packages = packages
|
|
self.isSingleKeyboardPackage = isSingleKeyboardPackage
|
|
self._expandedPackageID = expandedPackageID
|
|
self._idToDelete = idToDelete
|
|
self.showHelpTab = showHelpTab
|
|
}
|
|
|
|
public var body: some View {
|
|
ForEach(packages, id: \.id) { package in
|
|
ForEach(isSingleKeyboardPackage ? package.keyboards : package.keyboards.onlyFirst) { keyboard in
|
|
DisclosureGroup(isExpanded: isExpanded(package: package)) {
|
|
// the package info view is shown inside each disclosure group
|
|
if expandedPackageID == package.id {
|
|
PackageInfoView(package: package, showAlertFunction: { package in
|
|
idToDelete = package.id
|
|
//showDeleteAlert(for: package)
|
|
})
|
|
.transition(.move(edge: .top))
|
|
}
|
|
}
|
|
label: {
|
|
// a VStack is shown as the label for each disclosure group
|
|
VStack (alignment: .leading, spacing: 0) {
|
|
HStack {
|
|
// if the package contains one keyboard, show the keyboard name, otherwise show the package name
|
|
Text(isSingleKeyboardPackage ? keyboard.name: package.packageName)
|
|
.font(.title)
|
|
|
|
// see keyboard help button
|
|
if let url = package.helpFileUrl {
|
|
IconButtonView(
|
|
action: { showHelpTab(url) },
|
|
systemImage: "questionmark.circle",
|
|
font: .title2,
|
|
helpText: "Show keyboard help"
|
|
)
|
|
}
|
|
|
|
// the Spacer pushes the contents of the HStack to the either edge
|
|
Spacer()
|
|
|
|
// if the package contains one keyboard shows the toggle button for the keyboard
|
|
if isSingleKeyboardPackage {
|
|
Toggle("enabled", isOn: isEnabled(packageId: package.id, keyboardKey: keyboard.keyboardKey))
|
|
.controlSize(.mini)
|
|
.labelsHidden()
|
|
.toggleStyle(.switch)
|
|
.gridColumnAlignment(.leading)
|
|
}
|
|
}
|
|
|
|
// if the package contains multiple keyboards shows an HStack with the keyboard name and toggle button for each keyboard in the package
|
|
if !isSingleKeyboardPackage {
|
|
ForEach (package.keyboards) { keyboard in
|
|
HStack {
|
|
Text(keyboard.name)
|
|
.font(.title2)
|
|
.foregroundStyle(.primary)
|
|
.gridColumnAlignment(.leading)
|
|
|
|
// the Spacer pushes the other views inside the HStack to the opposite edge
|
|
Spacer()
|
|
|
|
// the toggle button for the keyboard
|
|
Toggle("enabled", isOn: isEnabled(packageId: package.id, keyboardKey: keyboard.keyboardKey))
|
|
.controlSize(.mini)
|
|
.labelsHidden()
|
|
.toggleStyle(.switch)
|
|
.gridColumnAlignment(.leading)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
// handles when the HStack is clicked by the user
|
|
.onTapGesture {
|
|
withAnimation () {
|
|
if self.expandedPackageID == package.id {
|
|
self.expandedPackageID = nil
|
|
} else {
|
|
self.expandedPackageID = package.id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listRowBackground(
|
|
Rectangle()
|
|
.fill(cardColor) // native Mac card color = Color(.controlBackgroundColor)
|
|
)
|
|
}
|
|
}
|
|
// animate changes in the package list
|
|
.animation(.easeInOut, value: packages)
|
|
.padding(.vertical, 8)
|
|
}
|
|
|
|
// the helper method to generate the custom binding for whether a package's disclosure group is expanded or not
|
|
func isExpanded(package: KeymanPackage) -> Binding<Bool> {
|
|
Binding(
|
|
// the getter renders the position of the disclosure group
|
|
get: { expandedPackageID == package.id },
|
|
// setter handles when the chevron arrow is clicked by the user
|
|
// $0 = true when disclosure group is open and $0 = false when disclosure group is closed
|
|
set: { isExpanded in
|
|
withAnimation {
|
|
expandedPackageID = isExpanded ? package.id : nil }
|
|
}
|
|
)
|
|
}
|
|
|
|
// the helper method to generate the custom binding for whether a keyboard is enabled or not
|
|
func isEnabled(packageId: UUID, keyboardKey: String) -> Binding<Bool> {
|
|
Binding(
|
|
// the getter renders the state of the toggle button based on the enabled property of the keyboard
|
|
get: { settings.isKeyboardEnabled(packageId: packageId, keyboardKey: keyboardKey) },
|
|
// the setter handles when the toggle button is clicked by the user
|
|
// $0 = true when the toggle button is on and $0 = false when the toggle button is off
|
|
set: {
|
|
settings.setKeyboardEnabled(packageId: packageId, keyboardKey: keyboardKey, enabled: $0)
|
|
settings.objectWillChange.send()
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
extension Collection {
|
|
// returns the first element of an array in an array or returns an empty array
|
|
var onlyFirst: [Element] {
|
|
guard let first = self.first else { return [] }
|
|
return [first]
|
|
}
|
|
}
|