Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

app: Handle iCloud files gracefully #846

Merged
merged 2 commits into from
May 25, 2023
Merged
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
6 changes: 6 additions & 0 deletions Code.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@
9F046C3B29223D1600BDE4E9 /* RemoteExecutionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F046C3929223D1600BDE4E9 /* RemoteExecutionExtension.swift */; };
9F046C3D29223E1500BDE4E9 /* CodeAppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F046C3C29223E1500BDE4E9 /* CodeAppExtension.swift */; };
9F046C3E29223E1500BDE4E9 /* CodeAppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F046C3C29223E1500BDE4E9 /* CodeAppExtension.swift */; };
9F29759C2A1F488E007FDB3D /* deferredRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F29759B2A1F488E007FDB3D /* deferredRendering.swift */; };
9F29759D2A1F488E007FDB3D /* deferredRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F29759B2A1F488E007FDB3D /* deferredRendering.swift */; };
9F37391E293F37F8006886C1 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37391D293F37F8006886C1 /* ThemeManager.swift */; };
9F37391F293F37F8006886C1 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37391D293F37F8006886C1 /* ThemeManager.swift */; };
9F3C2DC82918A31000BFF14C /* hiddenScrollableContentBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3C2DC72918A31000BFF14C /* hiddenScrollableContentBackground.swift */; };
Expand Down Expand Up @@ -1693,6 +1695,7 @@
9F046C3429222D8E00BDE4E9 /* ExtensionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManager.swift; sourceTree = "<group>"; };
9F046C3929223D1600BDE4E9 /* RemoteExecutionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteExecutionExtension.swift; sourceTree = "<group>"; };
9F046C3C29223E1500BDE4E9 /* CodeAppExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeAppExtension.swift; sourceTree = "<group>"; };
9F29759B2A1F488E007FDB3D /* deferredRendering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = deferredRendering.swift; sourceTree = "<group>"; };
9F37391D293F37F8006886C1 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
9F3C2DC72918A31000BFF14C /* hiddenScrollableContentBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hiddenScrollableContentBackground.swift; sourceTree = "<group>"; };
9F3C2DE92918E19B00BFF14C /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2464,6 +2467,7 @@
children = (
9F3C2DC72918A31000BFF14C /* hiddenScrollableContentBackground.swift */,
94A347BC293DE24B00A59658 /* hiddenSystemOverlays.swift */,
9F29759B2A1F488E007FDB3D /* deferredRendering.swift */,
);
path = Modifiers;
sourceTree = "<group>";
Expand Down Expand Up @@ -3008,6 +3012,7 @@
9F3C2DEB2918E19B00BFF14C /* Theme.swift in Sources */,
94196957280316C7008AAEB2 /* SettingsView.swift in Sources */,
94196958280316C7008AAEB2 /* wasm.swift in Sources */,
9F29759D2A1F488E007FDB3D /* deferredRendering.swift in Sources */,
94196959280316C7008AAEB2 /* SearchManager.swift in Sources */,
9419695A280316C7008AAEB2 /* ArchiveDir.swift in Sources */,
9419695B280316C7008AAEB2 /* MarkdownView.swift in Sources */,
Expand Down Expand Up @@ -3162,6 +3167,7 @@
9F3C2DEA2918E19B00BFF14C /* Theme.swift in Sources */,
94A777F6257B9260008FE7B2 /* SettingsView.swift in Sources */,
94CF58E1265E8A4B00CB6A4B /* wasm.swift in Sources */,
9F29759C2A1F488E007FDB3D /* deferredRendering.swift in Sources */,
947BF349262453040015DAEB /* SearchManager.swift in Sources */,
94A7781E257BC473008FE7B2 /* ArchiveDir.swift in Sources */,
94A5682B257CBDE4008A6530 /* MarkdownView.swift in Sources */,
Expand Down
22 changes: 18 additions & 4 deletions CodeApp/Managers/FileSystem/Local/LocalFileSystemProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,24 @@ class LocalFileSystemProvider: FileSystemProvider {
}

func contents(at: URL, completionHandler: @escaping (Data?, Error?) -> Void) {
do {
let data = try Data(contentsOf: at)
completionHandler(data, nil)
} catch {
// Using a FileCoordinator allows downloading iCloud file using a completion handler pattern
// Reference: https://developer.apple.com/forums/thread/681520
var error: NSError?
let fileCoordinator = NSFileCoordinator()

fileCoordinator.coordinate(
readingItemAt: at, options: .withoutChanges, error: &error
) { newURL in
do {
let data = try Data(contentsOf: newURL)
completionHandler(data, nil)
} catch {
completionHandler(nil, error)
}
return
}

if let error {
completionHandler(nil, error)
}
}
Expand Down
19 changes: 15 additions & 4 deletions CodeApp/Managers/MainApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -753,17 +753,28 @@ class MainApp: ObservableObject {
urlQueue.append(url)
throw AppError.editorIsNotReady
}
var url = url
if url.pathExtension == "icloud" {
let originalFileName = String(
url.lastPathComponent.dropFirst(".".count).dropLast(".icloud".count))
url = url.deletingLastPathComponent().appendingPathComponent(originalFileName)
}
if let existingEditor = try? openEditorForURL(url: url) {
return existingEditor
}
// TODO: Avoid reading the same file twice
if let textEditor = try? await createTextEditorFromURL(url: url) {
do {
let textEditor = try await createTextEditorFromURL(url: url)
appendAndFocusNewEditor(editor: textEditor, alwaysInNewTab: alwaysInNewTab)
return textEditor
} catch NSFileProviderError.serverUnreachable {
throw NSFileProviderError(.serverUnreachable)
} catch {
// Otherwise, fallback to using extensions
let editor = try createExtensionEditorFromURL(url: url)
appendAndFocusNewEditor(editor: editor, alwaysInNewTab: alwaysInNewTab)
return editor
}
let editor = try createExtensionEditorFromURL(url: url)
appendAndFocusNewEditor(editor: editor, alwaysInNewTab: alwaysInNewTab)
return editor
}

@MainActor
Expand Down
2 changes: 1 addition & 1 deletion CodeApp/Managers/WebViewBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class WebViewBase: KBWebViewBase {
let newMethod = class_getInstanceMethod(
WebViewBase.self, #selector(WebViewBase.getCustomInputAccessoryView))
class_addMethod(
newClass.self, #selector(getter:UIResponder.inputAccessoryView),
newClass.self, #selector(getter: UIResponder.inputAccessoryView),
method_getImplementation(newMethod!), method_getTypeEncoding(newMethod!))

objc_registerClassPair(newClass!)
Expand Down
49 changes: 49 additions & 0 deletions CodeApp/Modifiers/deferredRendering.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// deferredRendering.swift
// Code
//
// Created by Ken Chung on 25/5/2023.
//

import SwiftUI

// Reference: https://stackoverflow.com/questions/59731724/swiftui-show-custom-view-with-delay

/// A ViewModifier that defers its rendering until after the provided threshold surpasses
private struct DeferredViewModifier: ViewModifier {

// MARK: API

let threshold: Double

// MARK: - ViewModifier

func body(content: Content) -> some View {
_content(content)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + threshold) {
self.shouldRender = true
}
}
}

// MARK: - Private

@ViewBuilder
private func _content(_ content: Content) -> some View {
if shouldRender {
content
} else {
content
.hidden()
}
}

@State private var shouldRender = false
}

extension View {
func deferredRendering(for seconds: Double) -> some View {
modifier(DeferredViewModifier(threshold: seconds))
}
}
12 changes: 5 additions & 7 deletions CodeApp/Views/EditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,16 @@ struct EditorView: View {
.background(Color.init(id: "editor.background"))
}
} else if let editor = App.activeEditor {

editor.view

VStack {
InfinityProgressView(enabled: App.workSpaceStorage.editorIsBusy)
Spacer()
}

} else {
DescriptionText("You don't have any open editor.")
}

VStack {
InfinityProgressView(enabled: App.workSpaceStorage.editorIsBusy)
Spacer()
}

}
.onReceive(
NotificationCenter.default.publisher(
Expand Down
4 changes: 3 additions & 1 deletion CodeApp/Views/InfiniteProgressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SwiftUI
struct InfinityProgressView: View {

@State private var atLeading: Bool = false
let enabled: Bool
var enabled: Bool

private var repeatingAnimation: Animation {
Animation
Expand All @@ -26,10 +26,12 @@ struct InfinityProgressView: View {
.frame(maxWidth: 30, maxHeight: 3)
.offset(x: atLeading ? geometry.size.width - 30 : 0, y: 0)
.onAppear {
atLeading = false
withAnimation(repeatingAnimation) {
atLeading.toggle()
}
}
.deferredRendering(for: 0.5)
}
}
.frame(height: 8)
Expand Down