diff --git a/LocalPackage/Sources/Model/Stores/CustomMetricsSettings.swift b/LocalPackage/Sources/Model/Stores/CustomMetricsSettings.swift index 7127d72..580822a 100644 --- a/LocalPackage/Sources/Model/Stores/CustomMetricsSettings.swift +++ b/LocalPackage/Sources/Model/Stores/CustomMetricsSettings.swift @@ -37,7 +37,7 @@ public final class CustomMetricsSettings: Composable { public var showingFileImporter: Bool public var showingConfirmationDialog: Bool public var pendingRemovalSourceID: UUID? - public var showingHelpPopover: Bool + public var showingInfoPopover: Bool public let action: (Action) async -> Void public init( @@ -47,7 +47,7 @@ public final class CustomMetricsSettings: Composable { showingFileImporter: Bool = false, showingConfirmationDialog: Bool = false, pendingRemovalSourceID: UUID? = nil, - showingHelpPopover: Bool = false, + showingInfoPopover: Bool = false, action: @escaping (Action) async -> Void = { _ in } ) { self.appStateClient = appDependencies.appStateClient @@ -60,7 +60,7 @@ public final class CustomMetricsSettings: Composable { self.showingFileImporter = showingFileImporter self.showingConfirmationDialog = showingConfirmationDialog self.pendingRemovalSourceID = pendingRemovalSourceID - self.showingHelpPopover = showingHelpPopover + self.showingInfoPopover = showingInfoPopover self.action = action } @@ -103,9 +103,6 @@ public final class CustomMetricsSettings: Composable { case .addCustomMetricsSourceButtonTapped: showingFileImporter = true - case .helpButtonTapped: - showingHelpPopover = true - case let .fileImporterResponse(.success(urls)): guard let url = urls.first else { return } do { @@ -131,6 +128,9 @@ public final class CustomMetricsSettings: Composable { case .removingCustomMetricsSourceCancelled: pendingRemovalSourceID = nil + case .infoButtonTapped: + showingInfoPopover = true + case .errorOccurred: return } @@ -154,10 +154,10 @@ public final class CustomMetricsSettings: Composable { case customMetricsSourceLinkTapped(CustomMetricsSource) case customMetricsSourceRowMoved(IndexSet, Int) case addCustomMetricsSourceButtonTapped - case helpButtonTapped case fileImporterResponse(Result<[URL], any Error>) case removingCustomMetricsSourceConfirmed case removingCustomMetricsSourceCancelled + case infoButtonTapped case errorOccurred(RCNError) } } diff --git a/LocalPackage/Sources/Model/Stores/CustomRunnerSettings.swift b/LocalPackage/Sources/Model/Stores/CustomRunnerSettings.swift index 6b66d3e..ba73068 100644 --- a/LocalPackage/Sources/Model/Stores/CustomRunnerSettings.swift +++ b/LocalPackage/Sources/Model/Stores/CustomRunnerSettings.swift @@ -42,6 +42,7 @@ public final class CustomRunnerSettings: Composable { public var previewingFrameImage: FrameImage? public var previewSpeed: Double public var showingFileImporter: Bool + public var showingInfoPopover: Bool public let action: (Action) async -> Void public var canAdd: Bool { @@ -52,6 +53,7 @@ public final class CustomRunnerSettings: Composable { _ appDependencies: AppDependencies, customRunnerBundleList: [RunnerBundle] = [], showingCustomRunnerEditorSheet: Bool = false, + showingInfoPopover: Bool = false, runnerName: String = "", isTemplate: Bool = true, frameImages: [FrameImage] = [], @@ -68,6 +70,7 @@ public final class CustomRunnerSettings: Composable { self.runnerService = .init(appDependencies) self.customRunnerBundleList = customRunnerBundleList self.showingCustomRunnerEditorSheet = showingCustomRunnerEditorSheet + self.showingInfoPopover = showingInfoPopover self.runnerName = runnerName self.isTemplate = isTemplate self.frameImages = frameImages @@ -219,7 +222,10 @@ public final class CustomRunnerSettings: Composable { } catch { logService.critical(.unknown(error)) } - + + case .infoButtonTapped: + showingInfoPopover = true + case .errorOccurred: return } @@ -263,6 +269,7 @@ public final class CustomRunnerSettings: Composable { case deleteFrameButtonTapped case fileImporterResponse(Result<[URL], any Error>) case addButtonTapped + case infoButtonTapped case errorOccurred(RCNError) } } diff --git a/LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings b/LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings index ab4d0ef..7b93f46 100644 --- a/LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings +++ b/LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings @@ -2245,6 +2245,72 @@ } } }, + "information" : { + "comment" : "A button that opens a popover with more information about custom metrics.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Informationen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Information" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Información" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Informations" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "情報" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "정보" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Информация" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thông tin" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "信息" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "資訊" + } + } + } + }, "invalidFormat" : { "comment" : "Error message when a metrics file does not follow the schema.", "isCommentAutoGenerated" : true, @@ -5610,6 +5676,72 @@ } } } + }, + "viewRunnerGallery" : { + "comment" : "A link to the runner gallery.", + "isCommentAutoGenerated" : true, + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Runner Gallery ansehen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "View Runner Gallery" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver la Runner Gallery" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voir la Runner Gallery" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Runner Galleryを見る" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Runner Gallery 보기" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Посмотреть Runner Gallery" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "Xem Runner Gallery" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看 Runner Gallery" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看 Runner Gallery" + } + } + } } }, "version" : "1.2" diff --git a/LocalPackage/Sources/UserInterface/Views/Settings/MetricsSettings/CustomMetricsSettingsSectionView.swift b/LocalPackage/Sources/UserInterface/Views/Settings/MetricsSettings/CustomMetricsSettingsSectionView.swift index 8a55c39..516c371 100644 --- a/LocalPackage/Sources/UserInterface/Views/Settings/MetricsSettings/CustomMetricsSettingsSectionView.swift +++ b/LocalPackage/Sources/UserInterface/Views/Settings/MetricsSettings/CustomMetricsSettingsSectionView.swift @@ -45,6 +45,30 @@ struct CustomMetricsSettingsSectionView: View { } } } + .confirmationDialog( + Text("removeCustomMetrics", bundle: .module), + isPresented: $store.showingConfirmationDialog, + presenting: store.pendingRemovalSourceID, + actions: { sourceID in + Button(role: .destructive) { + Task { + await store.send(.removingCustomMetricsSourceConfirmed) + } + } label: { + Text("remove", bundle: .module) + } + Button(role: .cancel) { + Task { + await store.send(.removingCustomMetricsSourceCancelled) + } + } label: { + Text("cancel", bundle: .module) + } + }, + message: { _ in + Text("customMetricsConfirmationMessage", bundle: .module) + } + ) HStack { Spacer() Button { @@ -58,15 +82,32 @@ struct CustomMetricsSettingsSectionView: View { Image(systemName: "plus") } } + .fileImporter( + isPresented: $store.showingFileImporter, + allowedContentTypes: [.json], + allowsMultipleSelection: false, + onCompletion: { result in + Task { + await store.send(.fileImporterResponse(result)) + } + } + ) + .fileDialogMessage(Text("chooseJsonFile", bundle: .module)) + .fileDialogConfirmationLabel(Text("add", bundle: .module)) Button { Task { - await store.send(.helpButtonTapped) + await store.send(.infoButtonTapped) } } label: { - Image(systemName: "info.circle") + Label { + Text("information", bundle: .module) + } icon: { + Image(systemName: "info.circle") + } + .labelStyle(.iconOnly) } .buttonStyle(.borderless) - .popover(isPresented: $store.showingHelpPopover, arrowEdge: .bottom) { + .popover(isPresented: $store.showingInfoPopover, arrowEdge: .bottom) { VStack(alignment: .leading, spacing: 4) { Text("customMetricsDescription", bundle: .module) .font(.caption) @@ -81,42 +122,6 @@ struct CustomMetricsSettingsSectionView: View { .frame(maxWidth: 360, alignment: .leading) } } - .fileImporter( - isPresented: $store.showingFileImporter, - allowedContentTypes: [.json], - allowsMultipleSelection: false, - onCompletion: { result in - Task { - await store.send(.fileImporterResponse(result)) - } - } - ) - .fileDialogMessage(Text("chooseJsonFile", bundle: .module)) - .fileDialogConfirmationLabel(Text("add", bundle: .module)) - .confirmationDialog( - Text("removeCustomMetrics", bundle: .module), - isPresented: $store.showingConfirmationDialog, - presenting: store.pendingRemovalSourceID, - actions: { sourceID in - Button(role: .destructive) { - Task { - await store.send(.removingCustomMetricsSourceConfirmed) - } - } label: { - Text("remove", bundle: .module) - } - Button(role: .cancel) { - Task { - await store.send(.removingCustomMetricsSourceCancelled) - } - } label: { - Text("cancel", bundle: .module) - } - }, - message: { _ in - Text("customMetricsConfirmationMessage", bundle: .module) - } - ) } header: { Text("customMetrics", bundle: .module) } diff --git a/LocalPackage/Sources/UserInterface/Views/Settings/RunnerSettings/CustomRunnerSettingsSectionView.swift b/LocalPackage/Sources/UserInterface/Views/Settings/RunnerSettings/CustomRunnerSettingsSectionView.swift index 5590da8..a665912 100644 --- a/LocalPackage/Sources/UserInterface/Views/Settings/RunnerSettings/CustomRunnerSettingsSectionView.swift +++ b/LocalPackage/Sources/UserInterface/Views/Settings/RunnerSettings/CustomRunnerSettingsSectionView.swift @@ -24,15 +24,6 @@ import SwiftUI struct CustomRunnerSettingsSectionView: View { @State var store: CustomRunnerSettings - private var runnerGalleryGuidance: AttributedString { - var text = AttributedString(String(localized: "runnerGalleryDescription", bundle: .module)) - if let range = text.range(of: "Runner Gallery") { - text[range].link = URL.runnerGallery - text[range].foregroundColor = NSColor.linkColor - } - return text - } - var body: some View { Section { List { @@ -70,15 +61,36 @@ struct CustomRunnerSettingsSectionView: View { } content: { CustomRunnerEditorView(store: store) } + Button { + Task { + await store.send(.infoButtonTapped) + } + } label: { + Label { + Text("information", bundle: .module) + } icon: { + Image(systemName: "info.circle") + } + .labelStyle(.iconOnly) + } + .buttonStyle(.borderless) + .popover(isPresented: $store.showingInfoPopover, arrowEdge: .bottom) { + VStack(alignment: .leading, spacing: 4) { + Text("runnerGalleryDescription", bundle: .module) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Link(destination: URL.runnerGallery) { + Text("viewRunnerGallery", bundle: .module) + .font(.caption) + } + } + .padding() + .frame(maxWidth: 360, alignment: .leading) + } } } header: { Text("customRunners", bundle: .module) - } footer: { - Text(runnerGalleryGuidance) - .multilineTextAlignment(.leading) - .foregroundStyle(.secondary) - .frame(width: 420) - .fixedSize() } .task { await store.send(.viewAppeared) diff --git a/LocalPackage/Tests/ModelTests/StoreTests/CustomMetricsSettingsTests.swift b/LocalPackage/Tests/ModelTests/StoreTests/CustomMetricsSettingsTests.swift index 99a3d50..71377d1 100644 --- a/LocalPackage/Tests/ModelTests/StoreTests/CustomMetricsSettingsTests.swift +++ b/LocalPackage/Tests/ModelTests/StoreTests/CustomMetricsSettingsTests.swift @@ -213,10 +213,10 @@ struct CustomMetricsSettingsTests { } @MainActor @Test - func send_helpButtonTapped_shows_help_popover() async { + func send_infoButtonTapped_shows_info_popover() async { let sut = CustomMetricsSettings(.testDependencies()) - await sut.send(.helpButtonTapped) - #expect(sut.showingHelpPopover == true) + await sut.send(.infoButtonTapped) + #expect(sut.showingInfoPopover == true) } @MainActor @Test diff --git a/LocalPackage/Tests/ModelTests/StoreTests/CustomRunnerSettingsTests.swift b/LocalPackage/Tests/ModelTests/StoreTests/CustomRunnerSettingsTests.swift index ed707be..8b88923 100644 --- a/LocalPackage/Tests/ModelTests/StoreTests/CustomRunnerSettingsTests.swift +++ b/LocalPackage/Tests/ModelTests/StoreTests/CustomRunnerSettingsTests.swift @@ -353,4 +353,11 @@ struct CustomRunnerSettingsTests { #expect(recorder.lock.withLock(\.self) == nil) #expect(sut.customRunnerBundleList.isEmpty) } + + @MainActor @Test + func send_infoButtonTapped_shows_info_popover() async { + let sut = CustomRunnerSettings(.testDependencies()) + await sut.send(.infoButtonTapped) + #expect(sut.showingInfoPopover == true) + } }