app fixes

This commit is contained in:
2025-10-20 00:04:18 +02:00
parent 7c492627f0
commit cf5a848741
3 changed files with 150 additions and 58 deletions

View File

@@ -268,6 +268,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "cardboy-companion/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Cardboy;
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Cardboy Companion needs Bluetooth to sync time with your handheld.";
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "Cardboy Companion needs Bluetooth to sync time with your handheld.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -304,6 +305,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "cardboy-companion/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = Cardboy;
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Cardboy Companion needs Bluetooth to sync time with your handheld.";
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "Cardboy Companion needs Bluetooth to sync time with your handheld.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;

View File

@@ -98,6 +98,68 @@ private struct FileManagerTabView: View {
@Binding var shareURL: URL?
@Binding var errorWrapper: ErrorWrapper?
@State private var navigationPath: [String] = []
var body: some View {
ZStack {
NavigationStack(path: $navigationPath) {
DirectoryView(
path: "/",
navigationPath: $navigationPath,
shareURL: $shareURL,
errorWrapper: $errorWrapper
)
.navigationDestination(for: String.self) { destination in
DirectoryView(
path: destination,
navigationPath: $navigationPath,
shareURL: $shareURL,
errorWrapper: $errorWrapper
)
}
}
if let operation = manager.activeFileOperation {
FileOperationHUD(operation: operation)
}
if manager.connectionState != .ready {
ConnectionOverlay(
state: manager.connectionState,
statusMessage: manager.statusMessage,
retryAction: manager.forceRescan
)
}
}
.onAppear {
if manager.currentDirectory != "/" {
manager.changeDirectory(to: "/")
}
navigationPath = stackForPath(manager.currentDirectory)
}
.onChange(of: manager.currentDirectory) { newValue in
let desired = stackForPath(newValue)
if desired != navigationPath {
navigationPath = desired
}
}
.onChange(of: navigationPath) { newValue in
let target = newValue.last ?? "/"
if target != manager.currentDirectory {
manager.changeDirectory(to: target)
}
}
}
}
private struct DirectoryView: View {
let path: String
@Binding var navigationPath: [String]
@Binding var shareURL: URL?
@Binding var errorWrapper: ErrorWrapper?
@EnvironmentObject private var manager: TimeSyncManager
@State private var showingImporter = false
@State private var showingNewFolderSheet = false
@State private var showingRenameSheet = false
@@ -105,8 +167,18 @@ private struct FileManagerTabView: View {
@State private var renameText = ""
@State private var renameTarget: TimeSyncManager.RemoteFileEntry?
private var pathComponents: [String] {
manager.currentDirectory.split(separator: "/").map(String.init)
private var pathSegments: [(name: String, fullPath: String)] {
var segments: [(String, String)] = []
var current = ""
for component in path.split(separator: "/").map(String.init) {
current += "/" + component
segments.append((component, current))
}
return segments
}
private var displayTitle: String {
pathSegments.last?.name ?? "Files"
}
var body: some View {
@@ -117,18 +189,16 @@ private struct FileManagerTabView: View {
.font(.headline)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
Button(action: { manager.changeDirectory(to: "/") }) {
Button(action: { navigationPath = [] }) {
Text("/")
}
.buttonStyle(.bordered)
ForEach(pathComponents.indices, id: \.self) { index in
let component = pathComponents[index]
ForEach(Array(pathSegments.enumerated()), id: \.element.fullPath) { index, segment in
Button(action: {
let path = "/" + pathComponents.prefix(index + 1).joined(separator: "/")
manager.changeDirectory(to: path)
navigationPath = Array(pathSegments.prefix(index + 1).map(\.fullPath))
}) {
Text(component)
Text(segment.name)
}
.buttonStyle(.bordered)
}
@@ -139,17 +209,30 @@ private struct FileManagerTabView: View {
List {
ForEach(manager.directoryEntries) { entry in
FileRow(entry: entry)
.contentShape(Rectangle())
.onTapGesture {
if entry.isDirectory {
manager.enter(directory: entry)
}
NavigationLink(value: entry.path) {
FileRow(entry: entry)
}
.contextMenu {
if entry.isDirectory {
Button("Open", action: { manager.enter(directory: entry) })
Button("Open") {
navigationPath = stackForPath(entry.path)
}
Button("Rename") {
renameTarget = entry
renameText = entry.name
showingRenameSheet = true
}
Button(role: .destructive) {
manager.delete(entry: entry)
} label: {
Text("Delete")
}
}
} else {
FileRow(entry: entry)
.contextMenu {
Button("Download") {
manager.download(entry: entry) { result in
switch result {
@@ -160,7 +243,6 @@ private struct FileManagerTabView: View {
}
}
}
}
Button("Rename") {
renameTarget = entry
@@ -176,15 +258,16 @@ private struct FileManagerTabView: View {
}
}
}
}
.listStyle(.plain)
HStack {
HStack(spacing: 12) {
Menu {
Button {
showingImporter = true
} label: {
Label("Upload", systemImage: "square.and.arrow.up")
Label("Upload File", systemImage: "square.and.arrow.up")
}
.buttonStyle(.bordered)
Button {
showingNewFolderSheet = true
@@ -192,14 +275,11 @@ private struct FileManagerTabView: View {
} label: {
Label("New Folder", systemImage: "folder.badge.plus")
}
.buttonStyle(.bordered)
Button {
manager.navigateUp()
} label: {
Label("Up", systemImage: "arrow.up")
Label("Actions", systemImage: "ellipsis.circle")
}
.buttonStyle(.bordered)
.controlSize(.large)
Spacer()
@@ -208,21 +288,18 @@ private struct FileManagerTabView: View {
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
}
.buttonStyle(.bordered)
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
.padding([.horizontal, .bottom])
}
if let operation = manager.activeFileOperation {
FileOperationHUD(operation: operation)
}
if manager.connectionState != .ready {
ConnectionOverlay(
state: manager.connectionState,
statusMessage: manager.statusMessage,
retryAction: manager.forceRescan
)
.navigationTitle(displayTitle)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
if manager.currentDirectory != path {
manager.changeDirectory(to: path)
}
}
.fileImporter(
@@ -297,6 +374,17 @@ private struct FileManagerTabView: View {
}
}
private func stackForPath(_ path: String) -> [String] {
guard path != "/" else { return [] }
var stack: [String] = []
var current = ""
for component in path.split(separator: "/").map(String.init) {
current += "/" + component
stack.append(current)
}
return stack
}
private struct FileRow: View {
let entry: TimeSyncManager.RemoteFileEntry

View File

@@ -191,9 +191,11 @@ final class TimeSyncManager: NSObject, ObservableObject {
payload.append(isDst)
payload.append(UInt8(0)) // Reserved byte
connectionState = .ready
statusMessage = "Sending current time…"
peripheral.writeValue(payload, for: characteristic, type: .withResponse)
lastSyncDate = now
connectionState = .ready
let timeString = DateFormatter.localizedString(from: now, dateStyle: .none, timeStyle: .medium)
statusMessage = "Time synced at \(timeString)."
}
// MARK: - File operations exposed to UI