Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
742 changes: 317 additions & 425 deletions piPhone/Apps/AppsView.swift

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions piPhone/Apps/Executable/AddExecutableView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
51 changes: 51 additions & 0 deletions piPhone/Apps/Executable/EmptyExecutableView.swift
Original file line number Diff line number Diff line change
@@ -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."
)
}
}
}
52 changes: 52 additions & 0 deletions piPhone/Apps/Executable/ExecutableRowView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// 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())
}
}
133 changes: 133 additions & 0 deletions piPhone/Apps/Executable/ExecutableView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//
// ExecutableView.swift
// piPhone
//
// Created by Gentris Leci on 1/12/26.
//

import SwiftUI

// MARK: - Executable Model
struct Executable: Identifiable, Equatable {
let id: String
var name: String
var url: String
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,
createdAt: Date = Date(),
sizeBytes: Int = 0
) {
self.id = id
self.name = name
self.url = url
self.code = code
self.language = language
self.createdAt = createdAt
self.sizeBytes = sizeBytes
}
}

// MARK: - Pick executable screen
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 }
return options.filter { $0.name.localizedCaseInsensitiveContains(q) }
}

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
)
}
}
.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("Executables")
.searchable(
text: $searchText,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Search"
)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showAddExecutableSheet = true
} label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showAddExecutableSheet) {
AddExecutableSheet { newExecutable in
options.append(newExecutable)
selectedExecutableName = newExecutable.name
}
}
}
}
18 changes: 18 additions & 0 deletions piPhone/Apps/Executable/FileView.swift
Original file line number Diff line number Diff line change
@@ -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()
}
Loading