diff --git a/CHANGELOG.md b/CHANGELOG.md index b01f6aa..221b216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **Unified assistant keyboard**: merge Voice and AI into one Assistant tab with tap-to-dictate, hold-to-ask-AI, a liquid-glass capsule microphone, contextual hotwords, one-row paged clipboard skills, and shared Send / undo / edit actions; delete, space, undo, and edit remain available while clipboard skills are visible. / **统一助手键盘**:将语音与 AI 合并为一个助手入口,支持轻点听写、长按问 AI、液态玻璃胶囊麦克风、情境热词、单行分页剪贴板技能,以及共用的发送 / 撤销 / 编辑操作;剪贴板技能出现时仍保留删除、空格、撤销与编辑按钮。 +- **Adaptive field action**: the assistant action now follows the focused field’s current content and Return semantics, refreshes immediately after keyboard-generated edits, and shows Send, Search, Go, Done, Next, newline, and other matching icons. / **自适应输入框动作**:助手动作现根据焦点输入框的当前内容与 Return 语义显示发送、搜索、前往、完成、下一步、换行等对应图标,并在键盘主动编辑后立即刷新。 - **Clipboard setup guidance**: replace the Skills permission wall with a compact next-step card that enables history inline, verifies paste access, hides completed steps, and shortens Clipboard settings copy. / **剪贴板设置指引**:技能页权限墙改为紧凑的下一步卡片,可直接开启历史、验证粘贴访问并隐藏已完成步骤,同时精简剪贴板设置文案。 - **AI output language**: apply the global translation target to AI answers while letting an explicit language request override it and preserving source-language structured export data. / **AI 输出语言**:AI 回答遵循全局翻译目标,但明确的语言请求优先,结构化导出数据保留源语言。 diff --git a/OSGKeyboard/Views/AssistantKeyboardUITestHarness.swift b/OSGKeyboard/Views/AssistantKeyboardUITestHarness.swift index 533b233..45cfbe1 100644 --- a/OSGKeyboard/Views/AssistantKeyboardUITestHarness.swift +++ b/OSGKeyboard/Views/AssistantKeyboardUITestHarness.swift @@ -13,6 +13,7 @@ struct AssistantKeyboardUITestHarness: View { case pending case skillFailure case skills + case search } @StateObject private var state = KeyboardState() @@ -96,8 +97,8 @@ struct AssistantKeyboardUITestHarness: View { keyboardState.aiSession.beginPreparing(utteranceID: utteranceID) keyboardState.aiSession.beginListening(utteranceID: utteranceID) } - state.sendAssistantAction = { [weak keyboardState] in - keyboardState?.assistantSendAvailable = false + state.performAssistantFieldAction = { [weak keyboardState] in + keyboardState?.assistantActionAvailable = false } state.undoLastInsertion = { [weak keyboardState] in keyboardState?.undoAvailable = false @@ -149,7 +150,7 @@ struct AssistantKeyboardUITestHarness: View { AIKeyboardView.debugPreviewSkills = nil state.undoAvailable = true state.editAvailable = true - state.assistantSendAvailable = true + state.assistantActionAvailable = true case .pending: AIKeyboardView.debugPreviewSkills = nil let utteranceID = UUID() @@ -164,7 +165,7 @@ struct AssistantKeyboardUITestHarness: View { keyboardState.aiSession.markAnswerInserted(offersSend: true) keyboardState.undoAvailable = true keyboardState.editAvailable = true - keyboardState.assistantSendAvailable = true + keyboardState.assistantActionAvailable = true } state.discardPendingAIAnswer = { [weak keyboardState] in keyboardState?.aiSession.discardReadyAnswer() @@ -176,6 +177,10 @@ struct AssistantKeyboardUITestHarness: View { AIKeyboardView.debugPreviewSkills = AIClipboardSkillCatalog.catalog state.undoAvailable = true state.editAvailable = true + case .search: + AIKeyboardView.debugPreviewSkills = nil + state.returnKeyRole = .search + state.assistantActionAvailable = true } } diff --git a/OSGKeyboardExt/KeyboardViewController.swift b/OSGKeyboardExt/KeyboardViewController.swift index 25cc87d..4d6a9d2 100644 --- a/OSGKeyboardExt/KeyboardViewController.swift +++ b/OSGKeyboardExt/KeyboardViewController.swift @@ -63,6 +63,8 @@ public final class KeyboardViewController: UIInputViewController { /// changes instead of every pass. private var lastLoggedLayoutSnapshot: String? private var cancellables = Set() + /// Coalesces host-document refreshes after mutations issued by this keyboard. + private var assistantFieldActionRefreshTask: Task? private var editHintScheduler: EditHintScheduler! private var textInserter: KeyboardTextInserter! @@ -166,6 +168,8 @@ public final class KeyboardViewController: UIInputViewController { public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + assistantFieldActionRefreshTask?.cancel() + assistantFieldActionRefreshTask = nil clipboardCapture?.keyboardWillDisappear() // Presentation-scoped hints must never survive a reused extension // controller, including an active Flow handoff. @@ -274,16 +278,16 @@ public final class KeyboardViewController: UIInputViewController { state: state, host: WhatsNewDemoDriver.HostHooks( insertText: { [weak self] text in - self?.textDocumentProxy.insertText(text) + self?.insertTextIntoDocument(text) }, deleteBackward: { [weak self] in - self?.textDocumentProxy.deleteBackward() + self?.deleteBackwardFromDocument() }, contextBeforeInput: { [weak self] in self?.textDocumentProxy.documentContextBeforeInput }, performReturn: { [weak self] in - self?.textDocumentProxy.insertText("\n") + self?.performDocumentReturn() } ) ) @@ -304,6 +308,7 @@ public final class KeyboardViewController: UIInputViewController { public override func selectionDidChange(_ textInput: UITextInput?) { super.selectionDidChange(textInput) + refreshReturnKeyRole() typingSession.synchronizeEnglishDocumentContext(caretMoved: true) textInserter?.refreshEditingAvailability() lastInputEditCoordinator?.refreshContext() @@ -357,8 +362,8 @@ public final class KeyboardViewController: UIInputViewController { editHintScheduler = EditHintScheduler(state: state) textInserter = KeyboardTextInserter( state: state, - insertText: { [weak self] text in self?.textDocumentProxy.insertText(text) }, - deleteBackward: { [weak self] in self?.textDocumentProxy.deleteBackward() }, + insertText: { [weak self] text in self?.insertTextIntoDocument(text) }, + deleteBackward: { [weak self] in self?.deleteBackwardFromDocument() }, contextBeforeInput: { [weak self] in self?.textDocumentProxy.documentContextBeforeInput }, fieldContextProvider: { [weak self] in self?.captureFieldContext() }, selectedText: { [weak self] in self?.textDocumentProxy.selectedText }, @@ -420,7 +425,7 @@ public final class KeyboardViewController: UIInputViewController { self?.textInserter.insertAIAnswer(answer) ?? false }, performReturn: { [weak self] in - self?.textDocumentProxy.insertText("\n") + self?.performDocumentReturn() }, captureInsertionFingerprint: { [weak self] in self?.captureFieldContext().deliveryFingerprint @@ -508,8 +513,8 @@ public final class KeyboardViewController: UIInputViewController { state.discardPendingAIAnswer = { [weak self] in self?.aiKeyboardCoordinator.discardPendingAnswer() } - state.sendAssistantAction = { [weak self] in - self?.aiKeyboardCoordinator.sendCurrentFieldAction() + state.performAssistantFieldAction = { [weak self] in + self?.aiKeyboardCoordinator.performCurrentFieldAction() } state.submitAIHint = { [weak self] card in self?.aiKeyboardCoordinator.submitHintCard(card) @@ -564,9 +569,9 @@ public final class KeyboardViewController: UIInputViewController { self?.configSync.persistTranslationTargetLocaleId(id) self?.aiKeyboardCoordinator.resetConversationForConfigurationChange() } - state.insertNewline = { [weak self] in self?.textDocumentProxy.insertText("\n") } - state.insertSpace = { [weak self] in self?.textDocumentProxy.insertText(" ") } - state.deleteBackward = { [weak self] in self?.textDocumentProxy.deleteBackward() } + state.insertNewline = { [weak self] in self?.insertTextIntoDocument("\n") } + state.insertSpace = { [weak self] in self?.insertTextIntoDocument(" ") } + state.deleteBackward = { [weak self] in self?.deleteBackwardFromDocument() } state.undoLastInsertion = { [weak self] in self?.textInserter.undoLastInsertion() } state.redoLastInsertion = { [weak self] in self?.textInserter.redoLastInsertion() } state.copySelection = { [weak self] in self?.textInserter.copySelection() } @@ -710,11 +715,49 @@ public final class KeyboardViewController: UIInputViewController { ) } - private func refreshReturnKeyRole() { - state.returnKeyRole = returnKeyRole(for: textDocumentProxy.returnKeyType ?? .default) - if !state.returnKeyRole.usesActionFill { - state.assistantSendAvailable = false + /// Keeps field-action UI current even when a host app does not immediately + /// echo this keyboard's own document mutation through `textDidChange`. + private func insertTextIntoDocument(_ text: String) { + guard !text.isEmpty else { return } + textDocumentProxy.insertText(text) + // A non-empty insertion makes content actions available immediately. + // The next host callback remains the authoritative correction. + refreshAssistantFieldAction(hasTextOverride: true) + } + + private func deleteBackwardFromDocument() { + textDocumentProxy.deleteBackward() + refreshAssistantFieldAction() + scheduleAssistantFieldActionRefresh() + } + + private func performDocumentReturn() { + textDocumentProxy.insertText("\n") + scheduleAssistantFieldActionRefresh() + } + + private func scheduleAssistantFieldActionRefresh() { + assistantFieldActionRefreshTask?.cancel() + assistantFieldActionRefreshTask = Task { @MainActor [weak self] in + try? await Task.sleep(for: .milliseconds(100)) + guard !Task.isCancelled, let self else { return } + self.refreshAssistantFieldAction() + self.assistantFieldActionRefreshTask = nil } + } + + private func refreshAssistantFieldAction(hasTextOverride: Bool? = nil) { + let role = returnKeyRole(for: textDocumentProxy.returnKeyType ?? .default) + state.returnKeyRole = role + state.assistantActionAvailable = role.assistantActionAvailable( + hasText: hasTextOverride ?? textDocumentProxy.hasText + ) + } + + private func refreshReturnKeyRole() { + assistantFieldActionRefreshTask?.cancel() + assistantFieldActionRefreshTask = nil + refreshAssistantFieldAction() let isSecure = textDocumentProxy.isSecureTextEntry ?? false state.setSecureTextEntry(isSecure) clipboardCapture?.secureEntryDidChange(isSecure: isSecure) @@ -883,10 +926,10 @@ public final class KeyboardViewController: UIInputViewController { state: state, typing: typingSession, onInsert: { [weak self] text in - self?.textDocumentProxy.insertText(text) + self?.insertTextIntoDocument(text) }, onDeleteBackward: { [weak self] in - self?.textDocumentProxy.deleteBackward() + self?.deleteBackwardFromDocument() } ) let host = KeyboardHostingController(rootView: root) diff --git a/OSGKeyboardExt/Services/AIKeyboardCoordinator.swift b/OSGKeyboardExt/Services/AIKeyboardCoordinator.swift index 00f812f..3282355 100644 --- a/OSGKeyboardExt/Services/AIKeyboardCoordinator.swift +++ b/OSGKeyboardExt/Services/AIKeyboardCoordinator.swift @@ -186,9 +186,8 @@ final class AIKeyboardCoordinator { requestInsertionFingerprint = nil } - func sendCurrentFieldAction() { - guard state.assistantSendAvailable else { return } - state.assistantSendAvailable = false + func performCurrentFieldAction() { + guard state.assistantActionAvailable else { return } if state.aiSession.canSend { state.aiSession.markAnswerSent() } diff --git a/OSGKeyboardExt/Services/KeyboardTextInserter.swift b/OSGKeyboardExt/Services/KeyboardTextInserter.swift index 44c1aec..7e8ca36 100644 --- a/OSGKeyboardExt/Services/KeyboardTextInserter.swift +++ b/OSGKeyboardExt/Services/KeyboardTextInserter.swift @@ -187,7 +187,6 @@ final class KeyboardTextInserter { lastInsertedText = nil state.undoAvailable = false state.editAvailable = false - state.assistantSendAvailable = false EditableInputReferenceStore.clear() OSGLog.keyboardExt.info("undo length=\(text.count, privacy: .public)") } @@ -532,7 +531,6 @@ final class KeyboardTextInserter { ) ) state.editAvailable = true - state.assistantSendAvailable = state.returnKeyRole.usesActionFill state.assistantInsertionSucceeded = true successPulseTask?.cancel() successPulseTask = Task { @MainActor [weak state] in @@ -556,7 +554,6 @@ final class KeyboardTextInserter { lastInsertedText = nil state.undoAvailable = false state.editAvailable = false - state.assistantSendAvailable = false EditableInputReferenceStore.clear() } } diff --git a/OSGKeyboardExt/Views/AIKeyboardView.swift b/OSGKeyboardExt/Views/AIKeyboardView.swift index 54eb17e..0bac905 100644 --- a/OSGKeyboardExt/Views/AIKeyboardView.swift +++ b/OSGKeyboardExt/Views/AIKeyboardView.swift @@ -15,8 +15,8 @@ struct AIKeyboardView: View { static let compactPrimaryHeight: CGFloat = 56 static let compactPrimaryWidth: CGFloat = 148 static let secondaryHeight: CGFloat = 52 - static let sendWidth: CGFloat = 132 - static let compactIPadSendWidth: CGFloat = 112 + static let fieldActionWidth: CGFloat = 132 + static let compactIPadFieldActionWidth: CGFloat = 112 static let circleSize: CGFloat = 48 static let compactIPadCircleSize: CGFloat = 44 static let sideButtonEdgeInset: CGFloat = 8 @@ -64,7 +64,7 @@ struct AIKeyboardView: View { @State private var debugSkillsDismissed = false @State private var micLongPressConsumed = false @State private var micIsHoldingForAI = false - @State private var sendConfirmationVisible = false + @State private var fieldActionConfirmationVisible = false private var palette: ThemePalette { colorScheme == .dark ? Palette.dark : Palette.light @@ -184,7 +184,7 @@ struct AIKeyboardView: View { Image(systemName: "plus") .font(.system(size: 20, weight: .semibold)) .foregroundStyle(.white) - .frame(width: Layout.sendWidth, height: 44) + .frame(width: Layout.fieldActionWidth, height: 44) .background(palette.accent, in: Capsule()) } .buttonStyle(.plain) @@ -827,7 +827,7 @@ struct AIKeyboardView: View { .allowsHitTesting(sideButtonsVisible) .accessibilityHidden(!sideButtonsVisible) - sendButton + fieldActionButton } .frame(maxWidth: Layout.actionClusterMaxWidth) .frame(maxWidth: .infinity) @@ -891,49 +891,58 @@ struct AIKeyboardView: View { } } - private var sendButton: some View { - Button(action: performSend) { - Image(systemName: sendConfirmationVisible ? "checkmark" : "paperplane.fill") + private var fieldActionButton: some View { + Button(action: performFieldAction) { + Image(systemName: fieldActionSystemImage) .font(.system(size: 20, weight: .semibold)) - .foregroundStyle(sendButtonForeground) + .foregroundStyle(fieldActionButtonForeground) .frame( - width: sendButtonWidth, + width: fieldActionButtonWidth, height: KeyboardChromeLayout.assistantActionCapsuleHeight ) - .background(sendButtonFill, in: Capsule()) + .background(fieldActionButtonFill, in: Capsule()) .contentShape(Capsule()) } .buttonStyle(.plain) - .disabled(!state.assistantSendAvailable) - .accessibilityIdentifier("assistant.send") - .accessibilityLabel(ExtL10n.text("keyboard.ai.send")) - .accessibilityHint(ExtL10n.text("keyboard.assistant.sendHint")) + .disabled(!state.assistantActionAvailable) + .accessibilityIdentifier( + "assistant.action.\(state.returnKeyRole.assistantActionIdentifier)" + ) + .accessibilityLabel(ExtL10n.text(state.returnKeyRole.titleKey)) } - private var sendButtonFill: Color { - state.assistantSendAvailable + private var fieldActionSystemImage: String { + fieldActionConfirmationVisible + ? "checkmark" + : state.returnKeyRole.assistantActionSystemImage + } + + private var fieldActionButtonFill: Color { + state.assistantActionAvailable ? NativeKeyboardKeyColors.fill(for: colorScheme) : NativeKeyboardKeyColors.pressedFill(for: colorScheme) } - private var sendButtonForeground: Color { - state.assistantSendAvailable + private var fieldActionButtonForeground: Color { + state.assistantActionAvailable ? NativeKeyboardKeyColors.text(for: colorScheme) : NativeKeyboardKeyColors.text(for: colorScheme).opacity(0.58) } - private func performSend() { - guard state.assistantSendAvailable else { return } - state.sendAssistantAction() - sendConfirmationVisible = true + private func performFieldAction() { + guard state.assistantActionAvailable else { return } + state.performAssistantFieldAction() + fieldActionConfirmationVisible = true Task { @MainActor in try? await Task.sleep(for: .milliseconds(550)) - sendConfirmationVisible = false + fieldActionConfirmationVisible = false } } - private var sendButtonWidth: CGFloat { - compactIPadLayout ? Layout.compactIPadSendWidth : Layout.sendWidth + private var fieldActionButtonWidth: CGFloat { + compactIPadLayout + ? Layout.compactIPadFieldActionWidth + : Layout.fieldActionWidth } private var lowerCircleSize: CGFloat { diff --git a/OSGKeyboardShared/Services/KeyboardState.swift b/OSGKeyboardShared/Services/KeyboardState.swift index 01ed7fd..9446b22 100644 --- a/OSGKeyboardShared/Services/KeyboardState.swift +++ b/OSGKeyboardShared/Services/KeyboardState.swift @@ -187,9 +187,9 @@ public final class KeyboardState: ObservableObject { @Published public var editSession: EditSessionState = .inactive /// AI conversation UI state for the keyboard surface. The host owns the actual messages. @Published public var aiSession: AISessionState = .inactive - /// The latest generated insertion can be submitted through the host's - /// action-style Return key (Send / Search / Done / Go). - @Published public var assistantSendAvailable: Bool = false + /// Whether the assistant's field action can invoke the host Return key. + /// Derived from the focused field's role and `UITextDocumentProxy.hasText`. + @Published public var assistantActionAvailable: Bool = false /// Brief success pulse rendered on the unified assistant microphone. @Published public var assistantInsertionSucceeded: Bool = false @Published public var editCanReplaceOriginal: Bool = false @@ -212,7 +212,6 @@ public final class KeyboardState: ObservableObject { clipboardSuggestionText = nil clipboardSuggestionChangeCount = nil clipboardOverlay = .none - assistantSendAvailable = false } // MARK: - Host-app onboarding gate @@ -257,6 +256,57 @@ public final class KeyboardState: ObservableObject { public var usesActionFill: Bool { self != .newline } + + /// SF Symbol matching the semantic Return action exposed by the host. + public var assistantActionSystemImage: String { + switch self { + case .newline: + return "arrow.turn.down.left" + case .send: + return "paperplane.fill" + case .search, .google, .yahoo: + return "magnifyingglass" + case .go, .route: + return "arrow.right.circle.fill" + case .join: + return "person.badge.plus" + case .done: + return "checkmark" + case .next, .continue: + return "arrow.right" + case .emergencyCall: + return "phone.fill" + } + } + + /// Stable suffix for UI automation and diagnostics. + public var assistantActionIdentifier: String { + switch self { + case .newline: return "newline" + case .send: return "send" + case .go: return "go" + case .search: return "search" + case .join: return "join" + case .done: return "done" + case .next: return "next" + case .continue: return "continue" + case .route: return "route" + case .google: return "google" + case .yahoo: return "yahoo" + case .emergencyCall: return "emergencyCall" + } + } + + /// Text-producing actions need content; navigation actions and newline + /// remain useful in an empty field. + public func assistantActionAvailable(hasText: Bool) -> Bool { + switch self { + case .send, .go, .search, .join, .route, .google, .yahoo: + return hasText + case .newline, .done, .next, .continue, .emergencyCall: + return true + } + } } // Action hooks — injected by the view controller at install time. @@ -276,8 +326,8 @@ public final class KeyboardState: ObservableObject { /// Explicitly inserts a retained AI result after target validation failed. public var confirmPendingAIAnswer: () -> Void = {} public var discardPendingAIAnswer: () -> Void = {} - /// Performs the host's action-style Return after generated text was inserted. - public var sendAssistantAction: () -> Void = {} + /// Performs the focused field's semantic Return action. + public var performAssistantFieldAction: () -> Void = {} /// Sends a tapped idle hint card as the AI question (skip microphone). public var submitAIHint: (AIHintCard) -> Void = { _ in } /// Sends a clipboard skill (reply / summarize / translate / export). diff --git a/OSGKeyboardTests/AssistantFieldActionTests.swift b/OSGKeyboardTests/AssistantFieldActionTests.swift new file mode 100644 index 0000000..f3402bd --- /dev/null +++ b/OSGKeyboardTests/AssistantFieldActionTests.swift @@ -0,0 +1,51 @@ +import XCTest +@testable import OSGKeyboardShared + +@MainActor +final class AssistantFieldActionTests: XCTestCase { + func testContentActionsRequireText() { + let roles: [KeyboardState.ReturnKeyRole] = [ + .send, + .go, + .search, + .join, + .route, + .google, + .yahoo + ] + + for role in roles { + XCTAssertFalse(role.assistantActionAvailable(hasText: false)) + XCTAssertTrue(role.assistantActionAvailable(hasText: true)) + } + } + + func testNavigationActionsRemainAvailableWithoutText() { + let roles: [KeyboardState.ReturnKeyRole] = [ + .newline, + .done, + .next, + .continue, + .emergencyCall + ] + + for role in roles { + XCTAssertTrue(role.assistantActionAvailable(hasText: false)) + } + } + + func testSearchRolesUseSearchSymbol() { + XCTAssertEqual( + KeyboardState.ReturnKeyRole.search.assistantActionSystemImage, + "magnifyingglass" + ) + XCTAssertEqual( + KeyboardState.ReturnKeyRole.google.assistantActionSystemImage, + "magnifyingglass" + ) + XCTAssertEqual( + KeyboardState.ReturnKeyRole.yahoo.assistantActionSystemImage, + "magnifyingglass" + ) + } +} diff --git a/OSGKeyboardUITests/AssistantKeyboardUITests.swift b/OSGKeyboardUITests/AssistantKeyboardUITests.swift index c07fa42..86aa0fa 100644 --- a/OSGKeyboardUITests/AssistantKeyboardUITests.swift +++ b/OSGKeyboardUITests/AssistantKeyboardUITests.swift @@ -24,7 +24,7 @@ final class AssistantKeyboardUITests: XCTestCase { element("assistant.mic.dictationRecording", in: app) .waitForExistence(timeout: 3) ) - XCTAssertFalse(element("assistant.send", in: app).isHittable) + XCTAssertFalse(element("assistant.action.send", in: app).isHittable) } func testIdleHintHasBalancedSpacingAndFullCapsuleHitTarget() { @@ -84,7 +84,7 @@ final class AssistantKeyboardUITests: XCTestCase { let space = requiredElement("assistant.space", in: app) let undo = requiredElement("assistant.undo", in: app) let edit = requiredElement("assistant.edit", in: app) - let send = requiredElement("assistant.send", in: app) + let send = requiredElement("assistant.action.send", in: app) XCTAssertTrue(send.isEnabled) assertNoIntersection(delete, mic) @@ -126,7 +126,7 @@ final class AssistantKeyboardUITests: XCTestCase { element("assistant.mic.idle", in: app) .waitForExistence(timeout: 3) ) - XCTAssertTrue(requiredElement("assistant.send", in: app).isEnabled) + XCTAssertTrue(requiredElement("assistant.action.send", in: app).isEnabled) XCTAssertTrue(element("assistant.undo", in: app).exists) XCTAssertTrue(element("assistant.edit", in: app).exists) } @@ -160,6 +160,14 @@ final class AssistantKeyboardUITests: XCTestCase { XCTAssertFalse(element("assistant.skills.pager", in: app).exists) } + func testSearchFieldShowsEnabledSearchAction() { + let app = launch(scenario: "search") + let search = requiredElement("assistant.action.search", in: app) + + XCTAssertTrue(search.isEnabled) + XCTAssertTrue(search.isHittable) + } + func testActionGeometrySurvivesLandscapeRotation() { let app = launch(scenario: "completed") _ = requiredElement("assistant.mic.idle", in: app) @@ -171,7 +179,7 @@ final class AssistantKeyboardUITests: XCTestCase { let space = requiredElement("assistant.space", in: app) let undo = requiredElement("assistant.undo", in: app) let edit = requiredElement("assistant.edit", in: app) - let send = requiredElement("assistant.send", in: app) + let send = requiredElement("assistant.action.send", in: app) assertNoIntersection(delete, mic) assertNoIntersection(space, mic) assertNoIntersection(undo, send) diff --git a/project.yml b/project.yml index c25d8af..ba689a3 100644 --- a/project.yml +++ b/project.yml @@ -49,7 +49,7 @@ settings: STRING_CATALOG_GENERATE_SYMBOLS: YES CLANG_CXX_LANGUAGE_STANDARD: c++17 MARKETING_VERSION: "1.8.0" - CURRENT_PROJECT_VERSION: "74" + CURRENT_PROJECT_VERSION: "75" # 签名配置来自 Signing.local.xcconfig(gitignored,不会被覆盖) # 项目级签名 xcconfig,适用于所有 target