fix: app
This commit is contained in:
@@ -16,11 +16,13 @@ struct ControlView: View {
|
||||
@State private var isLoading = true
|
||||
@State private var streamTask: Task<Void, Never>?
|
||||
@State private var showMissionMenu = false
|
||||
@State private var shouldScrollToBottom = false
|
||||
|
||||
@FocusState private var isInputFocused: Bool
|
||||
|
||||
private let api = APIService.shared
|
||||
private let nav = NavigationState.shared
|
||||
private let bottomAnchorId = "bottom-anchor"
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -169,6 +171,11 @@ struct ControlView: View {
|
||||
.id(message.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom anchor for scrolling past last message
|
||||
Color.clear
|
||||
.frame(height: 1)
|
||||
.id(bottomAnchorId)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
@@ -177,15 +184,23 @@ struct ControlView: View {
|
||||
isInputFocused = false
|
||||
}
|
||||
.onChange(of: messages.count) { _, _ in
|
||||
if let lastMessage = messages.last {
|
||||
withAnimation {
|
||||
proxy.scrollTo(lastMessage.id, anchor: .bottom)
|
||||
}
|
||||
scrollToBottom(proxy: proxy)
|
||||
}
|
||||
.onChange(of: shouldScrollToBottom) { _, shouldScroll in
|
||||
if shouldScroll {
|
||||
scrollToBottom(proxy: proxy)
|
||||
shouldScrollToBottom = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func scrollToBottom(proxy: ScrollViewProxy) {
|
||||
withAnimation {
|
||||
proxy.scrollTo(bottomAnchorId, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyStateView: some View {
|
||||
VStack(spacing: 32) {
|
||||
Spacer()
|
||||
@@ -313,7 +328,7 @@ struct ControlView: View {
|
||||
private func loadCurrentMission() async {
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
|
||||
|
||||
do {
|
||||
if let mission = try await api.getCurrentMission() {
|
||||
currentMission = mission
|
||||
@@ -324,6 +339,11 @@ struct ControlView: View {
|
||||
content: entry.content
|
||||
)
|
||||
}
|
||||
|
||||
// Scroll to bottom after loading
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
shouldScrollToBottom = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to load mission: \(error)")
|
||||
@@ -333,7 +353,7 @@ struct ControlView: View {
|
||||
private func loadMission(id: String) async {
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
|
||||
|
||||
do {
|
||||
let missions = try await api.listMissions()
|
||||
if let mission = missions.first(where: { $0.id == id }) {
|
||||
@@ -346,6 +366,11 @@ struct ControlView: View {
|
||||
)
|
||||
}
|
||||
HapticService.success()
|
||||
|
||||
// Scroll to bottom after loading
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
shouldScrollToBottom = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to load mission: \(error)")
|
||||
|
||||
@@ -213,6 +213,7 @@ struct FilesView: View {
|
||||
isEditingPath = false
|
||||
} label: {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.title3)
|
||||
.foregroundStyle(Theme.textMuted)
|
||||
}
|
||||
|
||||
@@ -221,18 +222,20 @@ struct FilesView: View {
|
||||
isEditingPath = false
|
||||
} label: {
|
||||
Image(systemName: "arrow.right.circle.fill")
|
||||
.font(.title3)
|
||||
.foregroundStyle(Theme.accent)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
} else {
|
||||
// Breadcrumb path
|
||||
// Breadcrumb path using / separators
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 2) {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(Array(breadcrumbs.enumerated()), id: \.offset) { index, crumb in
|
||||
if index > 0 {
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption2.weight(.semibold))
|
||||
// Add / separator after first element (which is "/"), not before
|
||||
if index > 1 {
|
||||
Text("/")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundStyle(Theme.textMuted)
|
||||
}
|
||||
|
||||
@@ -242,30 +245,31 @@ struct FilesView: View {
|
||||
Text(crumb.name)
|
||||
.font(.subheadline.weight(index == breadcrumbs.count - 1 ? .semibold : .medium))
|
||||
.foregroundStyle(index == breadcrumbs.count - 1 ? Theme.textPrimary : Theme.textTertiary)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 6)
|
||||
.background(index == breadcrumbs.count - 1 ? Theme.backgroundSecondary : .clear)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
// Edit button
|
||||
Button {
|
||||
editedPath = currentPath
|
||||
isEditingPath = true
|
||||
isPathFieldFocused = true
|
||||
} label: {
|
||||
Image(systemName: "pencil")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted)
|
||||
.padding(6)
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 16)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
|
||||
// Edit button - larger tap target
|
||||
Button {
|
||||
editedPath = currentPath
|
||||
isEditingPath = true
|
||||
isPathFieldFocused = true
|
||||
HapticService.selectionChanged()
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.accent)
|
||||
.frame(width: 44, height: 44)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.leading, currentPath == "/" && !isEditingPath ? 16 : 0)
|
||||
.padding(.leading, currentPath == "/" && !isEditingPath ? 12 : 0)
|
||||
.frame(height: 44)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
|
||||
@@ -84,21 +84,37 @@ async function generateIcon(size, outputPath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function generateIconsForApp(iconDir, filenameMap, sizes) {
|
||||
if (!fs.existsSync(iconDir)) {
|
||||
console.log(`⚠ Icon directory not found: ${iconDir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nGenerating icons in: ${iconDir}`);
|
||||
|
||||
// Generate all icon sizes
|
||||
for (const size of sizes) {
|
||||
const filename = filenameMap[size];
|
||||
if (!filename) {
|
||||
console.log(`Warning: No filename mapping for size ${size}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputPath = path.join(iconDir, filename);
|
||||
await generateIcon(size, outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const scriptDir = __dirname;
|
||||
const projectRoot = path.resolve(scriptDir, '..');
|
||||
const iconDir = path.join(projectRoot, 'ios_dashboard', 'OpenAgentDashboard', 'Assets.xcassets', 'AppIcon.appiconset');
|
||||
|
||||
if (!fs.existsSync(iconDir)) {
|
||||
console.error(`Error: Icon directory not found: ${iconDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Generating iOS app icons in: ${iconDir}`);
|
||||
console.log(`Generating iOS app icons`);
|
||||
console.log(`Using indigo color: ${INDIGO_COLOR}\n`);
|
||||
|
||||
// Map sizes to filenames
|
||||
const filenameMap = {
|
||||
// OpenAgentDashboard icons
|
||||
const dashboardIconDir = path.join(projectRoot, 'ios_dashboard', 'OpenAgentDashboard', 'Assets.xcassets', 'AppIcon.appiconset');
|
||||
const dashboardFilenameMap = {
|
||||
20: 'icon-20.png',
|
||||
29: 'icon-29.png',
|
||||
40: 'icon-40.png',
|
||||
@@ -113,18 +129,14 @@ async function main() {
|
||||
180: 'icon-180.png',
|
||||
1024: 'icon-1024.png',
|
||||
};
|
||||
await generateIconsForApp(dashboardIconDir, dashboardFilenameMap, ICON_SIZES);
|
||||
|
||||
// Generate all icon sizes
|
||||
for (const size of ICON_SIZES) {
|
||||
const filename = filenameMap[size];
|
||||
if (!filename) {
|
||||
console.log(`Warning: No filename mapping for size ${size}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputPath = path.join(iconDir, filename);
|
||||
await generateIcon(size, outputPath);
|
||||
}
|
||||
// Calorily icons (only 1024x1024)
|
||||
const calorilyIconDir = path.join(projectRoot, 'calorily', 'Calorily', 'Assets.xcassets', 'AppIcon.appiconset');
|
||||
const calorilyFilenameMap = {
|
||||
1024: 'icon_1024.png',
|
||||
};
|
||||
await generateIconsForApp(calorilyIconDir, calorilyFilenameMap, [1024]);
|
||||
|
||||
console.log('\n✓ All icons generated successfully!');
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user