From 1d40afff305b37e42510e6de7225c6594f682e3c Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Tue, 13 Jan 2026 15:10:03 +0100 Subject: [PATCH 1/7] Changed name of the Runestone file and did some refactoring --- piPhone/Apps/AppsView.swift | 281 ++---------------- piPhone/Apps/Files/FileView.swift | 156 ++++++++++ .../Apps/{ => Runestone}/RunestoneTheme.swift | 0 piPhone/Apps/Runestone/RunestoneView.swift | 123 ++++++++ piPhone/Device/DeviceView.swift | 25 +- 5 files changed, 311 insertions(+), 274 deletions(-) create mode 100644 piPhone/Apps/Files/FileView.swift rename piPhone/Apps/{ => Runestone}/RunestoneTheme.swift (100%) create mode 100644 piPhone/Apps/Runestone/RunestoneView.swift diff --git a/piPhone/Apps/AppsView.swift b/piPhone/Apps/AppsView.swift index 0dd18ba..d530a32 100644 --- a/piPhone/Apps/AppsView.swift +++ b/piPhone/Apps/AppsView.swift @@ -13,130 +13,7 @@ import TreeSitterJavaScriptRunestone import TreeSitterPythonRunestone import UIKit -struct AppItem: Identifiable { - var id = UUID() - var title: String - var icon: String -} - -enum CodeLanguage: Equatable { - case plainText - case javaScript - case python - case java - - static func fromFilename(_ name: String) -> CodeLanguage { - let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) - let ext = (trimmed as NSString).pathExtension.lowercased() - - switch ext { - case "js", "jsx", "mjs", "cjs": - return .javaScript - case "py": - return .python - case "java": - return .java - default: - return .plainText - } - } -} - -struct FileOption: Identifiable, Equatable { - let id: String - var name: String - var url: String - var code: String - var language: CodeLanguage - - init( - id: String = UUID().uuidString, - name: String, - url: String, - code: String = "", - language: CodeLanguage = .plainText - ) { - self.id = id - self.name = name - self.url = url - self.code = code - self.language = language - } -} - -struct RunestoneEditorView: UIViewRepresentable { - @Binding var text: String - var language: CodeLanguage - - func makeUIView(context: Context) -> TextView { - let tv = TextView() - tv.editorDelegate = context.coordinator - - tv.theme = RunestoneTheme() - tv.backgroundColor = UIColor.clear - - tv.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5) - tv.showLineNumbers = true - tv.lineHeightMultiplier = 1.2 - tv.kern = 0.3 - - tv.characterPairs = defaultPairs - tv.indentStrategy = .space(length: 4) - tv.autocorrectionType = .no - tv.autocapitalizationType = .none - tv.smartQuotesType = .no - tv.smartDashesType = .no - tv.smartInsertDeleteType = .no - tv.spellCheckingType = .no - - tv.text = text - applyLanguage(to: tv, language: language) - - return tv - } - - func updateUIView(_ tv: TextView, context: Context) { - if tv.text != text { - tv.text = text - } - - if context.coordinator.lastLanguage != language { - context.coordinator.lastLanguage = language - applyLanguage(to: tv, language: language) - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(text: $text, lastLanguage: language) - } - - final class Coordinator: NSObject, TextViewDelegate { - var text: Binding - var lastLanguage: CodeLanguage - - init(text: Binding, lastLanguage: CodeLanguage) { - self.text = text - self.lastLanguage = lastLanguage - } - } - - private func applyLanguage(to tv: TextView, language: CodeLanguage) { - switch language { - case .plainText: - tv.setLanguageMode(PlainTextLanguageMode()) - - case .javaScript: - tv.setLanguageMode(TreeSitterLanguageMode(language: .javaScript)) - - case .python: - tv.setLanguageMode(TreeSitterLanguageMode(language: .python)) - - case .java: - tv.setLanguageMode(TreeSitterLanguageMode(language: .java)) - } - } -} - +// MARK: - Main View struct AppsView: View { @State private var showAddSheet = false @State private var newTitle = "" @@ -146,6 +23,7 @@ struct AppsView: View { @State private var appPendingDelete: AppItem? = nil @State private var showDeleteAlert = false + // MARK: - App Item Mock Data @State private var apps: [AppItem] = [ .init(title: "UI change", icon: "photo"), .init(title: "Take a Break", icon: "timer"), @@ -182,6 +60,7 @@ struct AppsView: View { showAddSheet = false } + // MARK: view var body: some View { NavigationStack { ZStack { @@ -282,6 +161,14 @@ struct AppsView: View { } } +// MARK: - Model +struct AppItem: Identifiable { + var id = UUID() + var title: String + var icon: String +} + +// MARK: - App Card View struct AppCard: View { let item: AppItem @@ -303,7 +190,8 @@ struct AppCard: View { } } -struct AppIconCell: View { +// MARK: - App Icon Cell View + struct AppIconCell: View { let item: AppItem var body: some View { @@ -321,6 +209,7 @@ struct AppIconCell: View { } } +// MARK: - Destination Screens struct AppDetailView: View { let item: AppItem @@ -339,72 +228,7 @@ struct AppDetailView: View { } } -struct FileRow: View { - let title: String - let value: String - - var body: some View { - HStack { - Text("Choose a file:") - .foregroundColor(.secondary) - - Spacer() - - Text(value) - .foregroundStyle(.secondary) - } - } -} - -struct FilePickerScreen: View { - @Binding var selectedFileId: String - @Binding var options: [FileOption] - - @Environment(\.dismiss) private var dismiss - @State private var showAddFileSheet = false - - var body: some View { - List { - ForEach(options) { opt in - Button { - selectedFileId = (selectedFileId == opt.id) ? "none" : opt.id - } label: { - HStack { - Text(opt.name) - .foregroundStyle(.primary) - - Spacer() - - if opt.id == selectedFileId { - Image(systemName: "checkmark") - .foregroundStyle(.primary) - } - } - .contentShape(Rectangle()) - } - .buttonStyle(.plain) - } - } - .navigationTitle("File") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button { - showAddFileSheet = true - } label: { - Image(systemName: "plus") - } - } - } - .sheet(isPresented: $showAddFileSheet) { - AddFileSheet { newFile in - options.append(newFile) - selectedFileId = newFile.id - } - } - } -} - +// MARK: - Add App Sheet struct AddAppSheet: View { @Binding var title: String @Binding var icon: String @@ -412,10 +236,11 @@ struct AddAppSheet: View { let onAdd: () -> Void - @State private var fileOptions: [FileOption] = [ - FileOption(id: "f1", name: "projects.pdf", url: "files://projects") + @State private var fileOptions: [File] = [ + File(id: "f1", name: "projects.pdf", url: "files://projects") ] + // MARK: - Icons private let iconCategories: [(title: String, symbols: [String])] = [ ( "Communication", @@ -469,6 +294,7 @@ struct AddAppSheet: View { fileOptions.first(where: { $0.id == selectedFileId })?.name ?? "None" } + // MARK: - Sheet var body: some View { NavigationStack { Form { @@ -489,7 +315,6 @@ struct AddAppSheet: View { Section("Icon") { - // Selected icon preview HStack(spacing: 12) { Image(systemName: icon) .font(.title2) @@ -545,74 +370,6 @@ struct AddAppSheet: View { } } -struct AddFileSheet: View { - @Environment(\.dismiss) private var dismiss - - @State private var name: String = "" - @State private var code: String = "" - - let onSave: (FileOption) -> Void - - private var detectedLanguage: CodeLanguage { - CodeLanguage.fromFilename(name) - } - - var body: some View { - NavigationStack { - Form { - Section("File Info") { - TextField("File name (e.g. notes.py)", text: $name) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - } - - Section("Code") { - RunestoneEditorView(text: $code, language: detectedLanguage) - .frame(minHeight: 550) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - } - } - .scrollDismissesKeyboard(.interactively) - .navigationTitle("Add File") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { dismiss() } - } - - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return } - - let newFile = FileOption( - name: trimmed, - url: "files://\(trimmed)", - code: code - ) - onSave(newFile) - dismiss() - } - .disabled(name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) - } - } - } - } -} - -struct SimplePair: CharacterPair { - let leading: String - let trailing: String -} - -private let defaultPairs: [CharacterPair] = [ - SimplePair(leading: "{", trailing: "}"), - SimplePair(leading: "(", trailing: ")"), - SimplePair(leading: "[", trailing: "]"), - SimplePair(leading: "\"", trailing: "\""), - SimplePair(leading: "'", trailing: "'"), -] - #Preview { ContentView() } diff --git a/piPhone/Apps/Files/FileView.swift b/piPhone/Apps/Files/FileView.swift new file mode 100644 index 0000000..59296bb --- /dev/null +++ b/piPhone/Apps/Files/FileView.swift @@ -0,0 +1,156 @@ +// +// FileView.swift +// piPhone +// +// Created by Eris Leci on 1/12/26. +// + +import SwiftUI + +// MARK: - File Model +struct File: Identifiable, Equatable { + let id: String + var name: String + var url: String + var code: String + var language: CodeLanguage + + init( + id: String = UUID().uuidString, + name: String, + url: String, + code: String = "", + language: CodeLanguage = .plainText + ) { + self.id = id + self.name = name + self.url = url + self.code = code + self.language = language + } +} + +// MARK: - FileRow Model +struct FileRow: View { + let title: String + let value: String + + var body: some View { + HStack { + Text("Choose a file:") + .foregroundColor(.secondary) + + Spacer() + + Text(value) + .foregroundStyle(.secondary) + } + } +} + +// MARK: - Pick file screen +struct FilePickerScreen: View { + @Binding var selectedFileId: String + @Binding var options: [File] + + @Environment(\.dismiss) private var dismiss + @State private var showAddFileSheet = false + + var body: some View { + List { + ForEach(options) { opt in + Button { + selectedFileId = (selectedFileId == opt.id) ? "none" : opt.id + } label: { + HStack { + Text(opt.name) + .foregroundStyle(.primary) + + Spacer() + + if opt.id == selectedFileId { + Image(systemName: "checkmark") + .foregroundStyle(.primary) + } + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } + } + .navigationTitle("File") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + showAddFileSheet = true + } label: { + Image(systemName: "plus") + } + } + } + .sheet(isPresented: $showAddFileSheet) { + AddFileSheet { newFile in + options.append(newFile) + selectedFileId = newFile.id + } + } + } +} + +// MARK: - Add File sheet +struct AddFileSheet: View { + @Environment(\.dismiss) private var dismiss + + @State private var name: String = "" + @State private var code: String = "" + + let onSave: (File) -> Void + + private var detectedLanguage: CodeLanguage { + CodeLanguage.fromFilename(name) + } + + // MARK: - View + var body: some View { + NavigationStack { + Form { + Section("File Info") { + TextField("File name (e.g. notes.py)", text: $name) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + + Section("Code") { + RunestoneView(text: $code, language: detectedLanguage) + .frame(minHeight: 550) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + } + } + .scrollDismissesKeyboard(.interactively) + .navigationTitle("Add File") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Save") { + let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + + let newFile = File( + name: trimmed, + url: "files://\(trimmed)", + code: code + ) + onSave(newFile) + dismiss() + } + .disabled(name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + } + } + } +} diff --git a/piPhone/Apps/RunestoneTheme.swift b/piPhone/Apps/Runestone/RunestoneTheme.swift similarity index 100% rename from piPhone/Apps/RunestoneTheme.swift rename to piPhone/Apps/Runestone/RunestoneTheme.swift diff --git a/piPhone/Apps/Runestone/RunestoneView.swift b/piPhone/Apps/Runestone/RunestoneView.swift new file mode 100644 index 0000000..f2feffb --- /dev/null +++ b/piPhone/Apps/Runestone/RunestoneView.swift @@ -0,0 +1,123 @@ +// +// RunestoneView.swift +// piPhone +// +// Created by Eris Leci on 1/12/26. +// + +import SwiftUI +import Runestone +import UIKit + +import TreeSitterJavaRunestone +import TreeSitterJavaScriptRunestone +import TreeSitterPythonRunestone + +struct RunestoneView: UIViewRepresentable { + @Binding var text: String + var language: CodeLanguage + + func makeUIView(context: Context) -> TextView { + let tv = TextView() + tv.editorDelegate = context.coordinator + + tv.theme = RunestoneTheme() + tv.backgroundColor = UIColor.clear + + tv.textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5) + tv.showLineNumbers = true + tv.lineHeightMultiplier = 1.2 + tv.kern = 0.3 + + tv.characterPairs = defaultPairs + tv.indentStrategy = .space(length: 4) + tv.autocorrectionType = .no + tv.autocapitalizationType = .none + tv.smartQuotesType = .no + tv.smartDashesType = .no + tv.smartInsertDeleteType = .no + tv.spellCheckingType = .no + + tv.text = text + applyLanguage(to: tv, language: language) + + return tv + } + + func updateUIView(_ tv: TextView, context: Context) { + if tv.text != text { + tv.text = text + } + + if context.coordinator.lastLanguage != language { + context.coordinator.lastLanguage = language + applyLanguage(to: tv, language: language) + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(text: $text, lastLanguage: language) + } + + final class Coordinator: NSObject, TextViewDelegate { + var text: Binding + var lastLanguage: CodeLanguage + + init(text: Binding, lastLanguage: CodeLanguage) { + self.text = text + self.lastLanguage = lastLanguage + } + } + + private func applyLanguage(to tv: TextView, language: CodeLanguage) { + switch language { + case .plainText: + tv.setLanguageMode(PlainTextLanguageMode()) + + case .javaScript: + tv.setLanguageMode(TreeSitterLanguageMode(language: .javaScript)) + + case .python: + tv.setLanguageMode(TreeSitterLanguageMode(language: .python)) + + case .java: + tv.setLanguageMode(TreeSitterLanguageMode(language: .java)) + } + } +} + +enum CodeLanguage: Equatable { + case plainText + case javaScript + case python + case java + + static func fromFilename(_ name: String) -> CodeLanguage { + let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) + let ext = (trimmed as NSString).pathExtension.lowercased() + + switch ext { + case "js", "jsx", "mjs", "cjs": + return .javaScript + case "py": + return .python + case "java": + return .java + default: + return .plainText + } + } +} + +struct SimplePair: CharacterPair { + let leading: String + let trailing: String +} + +private let defaultPairs: [CharacterPair] = [ + SimplePair(leading: "{", trailing: "}"), + SimplePair(leading: "(", trailing: ")"), + SimplePair(leading: "[", trailing: "]"), + SimplePair(leading: "\"", trailing: "\""), + SimplePair(leading: "'", trailing: "'"), +] diff --git a/piPhone/Device/DeviceView.swift b/piPhone/Device/DeviceView.swift index ee3045a..73d0d8e 100644 --- a/piPhone/Device/DeviceView.swift +++ b/piPhone/Device/DeviceView.swift @@ -24,18 +24,19 @@ struct DeviceView: View { struct DeviceRealityView: View { var body: some View { - RealityView { content in - if let model = try? await ModelEntity(named: "piPhone-20260104-final") { - model.transform.scale *= 20.0 - model.generateCollisionShapes(recursive: true) - - content.add(model) - } - Task { - // Asynchronously perform any additional work to configure - // the content after the system renders the view. - } - } + // RealityView { content in + // if let model = try? await ModelEntity(named: "piPhone-20260104-final") { + // model.transform.scale *= 20.0 + // model.generateCollisionShapes(recursive: true) + // + // content.add(model) + // } + // Task { + // // Asynchronously perform any additional work to configure + // // the content after the system renders the view. + // } + // } + EmptyView() } } From 24e1b2e9c4a76f854bd23ebfdbbc64a3aa054bd6 Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Wed, 21 Jan 2026 00:54:50 +0100 Subject: [PATCH 2/7] Change AppCard look --- piPhone/Apps/AppsView.swift | 174 ++++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 78 deletions(-) diff --git a/piPhone/Apps/AppsView.swift b/piPhone/Apps/AppsView.swift index d530a32..9266754 100644 --- a/piPhone/Apps/AppsView.swift +++ b/piPhone/Apps/AppsView.swift @@ -18,29 +18,35 @@ struct AppsView: View { @State private var showAddSheet = false @State private var newTitle = "" @State private var newIcon = "" - @State private var newFileId = "none" + @State private var newExecutableId = "none" @State private var searchText = "" @State private var appPendingDelete: AppItem? = nil @State private var showDeleteAlert = false + @Environment(\.horizontalSizeClass) private var hSize + @Environment(\.verticalSizeClass) private var vSize // MARK: - App Item Mock Data @State private var apps: [AppItem] = [ - .init(title: "UI change", icon: "photo"), - .init(title: "Take a Break", icon: "timer"), - .init(title: "Add new Task", icon: "map"), - .init(title: "Add new Apple", icon: "phone"), - .init(title: "Add new Ticket", icon: "map"), - .init(title: "Should add a new Ticket", icon: "headphones"), - .init(title: "Change Theme", icon: "envelope"), - .init(title: "Phone details", icon: "phone"), - .init(title: "Take that dollar", icon: "dollarsign"), - .init(title: "CreditCard", icon: "creditcard"), + .init(title: "UI change", icon: "photo", executableName: "ui-change.py"), + .init(title: "Take a Break", icon: "timer", executableName: "break.sh"), + .init(title: "Add new Task", icon: "map", executableName: "task.js"), + .init(title: "Add new Apple", icon: "phone", executableName: "call.py"), + .init(title: "Add new Ticket", icon: "map", executableName: "ticket.py"), + .init(title: "Should add a new Ticket", icon: "headphones", executableName: "scan.js"), + .init(title: "Change Theme", icon: "envelope", executableName: "theme.js"), + .init(title: "Phone details", icon: "phone", executableName: "details.py"), + .init(title: "Take that dollar", icon: "dollarsign", executableName: "money.sh"), + .init(title: "CreditCard", icon: "creditcard", executableName: "pay.py"), ] + private var columns: [GridItem] { - [GridItem(.adaptive(minimum: 110))] + let isLandscape = (hSize == .regular && vSize == .compact) || (vSize == .compact) + let count = isLandscape ? 2 : 1 + return Array(repeating: GridItem(.flexible(), spacing: 12), count: count) } + private var filteredApps: [AppItem] { let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines) guard !q.isEmpty else { return apps } @@ -51,12 +57,16 @@ struct AppsView: View { let trimmed = newTitle.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } - let newItem = AppItem(title: trimmed, icon: newIcon) + let newItem = AppItem( + title: trimmed, + icon: newIcon, + executableName: newExecutableId + ) apps.append(newItem) newTitle = "" newIcon = "" - newFileId = "none" + newExecutableId = "none" showAddSheet = false } @@ -68,47 +78,36 @@ struct AppsView: View { .ignoresSafeArea() ScrollView { - LazyVGrid(columns: columns, spacing: 12) { + LazyVGrid( + columns: [GridItem(.adaptive(minimum: 300), spacing: 12)], + spacing: 12 + ) { ForEach(filteredApps) { item in NavigationLink { AppDetailView(item: item) } label: { - VStack(spacing: 8) { - AppCard(item: item) - .contentShape( - .contextMenuPreview, - RoundedRectangle(cornerRadius: 16, style: .continuous) - ) - .contextMenu { - Button { - } label: { - Label("Edit", systemImage: "pencil") - } - Button { - let copy = AppItem( - title: item.title, icon: item.icon) - apps.append(copy) - } label: { - Label("Duplicate", systemImage: "doc.on.doc") - } - - Button(role: .destructive) { - appPendingDelete = item - showDeleteAlert = true - } label: { - Label("Delete", systemImage: "trash") - } - - } - - Text(item.title) - .font(.footnote) - .foregroundStyle(Color(.label)) - .lineLimit(1) - .minimumScaleFactor(0.75) - .frame(width: 90) - .multilineTextAlignment(.center) - } + AppCard(item: item) + .contentShape( + .contextMenuPreview, + RoundedRectangle(cornerRadius: 24, style: .continuous) + ) + .contextMenu { + Button { } label: { Label("Edit", systemImage: "pencil") } + + Button { + let copy = AppItem( + title: item.title, + icon: item.icon, + executableName: item.executableName + ) + apps.append(copy) + } label: { Label("Duplicate", systemImage: "doc.on.doc") } + + Button(role: .destructive) { + appPendingDelete = item + showDeleteAlert = true + } label: { Label("Delete", systemImage: "trash") } + } } .buttonStyle(.plain) } @@ -121,7 +120,7 @@ struct AppsView: View { .searchable( text: $searchText, placement: .navigationBarDrawer(displayMode: .always), - prompt: "Search apps" + prompt: "Search" ) .toolbar { ToolbarItem(placement: .topBarTrailing) { @@ -153,7 +152,7 @@ struct AppsView: View { AddAppSheet( title: $newTitle, icon: $newIcon, - selectedFileId: $newFileId, + selectedExecutableId: $newExecutableId, onAdd: { addNewApp() } ) } @@ -166,6 +165,7 @@ struct AppItem: Identifiable { var id = UUID() var title: String var icon: String + var executableName: String } // MARK: - App Card View @@ -173,23 +173,41 @@ struct AppCard: View { let item: AppItem var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 16, style: .continuous) - .fill(Color(.secondarySystemGroupedBackground)) - .overlay( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .stroke(Color(.separator).opacity(0.35), lineWidth: 1) - ) + HStack(spacing: 14) { - Image(systemName: item.icon) - .font(.system(size: 30, weight: .semibold)) - .foregroundStyle(Color(.label)) + ZStack { + + Image(systemName: item.icon) + .font(.system(size: 30, weight: .semibold)) + .foregroundStyle(Color(.label)) + } + .frame(width: 72, height: 72) + + VStack(alignment: .leading, spacing: 4) { + Text(item.title) + .font(.system(size: 21, weight: .semibold)) + .foregroundStyle(.primary) + .lineLimit(1) + + Text(item.executableName) + .font(.system(size: 16)) + .foregroundStyle(Color(.label)) + .lineLimit(1) + .truncationMode(.middle) + } + + Spacer() } - .frame(width: 72, height: 72) - .shadow(color: Color.black.opacity(0.10), radius: 6, x: 0, y: 3) + .padding(.vertical, 14) + .padding(.horizontal, 16) + .background( + RoundedRectangle(cornerRadius: 24, style: .continuous) + .fill(Color(.secondarySystemGroupedBackground)) + ) } } + // MARK: - App Icon Cell View struct AppIconCell: View { let item: AppItem @@ -232,12 +250,12 @@ struct AppDetailView: View { struct AddAppSheet: View { @Binding var title: String @Binding var icon: String - @Binding var selectedFileId: String + @Binding var selectedExecutableId: String let onAdd: () -> Void - @State private var fileOptions: [File] = [ - File(id: "f1", name: "projects.pdf", url: "files://projects") + @State private var executableOptions: [Executable] = [ + Executable(id: "f1", name: "projects.pdf", url: "files://projects") ] // MARK: - Icons @@ -290,30 +308,30 @@ struct AddAppSheet: View { private let columns = Array(repeating: GridItem(.flexible(), spacing: 10), count: 5) - private var selectedFileName: String { - fileOptions.first(where: { $0.id == selectedFileId })?.name ?? "None" + private var selectedExecutableName: String { + executableOptions.first(where: { $0.id == selectedExecutableId })?.name ?? "None" } // MARK: - Sheet var body: some View { NavigationStack { Form { - Section("File") { + Section() { NavigationLink { - FilePickerScreen( - selectedFileId: $selectedFileId, - options: $fileOptions + ExecutablePickerScreen( + selectedExecutableId: $selectedExecutableId, + options: $executableOptions ) } label: { - FileRow(title: "File", value: selectedFileName) + ExecutableRow(title: "Executable", value: selectedExecutableName) } } - Section("App Info") { + Section() { TextField("App name", text: $title) } - Section("Icon") { + Section() { HStack(spacing: 12) { Image(systemName: icon) @@ -371,5 +389,5 @@ struct AddAppSheet: View { } #Preview { - ContentView() + AppsView() } From 956e7b7e178fdbd405d0533a4537fe27bcb589a7 Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Wed, 21 Jan 2026 00:56:42 +0100 Subject: [PATCH 3/7] Change the name from Files to Executable --- piPhone/Apps/Files/FileView.swift | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/piPhone/Apps/Files/FileView.swift b/piPhone/Apps/Files/FileView.swift index 59296bb..ffceec9 100644 --- a/piPhone/Apps/Files/FileView.swift +++ b/piPhone/Apps/Files/FileView.swift @@ -1,14 +1,14 @@ // -// FileView.swift +// ExecutableView.swift // piPhone // -// Created by Eris Leci on 1/12/26. +// Created by Gentris Leci on 1/12/26. // import SwiftUI -// MARK: - File Model -struct File: Identifiable, Equatable { +// MARK: - Executable Model +struct Executable: Identifiable, Equatable { let id: String var name: String var url: String @@ -30,14 +30,14 @@ struct File: Identifiable, Equatable { } } -// MARK: - FileRow Model -struct FileRow: View { +// MARK: - ExecutableRow Model +struct ExecutableRow: View { let title: String let value: String var body: some View { HStack { - Text("Choose a file:") + Text("Choose a executable:") .foregroundColor(.secondary) Spacer() @@ -48,19 +48,19 @@ struct FileRow: View { } } -// MARK: - Pick file screen -struct FilePickerScreen: View { - @Binding var selectedFileId: String - @Binding var options: [File] +// MARK: - Pick executable screen +struct ExecutablePickerScreen: View { + @Binding var selectedExecutableId: String + @Binding var options: [Executable] @Environment(\.dismiss) private var dismiss - @State private var showAddFileSheet = false + @State private var showAddExecutableSheet = false var body: some View { List { ForEach(options) { opt in Button { - selectedFileId = (selectedFileId == opt.id) ? "none" : opt.id + selectedExecutableId = (selectedExecutableId == opt.id) ? "none" : opt.id } label: { HStack { Text(opt.name) @@ -68,7 +68,7 @@ struct FilePickerScreen: View { Spacer() - if opt.id == selectedFileId { + if opt.id == selectedExecutableId { Image(systemName: "checkmark") .foregroundStyle(.primary) } @@ -78,45 +78,45 @@ struct FilePickerScreen: View { .buttonStyle(.plain) } } - .navigationTitle("File") + .navigationTitle("Executable") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { - showAddFileSheet = true + showAddExecutableSheet = true } label: { Image(systemName: "plus") } } } - .sheet(isPresented: $showAddFileSheet) { - AddFileSheet { newFile in - options.append(newFile) - selectedFileId = newFile.id + .sheet(isPresented: $showAddExecutableSheet) { + AddExecutableSheet { newExecutable in + options.append(newExecutable) + selectedExecutableId = newExecutable.id } } } } -// MARK: - Add File sheet -struct AddFileSheet: View { +// MARK: - Add Executable sheet +struct AddExecutableSheet: View { @Environment(\.dismiss) private var dismiss @State private var name: String = "" @State private var code: String = "" - let onSave: (File) -> Void + let onSave: (Executable) -> Void private var detectedLanguage: CodeLanguage { - CodeLanguage.fromFilename(name) + CodeLanguage.fromExecutableName(name) } // MARK: - View var body: some View { NavigationStack { Form { - Section("File Info") { - TextField("File name (e.g. notes.py)", text: $name) + Section() { + TextField("Executable name (e.g. notes.py)", text: $name) .textInputAutocapitalization(.never) .autocorrectionDisabled() } @@ -128,7 +128,7 @@ struct AddFileSheet: View { } } .scrollDismissesKeyboard(.interactively) - .navigationTitle("Add File") + .navigationTitle("Add Executable") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { @@ -140,7 +140,7 @@ struct AddFileSheet: View { let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } - let newFile = File( + let newFile = Executable( name: trimmed, url: "files://\(trimmed)", code: code From 15c654d269af9203f093cb4abf43fc157a436c7d Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Wed, 21 Jan 2026 00:57:54 +0100 Subject: [PATCH 4/7] Changing the files to executable --- .../{Files/FileView.swift => Executable/ExecutableView.swift} | 0 piPhone/Apps/Runestone/RunestoneView.swift | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename piPhone/Apps/{Files/FileView.swift => Executable/ExecutableView.swift} (100%) diff --git a/piPhone/Apps/Files/FileView.swift b/piPhone/Apps/Executable/ExecutableView.swift similarity index 100% rename from piPhone/Apps/Files/FileView.swift rename to piPhone/Apps/Executable/ExecutableView.swift diff --git a/piPhone/Apps/Runestone/RunestoneView.swift b/piPhone/Apps/Runestone/RunestoneView.swift index f2feffb..f19f69e 100644 --- a/piPhone/Apps/Runestone/RunestoneView.swift +++ b/piPhone/Apps/Runestone/RunestoneView.swift @@ -92,7 +92,7 @@ enum CodeLanguage: Equatable { case python case java - static func fromFilename(_ name: String) -> CodeLanguage { + static func fromExecutableName(_ name: String) -> CodeLanguage { let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) let ext = (trimmed as NSString).pathExtension.lowercased() From 6dc8137eae4f71d7066f7532e3d9f011ea298b0d Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Sat, 24 Jan 2026 02:48:08 +0100 Subject: [PATCH 5/7] Add the new UI for AddAppSheet and new View for Files --- piPhone/Apps/AppsView.swift | 382 +++++++++++++++++++++++++----------- 1 file changed, 264 insertions(+), 118 deletions(-) diff --git a/piPhone/Apps/AppsView.swift b/piPhone/Apps/AppsView.swift index 9266754..feb7b55 100644 --- a/piPhone/Apps/AppsView.swift +++ b/piPhone/Apps/AppsView.swift @@ -22,24 +22,16 @@ struct AppsView: View { @State private var searchText = "" @State private var appPendingDelete: AppItem? = nil @State private var showDeleteAlert = false + @State private var newColor: Color = .blue @Environment(\.horizontalSizeClass) private var hSize @Environment(\.verticalSizeClass) private var vSize // MARK: - App Item Mock Data @State private var apps: [AppItem] = [ - .init(title: "UI change", icon: "photo", executableName: "ui-change.py"), - .init(title: "Take a Break", icon: "timer", executableName: "break.sh"), - .init(title: "Add new Task", icon: "map", executableName: "task.js"), - .init(title: "Add new Apple", icon: "phone", executableName: "call.py"), - .init(title: "Add new Ticket", icon: "map", executableName: "ticket.py"), - .init(title: "Should add a new Ticket", icon: "headphones", executableName: "scan.js"), - .init(title: "Change Theme", icon: "envelope", executableName: "theme.js"), - .init(title: "Phone details", icon: "phone", executableName: "details.py"), - .init(title: "Take that dollar", icon: "dollarsign", executableName: "money.sh"), - .init(title: "CreditCard", icon: "creditcard", executableName: "pay.py"), + .init(title: "Hi", icon: "photo", executableName: "his.py", color: .blue), + .init(title: "Take a Break please!", icon: "timer", executableName: "break.sh", color: .green), ] - private var columns: [GridItem] { let isLandscape = (hSize == .regular && vSize == .compact) || (vSize == .compact) let count = isLandscape ? 2 : 1 @@ -57,11 +49,17 @@ struct AppsView: View { let trimmed = newTitle.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } + let iconToSave = newIcon.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + ? "pc" + : newIcon + let newItem = AppItem( title: trimmed, - icon: newIcon, - executableName: newExecutableId + icon: iconToSave, + executableName: newExecutableId, + color: newColor ) + apps.append(newItem) newTitle = "" @@ -70,6 +68,7 @@ struct AppsView: View { showAddSheet = false } + // MARK: view var body: some View { NavigationStack { @@ -98,7 +97,8 @@ struct AppsView: View { let copy = AppItem( title: item.title, icon: item.icon, - executableName: item.executableName + executableName: item.executableName, + color: item.color ) apps.append(copy) } label: { Label("Duplicate", systemImage: "doc.on.doc") } @@ -153,6 +153,7 @@ struct AppsView: View { title: $newTitle, icon: $newIcon, selectedExecutableId: $newExecutableId, + selectedColor: $newColor, onAdd: { addNewApp() } ) } @@ -166,42 +167,55 @@ struct AppItem: Identifiable { var title: String var icon: String var executableName: String + var color: Color } // MARK: - App Card View struct AppCard: View { let item: AppItem + var body: some View { + let shouldCenter = item.title.count <= 6 && item.executableName.count <= 8 HStack(spacing: 14) { - ZStack { + Image(systemName: item.icon) + .font(.system(size: 25, weight: .semibold)) + .foregroundStyle(item.color) + .frame(width: 72, height: 68) - Image(systemName: item.icon) - .font(.system(size: 30, weight: .semibold)) - .foregroundStyle(Color(.label)) - } - .frame(width: 72, height: 72) - - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: shouldCenter ? .center : .leading, spacing: 2) { Text(item.title) - .font(.system(size: 21, weight: .semibold)) - .foregroundStyle(.primary) + .font(.system(size: 19, weight: .semibold)) .lineLimit(1) + .frame(maxWidth: .infinity, alignment: shouldCenter ? .center : .leading) Text(item.executableName) - .font(.system(size: 16)) - .foregroundStyle(Color(.label)) + .font(.system(size: 14)) + .foregroundStyle(.secondary) .lineLimit(1) - .truncationMode(.middle) + .truncationMode(.tail) + .frame(maxWidth: .infinity, alignment: shouldCenter ? .center : .leading) } + .frame(maxWidth: .infinity) + .padding(.leading, 15) Spacer() + + VStack(alignment: .trailing, spacing: 2) { + Text("Success") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(.green) + } + .padding(.trailing, 15) + + .frame(minWidth: 70, alignment: .trailing) } + .padding(.vertical, 14) .padding(.horizontal, 16) .background( - RoundedRectangle(cornerRadius: 24, style: .continuous) + RoundedRectangle(cornerRadius: 50, style: .continuous) .fill(Color(.secondarySystemGroupedBackground)) ) } @@ -251,143 +265,275 @@ struct AddAppSheet: View { @Binding var title: String @Binding var icon: String @Binding var selectedExecutableId: String + @Binding var selectedColor: Color + @State private var scrollMinY: CGFloat = 0 + let onAdd: () -> Void @State private var executableOptions: [Executable] = [ Executable(id: "f1", name: "projects.pdf", url: "files://projects") ] + + private let focusColors: [Color] = [ + .blue, + .teal, + .green, + .yellow, + .orange, + .red, + .pink, + .purple, + .indigo, + .gray + ] + // MARK: - Icons - private let iconCategories: [(title: String, symbols: [String])] = [ - ( - "Communication", - [ + private let icons: [String] = [ "mic.fill", "message.fill", "phone.fill", "video.fill", "envelope.fill", - ] - ), - ( - "Weather", - [ "sun.max.fill", "moon.fill", "cloud.fill", "cloud.rain.fill", - ] - ), - ( - "Objects & Tools", - [ "folder.fill", "paperclip", "link", "book.fill", "trash.fill", "gearshape.fill", "eraser.fill", "graduationcap.fill", "ruler.fill", "backpack.fill", - ] - ), - ( - "Devices", - [ + "globe.europe.africa", "sun.min.fill", + "cloud.sun.fill", "sun.max", + "sunrise.fill", "moon", + "sparkles", "moon.stars", + "cloud", "cloud.heavyrain.fill", + "wind", "snowflake", "leaf", "bolt", "keyboard.fill", "printer.fill", "desktopcomputer", "macpro.gen2", "pc", "airtag.fill", "macpro.gen3.fill", "display", "iphone.gen2", - ] - ), - ( - "Nature", - [ - "globe.europe.africa", "sun.min.fill", - "cloud.sun.fill", "sun.max.fill", - "sunrise.fill", "moon.fill", - "sparkles", "moon.stars", - "cloud.fill", "cloud.heavyrain.fill", - "wind", "snowflake", "leaf", "bolt", - ] - ), ] - private let columns = Array(repeating: GridItem(.flexible(), spacing: 10), count: 5) + private let columns = Array(repeating: GridItem(.flexible(), spacing: 10), count: 6) - private var selectedExecutableName: String { - executableOptions.first(where: { $0.id == selectedExecutableId })?.name ?? "None" + private var canGoNext: Bool { + !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } + + private struct ScrollOffsetKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } + } + @Environment(\.dismiss) private var dismiss + + - // MARK: - Sheet var body: some View { NavigationStack { - Form { - Section() { - NavigationLink { - ExecutablePickerScreen( - selectedExecutableId: $selectedExecutableId, - options: $executableOptions - ) - } label: { - ExecutableRow(title: "Executable", value: selectedExecutableName) - } - } + ZStack { + Color(.systemGroupedBackground).ignoresSafeArea() - Section() { - TextField("App name", text: $title) - } + ScrollView { + VStack(spacing: 18) { + + Text("Create New App") + .font(.system(size: 28, weight: .bold)) + .padding(.top, 50) + + ZStack { + Circle() + .fill(selectedColor) + .opacity(0.9) + Image(systemName: icon.isEmpty ? "pc" : icon) + .font(.system(size: 34, weight: .semibold)) + .foregroundStyle(Color(.label)) + } + .frame(width: 84, height: 84) + .padding(.top, 6) + + TextField("Name", text: $title) + .font(.system(size: 20, weight: .semibold)) + .foregroundStyle(selectedColor) + .multilineTextAlignment(.center) + .textInputAutocapitalization(.words) + .padding(.vertical, 14) + .padding(.horizontal, 16) + .background( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(Color(.secondarySystemGroupedBackground)) + ) + .padding(.horizontal, 22) + + + let cellSize: CGFloat = 44 + let ringWidth: CGFloat = 3 + let gap: CGFloat = 3 + let outerSize = cellSize + 2*(gap + ringWidth) + let bg = Color(.systemGroupedBackground) + + let columns: [GridItem] = Array(repeating: GridItem(.fixed(cellSize), spacing: 17), count: 6) + + LazyVGrid(columns: columns, spacing: 20) { + + ForEach(focusColors.indices, id: \.self) { index in + let color = focusColors[index] + + Button { selectedColor = color } label: { + ZStack { + Circle() + .fill(color) + .frame(width: 46, height: 46) + + if selectedColor == color { + Circle() + .stroke(bg, lineWidth: ringWidth + gap * 2) + .frame(width: cellSize + gap * 2, height: cellSize + gap * 2) + + Circle() + .stroke(Color.blue, lineWidth: ringWidth) + .frame(width: cellSize + 2*(gap + ringWidth/2), + height: cellSize + 2*(gap + ringWidth/2)) + } + } + .frame(width: outerSize, height: outerSize) + } + } - Section() { + ForEach(icons, id: \.self) { name in + Button { icon = name } label: { + ZStack { + Circle() + .fill(icon == name + ? Color.blue.opacity(0.20) + : Color(.secondarySystemGroupedBackground)) + .frame(width: cellSize, height: cellSize) + + Image(systemName: name) + .font(.system(size: 20, weight: .semibold)) + .foregroundStyle( + icon == name + ? Color.blue.opacity(0.85) + : Color(.secondaryLabel) + ) - HStack(spacing: 12) { - Image(systemName: icon) - .font(.title2) - .scaleEffect(1.3) - } + if icon == name { + Circle() + .stroke(bg, lineWidth: ringWidth + gap * 2) + .frame(width: cellSize + gap * 2, height: cellSize + gap * 2) - ScrollView(.vertical, showsIndicators: true) { - VStack(alignment: .leading, spacing: 16) { - - ForEach(iconCategories, id: \.title) { category in - VStack(alignment: .leading, spacing: 8) { - Text(category.title) - .font(.footnote.weight(.semibold)) - .foregroundStyle(.secondary) - - LazyVGrid(columns: columns, spacing: 10) { - ForEach(category.symbols, id: \.self) { name in - Button { - icon = name - } label: { - Image(systemName: name) - .font(.title) - .frame(maxWidth: .infinity, minHeight: 36) - .padding(.vertical, 6) - .background( - RoundedRectangle(cornerRadius: 10) - .fill( - name == icon - ? Color.primary.opacity(0.15) - : Color.clear) - ) - } - .buttonStyle(.plain) + Circle() + .stroke(Color.blue, lineWidth: ringWidth) + .frame(width: cellSize + 2*(gap + ringWidth/2), + height: cellSize + 2*(gap + ringWidth/2)) } } + .frame(width: outerSize, height: outerSize) } } } - .padding(.vertical, 6) + .padding(.horizontal, 16) + .padding(.top, 6) + + + Spacer().frame(height: 90) + } + } + + VStack { + Spacer() + NavigationLink { + ChooseExecutableStep( + selectedExecutableId: $selectedExecutableId, + options: $executableOptions, + onCreate: onAdd + ) + } label: { + Text("Next") + .font(.system(size: 17, weight: .semibold)) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(canGoNext ? Color.accentColor : Color(.systemGray3)) + .foregroundStyle(.white) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } - .frame(maxHeight: .infinity) + .disabled(!canGoNext) + .padding(.horizontal, 16) + .padding(.bottom, 14) } + .ignoresSafeArea(.keyboard, edges: .bottom) + } .scrollDismissesKeyboard(.interactively) - .navigationTitle("New App") - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button("Done") { onAdd() } - .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + + } + } +} + +struct ChooseExecutableStep: View { + @Binding var selectedExecutableId: String + @Binding var options: [Executable] + let onCreate: () -> Void + + private var selectedExecutableName: String { + options.first(where: { $0.id == selectedExecutableId })?.name ?? "None" + } + + var body: some View { + ZStack { + Color(.systemGroupedBackground).ignoresSafeArea() + + VStack(spacing: 16) { + Text("Choose Executable") + .font(.system(size: 24, weight: .bold)) + .padding(.top, 12) + + NavigationLink { + ExecutablePickerScreen( + selectedExecutableId: $selectedExecutableId, + options: $options + ) + } label: { + HStack { + Text("Executable") + .foregroundStyle(.primary) + Spacer() + Text(selectedExecutableName) + .foregroundStyle(.secondary) + .lineLimit(1) + Image(systemName: "chevron.right") + .foregroundStyle(.secondary) + } + .padding(.vertical, 14) + .padding(.horizontal, 16) + .background( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(Color(.secondarySystemGroupedBackground)) + ) + } + .buttonStyle(.plain) + .padding(.horizontal, 16) + + Spacer() + + Button { + onCreate() + } label: { + Text("Create") + .font(.system(size: 17, weight: .semibold)) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color.accentColor) + .foregroundStyle(.white) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } + .padding(.horizontal, 16) + .padding(.bottom, 14) } } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) } } + #Preview { AppsView() } From ee0c45b06140e2a394f2c34afb5bdac3a6224266 Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Sun, 25 Jan 2026 23:54:45 +0100 Subject: [PATCH 6/7] Add new changes for the executable view --- piPhone/Apps/AppsView.swift | 132 +++++-------- .../Apps/Executable/AddExecutableView.swift | 69 +++++++ .../Apps/Executable/EmptyExecutableView.swift | 51 ++++++ .../Apps/Executable/ExecutableRowView.swift | 54 ++++++ piPhone/Apps/Executable/ExecutableView.swift | 173 ++++++++---------- piPhone/Apps/Executable/FileView.swift | 18 ++ 6 files changed, 311 insertions(+), 186 deletions(-) create mode 100644 piPhone/Apps/Executable/AddExecutableView.swift create mode 100644 piPhone/Apps/Executable/EmptyExecutableView.swift create mode 100644 piPhone/Apps/Executable/ExecutableRowView.swift create mode 100644 piPhone/Apps/Executable/FileView.swift diff --git a/piPhone/Apps/AppsView.swift b/piPhone/Apps/AppsView.swift index feb7b55..61c0ceb 100644 --- a/piPhone/Apps/AppsView.swift +++ b/piPhone/Apps/AppsView.swift @@ -18,7 +18,7 @@ struct AppsView: View { @State private var showAddSheet = false @State private var newTitle = "" @State private var newIcon = "" - @State private var newExecutableId = "none" + @State private var newExecutableName = "none" @State private var searchText = "" @State private var appPendingDelete: AppItem? = nil @State private var showDeleteAlert = false @@ -56,7 +56,7 @@ struct AppsView: View { let newItem = AppItem( title: trimmed, icon: iconToSave, - executableName: newExecutableId, + executableName: newExecutableName, color: newColor ) @@ -64,7 +64,7 @@ struct AppsView: View { newTitle = "" newIcon = "" - newExecutableId = "none" + newExecutableName = "none" showAddSheet = false } @@ -152,7 +152,7 @@ struct AppsView: View { AddAppSheet( title: $newTitle, icon: $newIcon, - selectedExecutableId: $newExecutableId, + selectedExecutableName: $newExecutableName, selectedColor: $newColor, onAdd: { addNewApp() } ) @@ -173,10 +173,8 @@ struct AppItem: Identifiable { // MARK: - App Card View struct AppCard: View { let item: AppItem - var body: some View { - let shouldCenter = item.title.count <= 6 && item.executableName.count <= 8 HStack(spacing: 14) { Image(systemName: item.icon) @@ -184,34 +182,30 @@ struct AppCard: View { .foregroundStyle(item.color) .frame(width: 72, height: 68) - VStack(alignment: shouldCenter ? .center : .leading, spacing: 2) { + VStack(spacing: 2) { Text(item.title) .font(.system(size: 19, weight: .semibold)) .lineLimit(1) - .frame(maxWidth: .infinity, alignment: shouldCenter ? .center : .leading) + .multilineTextAlignment(.center) Text(item.executableName) .font(.system(size: 14)) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.tail) - .frame(maxWidth: .infinity, alignment: shouldCenter ? .center : .leading) + .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) - .padding(.leading, 15) - - Spacer() + .padding(.horizontal, 8) VStack(alignment: .trailing, spacing: 2) { Text("Success") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(.green) } - .padding(.trailing, 15) - .frame(minWidth: 70, alignment: .trailing) + .padding(.trailing, 15) } - .padding(.vertical, 14) .padding(.horizontal, 16) .background( @@ -264,7 +258,7 @@ struct AppDetailView: View { struct AddAppSheet: View { @Binding var title: String @Binding var icon: String - @Binding var selectedExecutableId: String + @Binding var selectedExecutableName: String @Binding var selectedColor: Color @State private var scrollMinY: CGFloat = 0 @@ -272,9 +266,41 @@ struct AddAppSheet: View { let onAdd: () -> Void @State private var executableOptions: [Executable] = [ - Executable(id: "f1", name: "projects.pdf", url: "files://projects") + Executable( + id: "f1", + name: "projects.pdf", + url: "files://projects.pdf", + language: .plainText, + createdAt: Date(timeIntervalSinceNow: -60 * 60 * 24 * 3), + sizeBytes: 2_048 + ), + Executable( + id: "f2", + name: "test.py", + url: "files://test.py", + language: .python, + createdAt: Date(timeIntervalSinceNow: -60 * 60 * 24), + sizeBytes: 4_321 + ), + Executable( + id: "f3", + name: "person.java", + url: "files://person.java", + language: .java, + createdAt: Date(timeIntervalSinceNow: -60 * 60 * 12), + sizeBytes: 9_812 + ), + Executable( + id: "f4", + name: "laravel.php", + url: "files://laravel.php", + language: .plainText, + createdAt: Date(), + sizeBytes: 15_672 + ) ] + // MARK: - Colors private let focusColors: [Color] = [ .blue, .teal, @@ -440,8 +466,8 @@ struct AddAppSheet: View { VStack { Spacer() NavigationLink { - ChooseExecutableStep( - selectedExecutableId: $selectedExecutableId, + ExecutablePickerScreen( + selectedExecutableName: $selectedExecutableName, options: $executableOptions, onCreate: onAdd ) @@ -466,74 +492,6 @@ struct AddAppSheet: View { } } } - -struct ChooseExecutableStep: View { - @Binding var selectedExecutableId: String - @Binding var options: [Executable] - let onCreate: () -> Void - - private var selectedExecutableName: String { - options.first(where: { $0.id == selectedExecutableId })?.name ?? "None" - } - - var body: some View { - ZStack { - Color(.systemGroupedBackground).ignoresSafeArea() - - VStack(spacing: 16) { - Text("Choose Executable") - .font(.system(size: 24, weight: .bold)) - .padding(.top, 12) - - NavigationLink { - ExecutablePickerScreen( - selectedExecutableId: $selectedExecutableId, - options: $options - ) - } label: { - HStack { - Text("Executable") - .foregroundStyle(.primary) - Spacer() - Text(selectedExecutableName) - .foregroundStyle(.secondary) - .lineLimit(1) - Image(systemName: "chevron.right") - .foregroundStyle(.secondary) - } - .padding(.vertical, 14) - .padding(.horizontal, 16) - .background( - RoundedRectangle(cornerRadius: 14, style: .continuous) - .fill(Color(.secondarySystemGroupedBackground)) - ) - } - .buttonStyle(.plain) - .padding(.horizontal, 16) - - Spacer() - - Button { - onCreate() - } label: { - Text("Create") - .font(.system(size: 17, weight: .semibold)) - .frame(maxWidth: .infinity) - .padding(.vertical, 14) - .background(Color.accentColor) - .foregroundStyle(.white) - .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) - } - .padding(.horizontal, 16) - .padding(.bottom, 14) - } - } - .navigationTitle("") - .navigationBarTitleDisplayMode(.inline) - } -} - - #Preview { AppsView() } diff --git a/piPhone/Apps/Executable/AddExecutableView.swift b/piPhone/Apps/Executable/AddExecutableView.swift new file mode 100644 index 0000000..993f9dc --- /dev/null +++ b/piPhone/Apps/Executable/AddExecutableView.swift @@ -0,0 +1,69 @@ +// +// AddExecutableView.swift +// piPhone +// +// Created by Eris Leci on 1/25/26. +// + +import SwiftUI + +struct AddExecutableSheet: View { + @Environment(\.dismiss) private var dismiss + + @State private var name: String = "" + @State private var code: String = "" + + let onSave: (Executable) -> Void + + private var detectedLanguage: CodeLanguage { + CodeLanguage.fromExecutableName(name) + } + + var body: some View { + NavigationStack { + Form { + Section() { + TextField("Name (e.g. notes.py)", text: $name) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + } + + Section("Code") { + RunestoneView(text: $code, language: detectedLanguage) + .frame(minHeight: 550) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + } + } + .scrollDismissesKeyboard(.interactively) + .navigationTitle("Add Executable") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Save") { + let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + + let utf8Count = code.lengthOfBytes(using: .utf8) + + let newFile = Executable( + name: trimmed, + url: "files://\(trimmed)", + code: code, + language: detectedLanguage, + createdAt: Date(), + sizeBytes: utf8Count + ) + + onSave(newFile) + dismiss() + } + .disabled(name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + } + } + } + } +} diff --git a/piPhone/Apps/Executable/EmptyExecutableView.swift b/piPhone/Apps/Executable/EmptyExecutableView.swift new file mode 100644 index 0000000..18e3378 --- /dev/null +++ b/piPhone/Apps/Executable/EmptyExecutableView.swift @@ -0,0 +1,51 @@ +// +// EmptyExecutableView.swift +// piPhone +// +// Created by Eris Leci on 1/25/26. +// + +import SwiftUI + +struct EmptyStateView: View { + let systemImage: String + let title: String + let subtitle: String + + var body: some View { + VStack(spacing: 14) { + Image(systemName: systemImage) + .font(.system(size: 44, weight: .regular)) + .foregroundColor(Color.accentColor) + + Text(title) + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(.primary) + + Text(subtitle) + .font(.system(size: 15)) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 24) + } + .padding(.horizontal, 24) + } +} + +struct ExecutablesEmptyState: View { + var body: some View { + if #available(iOS 17.0, *) { + ContentUnavailableView( + "No Executables", + systemImage: "sparkles", + description: Text("Tap + to add your first executable.") + ) + } else { + EmptyStateView( + systemImage: "sparkles", + title: "No Executables", + subtitle: "Tap + to add your first executable." + ) + } + } +} diff --git a/piPhone/Apps/Executable/ExecutableRowView.swift b/piPhone/Apps/Executable/ExecutableRowView.swift new file mode 100644 index 0000000..70f5010 --- /dev/null +++ b/piPhone/Apps/Executable/ExecutableRowView.swift @@ -0,0 +1,54 @@ +// +// ExecutableRowView.swift +// piPhone +// +// Created by Eris Leci on 1/25/26. +// + + +import SwiftUI + +struct ExecutableRow: View { + let executable: Executable + let isSelected: Bool + private let iconName = "doc" + + private var metaText: String { + let date = executable.createdAt.formatted(date: .numeric, time: .omitted) + let size = ByteCountFormatter.string( + fromByteCount: Int64(executable.sizeBytes), + countStyle: .file + ) + return "\(date) \u{2022} \(size)" + } + + var body: some View { + HStack(spacing: 12) { + + Image(systemName: iconName) + .font(.system(size: 30)) + .foregroundStyle(isSelected ? Color.accentColor : Color(.secondaryLabel)) + .frame(width: 34) + + VStack(alignment: .leading, spacing: 4) { + Text(executable.name) + .font(.system(size: 17)) + .foregroundColor(isSelected ? .accentColor : .primary) + + Text(metaText) + .font(.system(size: 13)) + .foregroundColor(isSelected ? .accentColor : .secondary) + } + + Spacer() + if isSelected { + Image(systemName: "checkmark") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(.accentColor) + } + } + .padding(.vertical, 10) + .contentShape(Rectangle()) + } +} + diff --git a/piPhone/Apps/Executable/ExecutableView.swift b/piPhone/Apps/Executable/ExecutableView.swift index ffceec9..3381712 100644 --- a/piPhone/Apps/Executable/ExecutableView.swift +++ b/piPhone/Apps/Executable/ExecutableView.swift @@ -15,76 +15,108 @@ struct Executable: Identifiable, Equatable { var code: String var language: CodeLanguage + var createdAt: Date + var sizeBytes: Int + init( id: String = UUID().uuidString, name: String, url: String, code: String = "", - language: CodeLanguage = .plainText + language: CodeLanguage = .plainText, + createdAt: Date = Date(), + sizeBytes: Int = 0 ) { self.id = id self.name = name self.url = url self.code = code self.language = language - } -} - -// MARK: - ExecutableRow Model -struct ExecutableRow: View { - let title: String - let value: String - - var body: some View { - HStack { - Text("Choose a executable:") - .foregroundColor(.secondary) - - Spacer() - - Text(value) - .foregroundStyle(.secondary) - } + self.createdAt = createdAt + self.sizeBytes = sizeBytes } } // MARK: - Pick executable screen struct ExecutablePickerScreen: View { - @Binding var selectedExecutableId: String + @State private var searchText = "" + @State private var showAddExecutableSheet = false + @Binding var selectedExecutableName: String + @Binding var options: [Executable] - @Environment(\.dismiss) private var dismiss - @State private var showAddExecutableSheet = false - var body: some View { - List { - ForEach(options) { opt in - Button { - selectedExecutableId = (selectedExecutableId == opt.id) ? "none" : opt.id - } label: { - HStack { - Text(opt.name) - .foregroundStyle(.primary) + let onCreate: () -> Void - Spacer() + private var canCreate: Bool { selectedExecutableName != "none" } + + private var filteredExecutables: [Executable] { + let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines) + guard !q.isEmpty else { return options } + return options.filter { $0.name.localizedCaseInsensitiveContains(q) } + } - if opt.id == selectedExecutableId { - Image(systemName: "checkmark") - .foregroundStyle(.primary) + var body: some View { + ZStack { + List { + Section { + ForEach(filteredExecutables) { opt in + Button { + selectedExecutableName = + (selectedExecutableName == opt.name) ? "none" : opt.name + } label: { + ExecutableRow( + executable: opt, + isSelected: opt.name == selectedExecutableName + ) } + .buttonStyle(.plain) + .listRowBackground( + opt.name == selectedExecutableName + ? Color.accentColor.opacity(0.08) + : Color.clear + ) } - .contentShape(Rectangle()) } - .buttonStyle(.plain) + .listSectionSeparator(.hidden) + } + .listStyle(.plain) + + if options.isEmpty { + ExecutablesEmptyState() + } + + VStack { + Spacer() + Button { + onCreate() + dismiss() + } label: { + Text("Done") + .font(.system(size: 17, weight: .semibold)) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(canCreate ? Color.accentColor : Color(.systemGray3)) + .foregroundStyle(.white) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + .disabled(!canCreate) + .padding(.horizontal, 16) + .padding(.bottom, 14) } + .ignoresSafeArea(.keyboard, edges: .bottom) } - .navigationTitle("Executable") + .navigationTitle("Executables") + .searchable( + text: $searchText, + placement: .navigationBarDrawer(displayMode: .always), + prompt: "Search" + ) .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button { - showAddExecutableSheet = true - } label: { + Button { showAddExecutableSheet = true } label: { Image(systemName: "plus") } } @@ -92,64 +124,7 @@ struct ExecutablePickerScreen: View { .sheet(isPresented: $showAddExecutableSheet) { AddExecutableSheet { newExecutable in options.append(newExecutable) - selectedExecutableId = newExecutable.id - } - } - } -} - -// MARK: - Add Executable sheet -struct AddExecutableSheet: View { - @Environment(\.dismiss) private var dismiss - - @State private var name: String = "" - @State private var code: String = "" - - let onSave: (Executable) -> Void - - private var detectedLanguage: CodeLanguage { - CodeLanguage.fromExecutableName(name) - } - - // MARK: - View - var body: some View { - NavigationStack { - Form { - Section() { - TextField("Executable name (e.g. notes.py)", text: $name) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - } - - Section("Code") { - RunestoneView(text: $code, language: detectedLanguage) - .frame(minHeight: 550) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - } - } - .scrollDismissesKeyboard(.interactively) - .navigationTitle("Add Executable") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Cancel") { dismiss() } - } - - ToolbarItem(placement: .confirmationAction) { - Button("Save") { - let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return } - - let newFile = Executable( - name: trimmed, - url: "files://\(trimmed)", - code: code - ) - onSave(newFile) - dismiss() - } - .disabled(name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) - } + selectedExecutableName = newExecutable.name } } } diff --git a/piPhone/Apps/Executable/FileView.swift b/piPhone/Apps/Executable/FileView.swift new file mode 100644 index 0000000..dbd7bca --- /dev/null +++ b/piPhone/Apps/Executable/FileView.swift @@ -0,0 +1,18 @@ +// +// FileView.swift +// piPhone +// +// Created by Eris Leci on 1/25/26. +// + +import SwiftUI + +struct FileView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + FileView() +} From 716fe4c9abb090ef229148207ab5855efa275d68 Mon Sep 17 00:00:00 2001 From: Eris Leci Date: Tue, 27 Jan 2026 19:56:32 +0100 Subject: [PATCH 7/7] Format code with Make Format --- piPhone/Apps/AppsView.swift | 117 ++++++++++-------- .../Apps/Executable/AddExecutableView.swift | 2 +- .../Apps/Executable/ExecutableRowView.swift | 2 - piPhone/Apps/Executable/ExecutableView.swift | 12 +- piPhone/Apps/Executable/FileView.swift | 2 +- piPhone/Apps/Runestone/RunestoneView.swift | 5 +- 6 files changed, 76 insertions(+), 64 deletions(-) diff --git a/piPhone/Apps/AppsView.swift b/piPhone/Apps/AppsView.swift index 61c0ceb..1c57f13 100644 --- a/piPhone/Apps/AppsView.swift +++ b/piPhone/Apps/AppsView.swift @@ -29,7 +29,8 @@ struct AppsView: View { // MARK: - App Item Mock Data @State private var apps: [AppItem] = [ .init(title: "Hi", icon: "photo", executableName: "his.py", color: .blue), - .init(title: "Take a Break please!", icon: "timer", executableName: "break.sh", color: .green), + .init( + title: "Take a Break please!", icon: "timer", executableName: "break.sh", color: .green), ] private var columns: [GridItem] { @@ -38,7 +39,6 @@ struct AppsView: View { return Array(repeating: GridItem(.flexible(), spacing: 12), count: count) } - private var filteredApps: [AppItem] { let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines) guard !q.isEmpty else { return apps } @@ -49,7 +49,8 @@ struct AppsView: View { let trimmed = newTitle.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } - let iconToSave = newIcon.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + let iconToSave = + newIcon.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "pc" : newIcon @@ -68,7 +69,6 @@ struct AppsView: View { showAddSheet = false } - // MARK: view var body: some View { NavigationStack { @@ -91,7 +91,10 @@ struct AppsView: View { RoundedRectangle(cornerRadius: 24, style: .continuous) ) .contextMenu { - Button { } label: { Label("Edit", systemImage: "pencil") } + Button { + } label: { + Label("Edit", systemImage: "pencil") + } Button { let copy = AppItem( @@ -101,12 +104,16 @@ struct AppsView: View { color: item.color ) apps.append(copy) - } label: { Label("Duplicate", systemImage: "doc.on.doc") } + } label: { + Label("Duplicate", systemImage: "doc.on.doc") + } Button(role: .destructive) { appPendingDelete = item showDeleteAlert = true - } label: { Label("Delete", systemImage: "trash") } + } label: { + Label("Delete", systemImage: "trash") + } } } .buttonStyle(.plain) @@ -215,9 +222,8 @@ struct AppCard: View { } } - // MARK: - App Icon Cell View - struct AppIconCell: View { +struct AppIconCell: View { let item: AppItem var body: some View { @@ -262,7 +268,6 @@ struct AddAppSheet: View { @Binding var selectedColor: Color @State private var scrollMinY: CGFloat = 0 - let onAdd: () -> Void @State private var executableOptions: [Executable] = [ @@ -297,9 +302,9 @@ struct AddAppSheet: View { language: .plainText, createdAt: Date(), sizeBytes: 15_672 - ) + ), ] - + // MARK: - Colors private let focusColors: [Color] = [ .blue, @@ -311,31 +316,30 @@ struct AddAppSheet: View { .pink, .purple, .indigo, - .gray + .gray, ] - // MARK: - Icons private let icons: [String] = [ - "mic.fill", "message.fill", "phone.fill", - "video.fill", "envelope.fill", - "sun.max.fill", "moon.fill", - "cloud.fill", "cloud.rain.fill", - "folder.fill", "paperclip", - "link", "book.fill", - "trash.fill", "gearshape.fill", - "eraser.fill", "graduationcap.fill", - "ruler.fill", "backpack.fill", - "globe.europe.africa", "sun.min.fill", - "cloud.sun.fill", "sun.max", - "sunrise.fill", "moon", - "sparkles", "moon.stars", - "cloud", "cloud.heavyrain.fill", - "wind", "snowflake", "leaf", "bolt", - "keyboard.fill", "printer.fill", - "desktopcomputer", "macpro.gen2", "pc", - "airtag.fill", "macpro.gen3.fill", "display", - "iphone.gen2", + "mic.fill", "message.fill", "phone.fill", + "video.fill", "envelope.fill", + "sun.max.fill", "moon.fill", + "cloud.fill", "cloud.rain.fill", + "folder.fill", "paperclip", + "link", "book.fill", + "trash.fill", "gearshape.fill", + "eraser.fill", "graduationcap.fill", + "ruler.fill", "backpack.fill", + "globe.europe.africa", "sun.min.fill", + "cloud.sun.fill", "sun.max", + "sunrise.fill", "moon", + "sparkles", "moon.stars", + "cloud", "cloud.heavyrain.fill", + "wind", "snowflake", "leaf", "bolt", + "keyboard.fill", "printer.fill", + "desktopcomputer", "macpro.gen2", "pc", + "airtag.fill", "macpro.gen3.fill", "display", + "iphone.gen2", ] private let columns = Array(repeating: GridItem(.flexible(), spacing: 10), count: 6) @@ -343,15 +347,13 @@ struct AddAppSheet: View { private var canGoNext: Bool { !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - + private struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } } @Environment(\.dismiss) private var dismiss - - var body: some View { NavigationStack { ZStack { @@ -388,21 +390,23 @@ struct AddAppSheet: View { ) .padding(.horizontal, 22) - let cellSize: CGFloat = 44 let ringWidth: CGFloat = 3 let gap: CGFloat = 3 - let outerSize = cellSize + 2*(gap + ringWidth) + let outerSize = cellSize + 2 * (gap + ringWidth) let bg = Color(.systemGroupedBackground) - let columns: [GridItem] = Array(repeating: GridItem(.fixed(cellSize), spacing: 17), count: 6) - + let columns: [GridItem] = Array( + repeating: GridItem(.fixed(cellSize), spacing: 17), count: 6) + LazyVGrid(columns: columns, spacing: 20) { ForEach(focusColors.indices, id: \.self) { index in let color = focusColors[index] - Button { selectedColor = color } label: { + Button { + selectedColor = color + } label: { ZStack { Circle() .fill(color) @@ -411,12 +415,15 @@ struct AddAppSheet: View { if selectedColor == color { Circle() .stroke(bg, lineWidth: ringWidth + gap * 2) - .frame(width: cellSize + gap * 2, height: cellSize + gap * 2) + .frame( + width: cellSize + gap * 2, + height: cellSize + gap * 2) Circle() .stroke(Color.blue, lineWidth: ringWidth) - .frame(width: cellSize + 2*(gap + ringWidth/2), - height: cellSize + 2*(gap + ringWidth/2)) + .frame( + width: cellSize + 2 * (gap + ringWidth / 2), + height: cellSize + 2 * (gap + ringWidth / 2)) } } .frame(width: outerSize, height: outerSize) @@ -424,12 +431,16 @@ struct AddAppSheet: View { } ForEach(icons, id: \.self) { name in - Button { icon = name } label: { + Button { + icon = name + } label: { ZStack { Circle() - .fill(icon == name - ? Color.blue.opacity(0.20) - : Color(.secondarySystemGroupedBackground)) + .fill( + icon == name + ? Color.blue.opacity(0.20) + : Color(.secondarySystemGroupedBackground) + ) .frame(width: cellSize, height: cellSize) Image(systemName: name) @@ -443,12 +454,15 @@ struct AddAppSheet: View { if icon == name { Circle() .stroke(bg, lineWidth: ringWidth + gap * 2) - .frame(width: cellSize + gap * 2, height: cellSize + gap * 2) + .frame( + width: cellSize + gap * 2, + height: cellSize + gap * 2) Circle() .stroke(Color.blue, lineWidth: ringWidth) - .frame(width: cellSize + 2*(gap + ringWidth/2), - height: cellSize + 2*(gap + ringWidth/2)) + .frame( + width: cellSize + 2 * (gap + ringWidth / 2), + height: cellSize + 2 * (gap + ringWidth / 2)) } } .frame(width: outerSize, height: outerSize) @@ -458,7 +472,6 @@ struct AddAppSheet: View { .padding(.horizontal, 16) .padding(.top, 6) - Spacer().frame(height: 90) } } diff --git a/piPhone/Apps/Executable/AddExecutableView.swift b/piPhone/Apps/Executable/AddExecutableView.swift index 993f9dc..5237230 100644 --- a/piPhone/Apps/Executable/AddExecutableView.swift +++ b/piPhone/Apps/Executable/AddExecutableView.swift @@ -22,7 +22,7 @@ struct AddExecutableSheet: View { var body: some View { NavigationStack { Form { - Section() { + Section { TextField("Name (e.g. notes.py)", text: $name) .textInputAutocapitalization(.never) .autocorrectionDisabled() diff --git a/piPhone/Apps/Executable/ExecutableRowView.swift b/piPhone/Apps/Executable/ExecutableRowView.swift index 70f5010..0892e07 100644 --- a/piPhone/Apps/Executable/ExecutableRowView.swift +++ b/piPhone/Apps/Executable/ExecutableRowView.swift @@ -5,7 +5,6 @@ // Created by Eris Leci on 1/25/26. // - import SwiftUI struct ExecutableRow: View { @@ -51,4 +50,3 @@ struct ExecutableRow: View { .contentShape(Rectangle()) } } - diff --git a/piPhone/Apps/Executable/ExecutableView.swift b/piPhone/Apps/Executable/ExecutableView.swift index 3381712..6a6bdf0 100644 --- a/piPhone/Apps/Executable/ExecutableView.swift +++ b/piPhone/Apps/Executable/ExecutableView.swift @@ -42,14 +42,14 @@ struct ExecutablePickerScreen: View { @State private var searchText = "" @State private var showAddExecutableSheet = false @Binding var selectedExecutableName: String - + @Binding var options: [Executable] @Environment(\.dismiss) private var dismiss let onCreate: () -> Void private var canCreate: Bool { selectedExecutableName != "none" } - + private var filteredExecutables: [Executable] { let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines) guard !q.isEmpty else { return options } @@ -73,8 +73,8 @@ struct ExecutablePickerScreen: View { .buttonStyle(.plain) .listRowBackground( opt.name == selectedExecutableName - ? Color.accentColor.opacity(0.08) - : Color.clear + ? Color.accentColor.opacity(0.08) + : Color.clear ) } } @@ -116,7 +116,9 @@ struct ExecutablePickerScreen: View { .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .topBarTrailing) { - Button { showAddExecutableSheet = true } label: { + Button { + showAddExecutableSheet = true + } label: { Image(systemName: "plus") } } diff --git a/piPhone/Apps/Executable/FileView.swift b/piPhone/Apps/Executable/FileView.swift index dbd7bca..b8cf97b 100644 --- a/piPhone/Apps/Executable/FileView.swift +++ b/piPhone/Apps/Executable/FileView.swift @@ -9,7 +9,7 @@ import SwiftUI struct FileView: View { var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + Text( /*@START_MENU_TOKEN@*/"Hello, World!" /*@END_MENU_TOKEN@*/) } } diff --git a/piPhone/Apps/Runestone/RunestoneView.swift b/piPhone/Apps/Runestone/RunestoneView.swift index f19f69e..21bd008 100644 --- a/piPhone/Apps/Runestone/RunestoneView.swift +++ b/piPhone/Apps/Runestone/RunestoneView.swift @@ -5,13 +5,12 @@ // Created by Eris Leci on 1/12/26. // -import SwiftUI import Runestone -import UIKit - +import SwiftUI import TreeSitterJavaRunestone import TreeSitterJavaScriptRunestone import TreeSitterPythonRunestone +import UIKit struct RunestoneView: UIViewRepresentable { @Binding var text: String