From 2b2fc15da30adb047c32871d3fe0feb70ca9a377 Mon Sep 17 00:00:00 2001 From: Gabriel Schantz Date: Mon, 3 Aug 2026 15:37:35 -0400 Subject: [PATCH] feat(mac): added tabview and webview --- mac/Config/Config/HelpView.swift | 34 +++++++++++ mac/Config/Config/MainConfigView.swift | 78 ++++++++++++++++--------- mac/Config/Config/PackageInfoView.swift | 1 + mac/Config/Config/PackageRowView.swift | 24 +++++--- 4 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 mac/Config/Config/HelpView.swift diff --git a/mac/Config/Config/HelpView.swift b/mac/Config/Config/HelpView.swift new file mode 100644 index 0000000000..f30e46014c --- /dev/null +++ b/mac/Config/Config/HelpView.swift @@ -0,0 +1,34 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Gabriel Schantz on 2026-08-03 + * + * Webview used to show help for Keyman keyboards + */ +import Foundation + +import SwiftUI +import WebKit +import KeymanSettings + +public struct HelpView: NSViewRepresentable { + @EnvironmentObject var settings: SettingsContainer + + let helpFileURL: URL + + // create the AppKit view instance + public func makeNSView(context: Context) -> WKWebView { + let webView = WKWebView() + return webView + } + + // update the view when SwiftUI state changes + public func updateNSView(_ nsView: WKWebView, context: Context) { + let request = URLRequest(url: helpFileURL) + + // only load the request if it's not already loading/loaded to prevent infinite loops + if nsView.url != helpFileURL { + nsView.load(request) + } + } +} diff --git a/mac/Config/Config/MainConfigView.swift b/mac/Config/Config/MainConfigView.swift index 427ea2c498..2a24491853 100644 --- a/mac/Config/Config/MainConfigView.swift +++ b/mac/Config/Config/MainConfigView.swift @@ -4,8 +4,7 @@ * Created by Gabriel Schantz on 2026-06-29 * * Main view used for configuring Keyman - * FEAT/MAC/CONFIG-WINDOW TODO: Finish writing file summary - * FEAT/MAC/CONFIG-WINDOW TODO: Set width and height for window + * FEAT/MAC/CONFIG-WINDOW TODO: Set default width and height for window */ import SwiftUI @@ -17,43 +16,68 @@ struct MainConfigView: View { // visibilty state for the add package sheet @State private var isShowingSheet = false // used to identify the expanded KeymanPackage id - // both single and multi package views share the same state variable so only single disclosure group is expanded at once + // both single and multi package views share the same state variable so only a single disclosure group is expanded at once @State private var expandedPackageID: UUID? = nil + @State private var selectedTab = 0 + @State private var packageSelectedForHelpUrl: URL? = nil + + /** + * Assigns packageSelectedForHelpUrl the url argument and changes the selected tab to the help tab + */ + public func showHelpTab(for url: URL) { + packageSelectedForHelpUrl = url + selectedTab = 1 + } var body: some View { - VStack { - // the add keyboard button - LabelButtonView( - action: { isShowingSheet = true }, - label: "Add Keyboard", - systemImage: "plus", - font: .title2 - ) - .clipShape(.capsule) - .padding([.top, .leading, .trailing]) - // binds the visibility state to the sheet builder - .sheet(isPresented: $isShowingSheet) { - InstallKeyboardView() - .frame(width: 960, height: 390) - // FEAT/MAC/CONFIG-WINDOW TODO: Make width and height percentages - } - ScrollView { - VStack { + TabView (selection: $selectedTab) { + VStack { + // the add keyboard button + LabelButtonView( + action: { isShowingSheet = true }, + label: "Add Keyboard", + systemImage: "plus", + font: .title2 + ) + .clipShape(.capsule) + .padding([.top, .leading, .trailing]) + // binds the visibility state to the sheet builder + .sheet(isPresented: $isShowingSheet) { + InstallKeyboardView() + .frame(width: 960, height: 390) + // FEAT/MAC/CONFIG-WINDOW TODO: Make width and height percentages + } + + Form { // the view for single keyboard packages - PackageRowView(packages: settings.singleKeyboardPackages, isSingleKeyboardPackage: true, expandedPackageID: $expandedPackageID) + PackageRowView(packages: settings.singleKeyboardPackages, isSingleKeyboardPackage: true, expandedPackageID: $expandedPackageID, showHelpTab: { url in + showHelpTab(for: url)}) // the view for multi keyboard packages - PackageRowView(packages: settings.multiKeyboardPackages, isSingleKeyboardPackage: false, expandedPackageID: $expandedPackageID) + PackageRowView(packages: settings.multiKeyboardPackages, isSingleKeyboardPackage: false, expandedPackageID: $expandedPackageID, showHelpTab: { url in + showHelpTab(for: url) }) } - .padding() - .background(.quinary) - .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + .formStyle(.grouped) // the Spacer pushes the contents of the VStack to the top of the VStack Spacer() } + .padding([.leading, .trailing, .bottom]) + .tabItem { Text("Keyboards") } + .tag(0) + + if let url = packageSelectedForHelpUrl { + HelpView(helpFileURL: url) + .padding() + .tabItem { Text("Help") } + .tag(1) + } else { + Text("Help not available.") + .font(.title) + .tabItem { Text("Help") } + .tag(1) + } } - .padding([.leading, .trailing, .bottom]) } } diff --git a/mac/Config/Config/PackageInfoView.swift b/mac/Config/Config/PackageInfoView.swift index 28638f5e46..8c3d19bdcd 100644 --- a/mac/Config/Config/PackageInfoView.swift +++ b/mac/Config/Config/PackageInfoView.swift @@ -39,6 +39,7 @@ public struct PackageInfoView: View { VStack (alignment: .leading) { // the text-based package properties presented in a grid Grid(horizontalSpacing: 10, verticalSpacing: 5) { + // the package version GridRow { Text("Package Version:").bold() diff --git a/mac/Config/Config/PackageRowView.swift b/mac/Config/Config/PackageRowView.swift index aa1d813a83..4d5e74ac20 100644 --- a/mac/Config/Config/PackageRowView.swift +++ b/mac/Config/Config/PackageRowView.swift @@ -20,14 +20,18 @@ public struct PackageRowView: View { // settings.singleKeyboardPackages or settings.multiKeyboardPackages let packages: [KeymanPackage] + // a boolean for weather or not a package contains multiple keyboards let isSingleKeyboardPackage: Bool // binded to the shared state variable in the parent view @Binding var expandedPackageID: UUID? + // closure passed from the parent view + let showHelpTab: (URL) -> Void - init(packages: [KeymanPackage], isSingleKeyboardPackage: Bool, expandedPackageID: Binding) { + init(packages: [KeymanPackage], isSingleKeyboardPackage: Bool, expandedPackageID: Binding, showHelpTab: @escaping (URL) -> Void) { self.packages = packages self.isSingleKeyboardPackage = isSingleKeyboardPackage self._expandedPackageID = expandedPackageID + self.showHelpTab = showHelpTab } /** @@ -57,6 +61,16 @@ public struct PackageRowView: View { 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() @@ -69,13 +83,7 @@ public struct PackageRowView: View { .gridColumnAlignment(.leading) } - // see keyboard help button - IconButtonView( - action: { print("Show keyboard help") }, - systemImage: "questionmark.circle", - font: .title2, - helpText: "Show keyboard help" - ) + } // if the package contains multiple keyboards shows an HStack with the keyboard name and toggle button for each keyboard in the package