Disable empty Assistant conversation switchers
This commit is contained in:
1 parent
2bf9c1de98
commit
cf96a3ab8d
3 files changed
+97
-2
No files matched your search
@@ -978,7 +978,9 @@ instead of independently reading placement or device capabilities. Workspace det
|
||||
retain the five application tabs beside top bars and in vertical toolbars. Bottom tabs
|
||||
stay hidden in SSH/SFTP details whenever the vertical toolbar is unavailable.
|
||||
Workspace and Assistant share a `rectangle.stack` switcher in top and vertical bars,
|
||||
with a title menu elsewhere.
|
||||
with a title menu elsewhere. When Assistant has no pinned or active conversations,
|
||||
its icon switcher stays visible but disabled, and its title has no menu. A sole current
|
||||
conversation still enables switching; archived conversations do not.
|
||||
These switchers select peers within the feature; they are separate
|
||||
from the five application tabs. Assistant library pages always retain their titles.
|
||||
Metrics uses a labeled icon menu in a vertical toolbar and a principal segmented picker
|
||||
|
||||
@@ -55,9 +55,10 @@ struct MobileAssistantView: View {
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
.accessibilityValue(conversationTitle)
|
||||
.disabled(!hasSelectableConversations)
|
||||
}
|
||||
.mobileProminentAction()
|
||||
} else if libraryRoute == nil {
|
||||
} else if libraryRoute == nil && hasSelectableConversations {
|
||||
ToolbarTitleMenu { conversationTitleMenu }
|
||||
}
|
||||
}
|
||||
@@ -157,6 +158,10 @@ struct MobileAssistantView: View {
|
||||
toolbarLayout.usesIconSwitcher && libraryRoute == nil
|
||||
}
|
||||
|
||||
private var hasSelectableConversations: Bool {
|
||||
!model.library.pinnedConversations.isEmpty || !model.library.activeConversations.isEmpty
|
||||
}
|
||||
|
||||
private var detail: some View {
|
||||
MobileAssistantConversation(
|
||||
app: app,
|
||||
|
||||
@@ -217,6 +217,94 @@ struct MobileAdaptiveLayoutTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
|
||||
func assistantSwitchersReflectAvailableConversationsAcrossToolbarLayouts(language: String) async throws {
|
||||
let app = try MobileAppModel(inMemory: true)
|
||||
let scene = MobileSceneModel(app: app)
|
||||
scene.selectedDestination = .assistant
|
||||
await scene.assistant.load()
|
||||
let conversation = scene.assistant.conversation
|
||||
conversation.composer.draft = "Keep the draft while switchers update"
|
||||
let regions = MobileRegionTestState()
|
||||
var features = MobileFeatureRegistry()
|
||||
features.register(
|
||||
MobileFeatureRegistration(id: .assistant, title: "Assistant", symbol: "sparkles") { app, scene in
|
||||
AnyView(
|
||||
MobileAssistantView(
|
||||
app: app, model: scene.assistant,
|
||||
libraryRoute: Binding(
|
||||
get: { scene.assistantLibraryRoute }, set: { scene.assistantLibraryRoute = $0 })
|
||||
)
|
||||
.environment(\.mobileToolbarLayout, regions.toolbarLayout))
|
||||
})
|
||||
try await withRoot(app: app, scene: scene, language: language, features: features, initialSizeClass: .compact) {
|
||||
controller in
|
||||
let navigation = try #require(navigation(titled: "Assistant", in: controller))
|
||||
let page = try #require(navigation.topViewController)
|
||||
for (state, enabled) in [
|
||||
("empty", false), ("current", true), ("pinned", true), ("archived", false), ("restored", true),
|
||||
] {
|
||||
switch state {
|
||||
case "current":
|
||||
conversation.appendUserTurn("The only conversation")
|
||||
scene.assistant.library.insertAndSelect(conversation)
|
||||
case "pinned": conversation.isPinned = true
|
||||
case "archived":
|
||||
conversation.isPinned = false
|
||||
conversation.archivedAt = .now
|
||||
case "restored": conversation.archivedAt = nil
|
||||
default: break
|
||||
}
|
||||
// Check a data-only update before changing toolbar placement.
|
||||
try await settle()
|
||||
try expectAssistantSwitcher(in: navigation, layout: regions.toolbarLayout, enabled: enabled)
|
||||
for (name, layout) in [
|
||||
("title", MobileToolbarLayout()),
|
||||
("top", MobileToolbarLayout(isTopBar: true)),
|
||||
("vertical", MobileToolbarLayout(verticalEdge: .trailing)),
|
||||
] {
|
||||
regions.toolbarLayout = layout
|
||||
try await settle()
|
||||
try expectAssistantSwitcher(in: navigation, layout: layout, enabled: enabled)
|
||||
#expect(navigation.topViewController === page)
|
||||
#expect(scene.assistant.conversation === conversation)
|
||||
#expect(conversation.composer.draft == "Keep the draft while switchers update")
|
||||
let newConversation = try #require(
|
||||
toolbarButton(namedOneOf: ["New Conversation", "新建对话", "新增對話"], in: navigation))
|
||||
#expect(newConversation.isEnabled)
|
||||
capture(controller, name: "assistant-switcher-\(state)-\(name)-\(language)")
|
||||
}
|
||||
if !enabled {
|
||||
let menu: UIMenu
|
||||
if #available(iOS 27.0, *) {
|
||||
menu = UIMenu(children: [try #require(page.navigationItem.additionalOverflowItems)])
|
||||
} else {
|
||||
menu = try #require(toolbarButton(namedOneOf: ["More", "更多"], in: navigation)?.menu)
|
||||
}
|
||||
let actions = try await resolvedMenuActions(menu, in: page.view)
|
||||
for names: Set<String> in [["Conversations", "对话", "對話"], ["Archived", "已归档", "已封存"]] {
|
||||
let action = try #require(actions.first { names.contains($0.title) })
|
||||
#expect(!action.attributes.contains(.disabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func expectAssistantSwitcher(
|
||||
in navigation: UINavigationController, layout: MobileToolbarLayout, enabled: Bool
|
||||
) throws {
|
||||
let page = try #require(navigation.topViewController)
|
||||
let switcher = toolbarButton(namedOneOf: ["Conversations", "对话", "對話"], in: navigation)
|
||||
if layout.usesIconSwitcher {
|
||||
#expect(try #require(switcher).isEnabled == enabled)
|
||||
} else {
|
||||
#expect(switcher == nil)
|
||||
#expect((page.navigationItem.titleMenuProvider != nil) == enabled)
|
||||
#expect(visibleLabels(in: navigation.navigationBar).contains { $0.text == page.navigationItem.title })
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
|
||||
func assistantTitleAdaptsWithoutLosingItsConversationMenuOrDrafts(language: String) async throws {
|
||||
guard UIDevice.current.userInterfaceIdiom == .pad else { return }
|
||||
|
||||
Reference in new issue
Block a user