mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 08:01:10 +00:00
* feat(ios): unify navigation on a black overlay sidebar and drop the phone tab bar * test(ios): rework navigation tests and snapshot UITests for the sidebar shell * chore(i18n): sync derived native catalogs for sidebar strings * feat(ios): reveal the sidebar beneath a push-away rounded content card * feat(ios): web-parity sidebar — agent switcher, pinned pages, sessions vocabulary, uniform content card * chore(i18n): translate sidebar and session strings across native catalogs * fix(ios): sidebar footer shows connection dot only when degraded, like the web * feat(ios): sidebar toggle top-left with edge-swipe open; card surface owns the drawer chrome * fix(ios): single sidebar refresh owner, alias-aware Home badges, RM tap-close, user pin order, drop vestigial idiom shims * feat(ios): align sidebar with the mobile prototype — agent roster, theme-following palette, web session subtitles, row badges * chore(i18n): translate agent roster and Recent strings across native catalogs * feat(ios): frosted-glass agent picker with uniform drawer insets * fix(ios): address sidebar review findings — settings toggle edge, per-agent scoping, alias-aware selection, cron paging, refresh cadence * fix(ios): clear landing CI blockers
226 lines
7.7 KiB
Swift
226 lines
7.7 KiB
Swift
import OpenClawKit
|
|
import OpenClawProtocol
|
|
import SwiftUI
|
|
|
|
extension AgentProTab {
|
|
@ViewBuilder
|
|
func destination(for route: AgentRoute) -> some View {
|
|
switch route {
|
|
case .agents:
|
|
self.agentsDestination
|
|
case .skills:
|
|
self.skillsDestination
|
|
case .instances:
|
|
self.instancesDestination
|
|
case .cron:
|
|
self.cronDestination
|
|
case .usage:
|
|
self.usageDestination
|
|
case .dreaming:
|
|
self.dreamingDestination
|
|
case .files:
|
|
self.filesDestination
|
|
}
|
|
}
|
|
|
|
var filesDestination: some View {
|
|
AgentWorkspaceFilesScreen(
|
|
agentId: self.activeAgentID,
|
|
headerSidebarAction: self.directHeaderSidebarAction(for: .files))
|
|
}
|
|
|
|
var agentsDestination: some View {
|
|
List {
|
|
Section {
|
|
if self.filteredAgents.isEmpty {
|
|
self.emptyAgentsRow
|
|
} else {
|
|
ForEach(self.filteredAgents, id: \.id) { agent in
|
|
self.agentRow(agent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.navigationTitle(self.headerTitle)
|
|
.navigationBarTitleDisplayMode(.large)
|
|
.searchable(text: self.$agentSearchText, prompt: "Search agents")
|
|
.refreshable {
|
|
await self.refreshOverview(force: true)
|
|
}
|
|
.font(OpenClawType.body)
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
self.agentFilterMenu
|
|
self.gatewayToolbarButton
|
|
}
|
|
if let headerSidebarAction {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
OpenClawSidebarHeaderLeadingSlot(action: headerSidebarAction)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var skillsDestination: some View {
|
|
ZStack {
|
|
OpenClawProBackground()
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
self.detailSummaryCard(
|
|
icon: "sparkles",
|
|
title: "Skills",
|
|
value: self.skillsValue,
|
|
detail: self.skillsDetail,
|
|
color: self.gatewayConnected ? OpenClawBrand.accent : .secondary)
|
|
self.skillsPolicyControls
|
|
self.skillsFilterField
|
|
self.clawHubSearchCard
|
|
self.skillsList
|
|
}
|
|
.padding(.vertical, 18)
|
|
.font(OpenClawType.body)
|
|
}
|
|
.refreshable {
|
|
await self.refreshOverview(force: true)
|
|
}
|
|
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
|
|
}
|
|
.navigationTitle("Skills")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
var instancesDestination: some View {
|
|
AgentProNodesDestination(
|
|
headerSidebarAction: self.directHeaderSidebarAction(for: .instances),
|
|
overview: self.overview,
|
|
gatewayConnected: self.gatewayConnected,
|
|
agentCount: self.appModel.gatewayAgents.count,
|
|
instancesValue: self.instancesValue,
|
|
instancesDetail: self.instancesDetail,
|
|
instancesColor: self.instancesColor,
|
|
refresh: {
|
|
await self.refreshOverview(force: true)
|
|
})
|
|
}
|
|
|
|
var cronDestination: some View {
|
|
ZStack {
|
|
OpenClawProBackground()
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
self.directHeader(
|
|
for: .cron,
|
|
title: "Automations",
|
|
subtitle: self.cronDetail)
|
|
self.detailSummaryCard(
|
|
icon: "clock.arrow.circlepath",
|
|
title: "Automations",
|
|
value: self.cronValue,
|
|
detail: self.cronDetail,
|
|
color: self.cronColor)
|
|
self.cronStatusCard
|
|
self.cronJobsList(limit: nil)
|
|
}
|
|
.padding(.vertical, 18)
|
|
.font(OpenClawType.body)
|
|
}
|
|
.refreshable {
|
|
await self.refreshOverview(force: true)
|
|
}
|
|
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
|
|
}
|
|
.navigationTitle("Automations")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
var usageDestination: some View {
|
|
ZStack {
|
|
OpenClawProBackground()
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
self.directHeader(
|
|
for: .usage,
|
|
title: "Usage",
|
|
subtitle: self.usageDetail)
|
|
self.detailSummaryCard(
|
|
icon: "chart.line.uptrend.xyaxis",
|
|
title: "Usage",
|
|
value: self.usageValue,
|
|
detail: self.usageDetail,
|
|
color: self.gatewayConnected ? OpenClawBrand.accent : .secondary)
|
|
self.usageTotalsCard
|
|
self.usageDailyList
|
|
}
|
|
.padding(.vertical, 18)
|
|
.font(OpenClawType.body)
|
|
}
|
|
.refreshable {
|
|
await self.refreshOverview(force: true)
|
|
}
|
|
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
|
|
}
|
|
.navigationTitle("Usage")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
var dreamingDestination: some View {
|
|
AgentProDreamingDestination(
|
|
headerSidebarAction: self.directHeaderSidebarAction(for: .dreaming),
|
|
overview: self.overview,
|
|
gatewayConnected: self.gatewayConnected,
|
|
overviewLoading: self.overviewLoading,
|
|
dreamingValue: self.dreamingValue,
|
|
dreamingDetail: self.dreamingDetail,
|
|
dreamingColor: self.dreamingColor,
|
|
refresh: {
|
|
await self.refreshOverview(force: true)
|
|
})
|
|
}
|
|
|
|
@ViewBuilder
|
|
func directHeader(for route: AgentRoute, title: String, subtitle: String) -> some View {
|
|
if let headerSidebarAction = self.directHeaderSidebarAction(for: route) {
|
|
OpenClawAdaptiveHeaderRow(
|
|
title: .localized(title),
|
|
subtitle: .localized(subtitle),
|
|
titleFont: OpenClawType.title3SemiBold,
|
|
subtitleFont: OpenClawType.subheadMedium)
|
|
{
|
|
OpenClawSidebarHeaderLeadingSlot(action: headerSidebarAction)
|
|
} accessory: {
|
|
EmptyView()
|
|
}
|
|
.padding(.horizontal, OpenClawProMetric.pagePadding)
|
|
}
|
|
}
|
|
|
|
func directHeaderSidebarAction(for route: AgentRoute) -> OpenClawSidebarHeaderAction? {
|
|
self.directRoute == route ? self.headerSidebarAction : nil
|
|
}
|
|
|
|
func detailSummaryCard(
|
|
icon: String,
|
|
title: String,
|
|
value: String,
|
|
detail: String,
|
|
color: Color) -> some View
|
|
{
|
|
ProCard(radius: AgentLayout.cardRadius) {
|
|
HStack(spacing: 12) {
|
|
ProIconBadge(systemName: icon, color: color)
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
Text(title)
|
|
.font(OpenClawType.headline)
|
|
Text(detail)
|
|
.font(OpenClawType.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Spacer(minLength: 8)
|
|
ProValuePill(value: value, color: color)
|
|
}
|
|
}
|
|
.padding(.horizontal, OpenClawProMetric.pagePadding)
|
|
}
|
|
}
|