Skip to content

Commit a7009ed

Browse files
committed
major preview refinement, and login flow fixes
1 parent a882808 commit a7009ed

File tree

5 files changed

+426
-85
lines changed

5 files changed

+426
-85
lines changed

Django Files/Django_FilesApp.swift

Lines changed: 113 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ struct Django_FilesApp: App {
4848
@State private var hasExistingSessions = false
4949
@State private var isLoading = true
5050
@State private var selectedTab: TabViewWindow.Tab = .files
51+
@State private var showingServerConfirmation = false
52+
@State private var pendingAuthURL: URL? = nil
53+
@State private var pendingAuthSignature: String? = nil
5154

5255
init() {
5356
// print("📱 Setting up WebSocketToastObserver")
@@ -62,18 +65,40 @@ struct Django_FilesApp: App {
6265
.onAppear {
6366
checkForExistingSessions()
6467
}
65-
} else if hasExistingSessions {
66-
TabViewWindow(sessionManager: sessionManager, selectedTab: $selectedTab)
67-
} else {
68+
} else if !hasExistingSessions {
6869
SessionEditor(onBoarding: true, session: nil, onSessionCreated: { newSession in
6970
sessionManager.selectedSession = newSession
7071
hasExistingSessions = true
7172
})
73+
} else if sessionManager.selectedSession == nil {
74+
NavigationStack {
75+
ServerSelector(selectedSession: $sessionManager.selectedSession)
76+
.navigationTitle("Select Server")
77+
}
78+
} else {
79+
TabViewWindow(sessionManager: sessionManager, selectedTab: $selectedTab)
7280
}
7381
}
7482
.onOpenURL { url in
7583
handleDeepLink(url)
7684
}
85+
.sheet(isPresented: $showingServerConfirmation) {
86+
ServerConfirmationView(
87+
serverURL: $pendingAuthURL,
88+
signature: $pendingAuthSignature,
89+
onConfirm: { setAsDefault in
90+
Task {
91+
await handleServerConfirmation(confirmed: true, setAsDefault: setAsDefault)
92+
}
93+
},
94+
onCancel: {
95+
Task {
96+
await handleServerConfirmation(confirmed: false, setAsDefault: false)
97+
}
98+
},
99+
context: sharedModelContainer.mainContext
100+
)
101+
}
77102
}
78103
.modelContainer(sharedModelContainer)
79104
#if os(macOS)
@@ -97,7 +122,7 @@ struct Django_FilesApp: App {
97122
case "authorize":
98123
deepLinkAuth(components)
99124
case "serverlist":
100-
selectedTab = .serverList
125+
selectedTab = .settings
101126
case "filelist":
102127
handleFileListDeepLink(components)
103128
default:
@@ -149,40 +174,111 @@ struct Django_FilesApp: App {
149174
do {
150175
let existingSessions = try context.fetch(descriptor)
151176
if let existingSession = existingSessions.first(where: { $0.url == serverURL.absoluteString }) {
152-
// If session exists, update it on the main thread
177+
// If session exists, just select it and update UI
153178
await MainActor.run {
154179
sessionManager.selectedSession = existingSession
155180
hasExistingSessions = true
181+
ToastManager.shared.showToast(message: "Connected to existing server \(existingSession.url)")
156182
}
157183
return
158184
}
159185

160-
// If no existing session, create and authenticate a new one
161-
if let newSession = await sessionManager.createAndAuthenticateSession(
162-
url: serverURL,
163-
signature: signature,
164-
context: context
165-
) {
166-
// Update the UI on the main thread
167-
await MainActor.run {
168-
sessionManager.selectedSession = newSession
169-
hasExistingSessions = true
170-
ToastManager.shared.showToast(message: "Successfully logged into \(newSession.url)")
171-
}
186+
// No existing session, show confirmation dialog
187+
await MainActor.run {
188+
pendingAuthURL = serverURL
189+
pendingAuthSignature = signature
190+
showingServerConfirmation = true
172191
}
173192
} catch {
174193
print("Error checking for existing sessions: \(error)")
175194
}
176195
}
177196
}
178197

198+
private func handleServerConfirmation(confirmed: Bool, setAsDefault: Bool) async {
199+
guard let serverURL = pendingAuthURL,
200+
let signature = pendingAuthSignature else {
201+
return
202+
}
203+
204+
// If user cancelled, just clear the pending data and return
205+
if !confirmed {
206+
pendingAuthURL = nil
207+
pendingAuthSignature = nil
208+
return
209+
}
210+
211+
// Create and authenticate the new session
212+
let context = sharedModelContainer.mainContext
213+
let descriptor = FetchDescriptor<DjangoFilesSession>()
214+
215+
do {
216+
let existingSessions = try context.fetch(descriptor)
217+
218+
// If no existing session, create and authenticate a new one
219+
if let newSession = await sessionManager.createAndAuthenticateSession(
220+
url: serverURL,
221+
signature: signature,
222+
context: context
223+
) {
224+
// Update the UI on the main thread
225+
await MainActor.run {
226+
if setAsDefault {
227+
// Reset all other sessions to not be default
228+
for session in existingSessions {
229+
session.defaultSession = false
230+
}
231+
newSession.defaultSession = true
232+
}
233+
sessionManager.selectedSession = newSession
234+
hasExistingSessions = true
235+
selectedTab = .files
236+
ToastManager.shared.showToast(message: "Successfully logged into \(newSession.url)")
237+
}
238+
}
239+
} catch {
240+
ToastManager.shared.showToast(message: "Problem signing into server \(error)")
241+
print("Error creating new session: \(error)")
242+
}
243+
244+
// Clear pending auth data
245+
pendingAuthURL = nil
246+
pendingAuthSignature = nil
247+
}
248+
249+
private func checkDefaultServer() {
250+
let context = sharedModelContainer.mainContext
251+
let descriptor = FetchDescriptor<DjangoFilesSession>()
252+
253+
Task {
254+
do {
255+
let sessions = try context.fetch(descriptor)
256+
await MainActor.run {
257+
if sessionManager.selectedSession == nil {
258+
if let defaultSession = sessions.first(where: { $0.defaultSession }) {
259+
sessionManager.selectedSession = defaultSession
260+
selectedTab = .files
261+
} else {
262+
selectedTab = .settings
263+
}
264+
}
265+
}
266+
} catch {
267+
print("Error checking for default server: \(error)")
268+
}
269+
}
270+
}
271+
179272
private func checkForExistingSessions() {
180273
let context = sharedModelContainer.mainContext
181274
let descriptor = FetchDescriptor<DjangoFilesSession>()
182275

183276
do {
184277
let sessionsCount = try context.fetchCount(descriptor)
185278
hasExistingSessions = sessionsCount > 0
279+
if hasExistingSessions {
280+
checkDefaultServer()
281+
}
186282
isLoading = false // Set loading to false after check completes
187283
} catch {
188284
print("Error checking for existing sessions: \(error)")

Django Files/Views/Preview.swift

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,87 @@ struct ContentPreview: View {
7979
// Text Preview
8080
private var textPreview: some View {
8181
ScrollView {
82-
if let content = content, let text = String(data: content, encoding: .utf8) {
83-
CodeText(text)
84-
.padding()
85-
} else {
86-
Text("Unable to decode text content")
87-
.foregroundColor(.red)
82+
ZStack {
83+
if let content = content, let text = String(data: content, encoding: .utf8) {
84+
CodeText(text)
85+
.highlightLanguage(determineLanguage(from: mimeType, fileName: fileURL.lastPathComponent))
86+
.padding()
87+
} else {
88+
Text("Unable to decode text content")
89+
.foregroundColor(.red)
90+
}
91+
}
92+
.padding(.top, 40)
93+
}
94+
}
95+
96+
// Helper function to determine the highlight language based on file type
97+
private func determineLanguage(from mimeType: String, fileName: String) -> HighlightLanguage {
98+
let fileExtension = (fileName as NSString).pathExtension.lowercased()
99+
100+
switch fileExtension {
101+
case "swift":
102+
return .swift
103+
case "py", "python":
104+
return .python
105+
case "js", "javascript":
106+
return .javaScript
107+
case "java":
108+
return .java
109+
case "cpp", "c", "h", "hpp":
110+
return .cPlusPlus
111+
case "html":
112+
return .html
113+
case "css":
114+
return .css
115+
case "json":
116+
return .json
117+
case "md", "markdown":
118+
return .markdown
119+
case "sh", "bash":
120+
return .bash
121+
case "rb", "ruby":
122+
return .ruby
123+
case "go":
124+
return .go
125+
case "rs":
126+
return .rust
127+
case "php":
128+
return .php
129+
case "sql":
130+
return .sql
131+
case "ts", "typescript":
132+
return .typeScript
133+
case "yaml", "yml":
134+
return .yaml
135+
default:
136+
// For plain text or unknown types
137+
if mimeType == "text/plain" {
138+
return .plaintext
139+
}
140+
// Try to determine from mime type if extension didn't match
141+
let mimeSubtype = mimeType.split(separator: "/").last?.lowercased() ?? ""
142+
switch mimeSubtype {
143+
case "javascript":
144+
return .javaScript
145+
case "python":
146+
return .python
147+
case "java":
148+
return .java
149+
case "html":
150+
return .html
151+
case "css":
152+
return .css
153+
case "json":
154+
return .json
155+
case "markdown":
156+
return .markdown
157+
case "xml":
158+
return .html
159+
default:
160+
return .plaintext
88161
}
89162
}
90-
.padding(.top, 35)
91163
}
92164

93165
// Image Preview
@@ -838,20 +910,21 @@ struct FilePreviewView: View {
838910
}
839911
.background(.ultraThinMaterial)
840912
.frame(width: 32, height: 32)
841-
.cornerRadius(8)
913+
.cornerRadius(16)
842914
.padding(.leading, 15)
843915
Spacer()
844916
Text(file.name)
845917
.padding(5)
846918
.font(.headline)
847919
.lineLimit(1)
848-
.shadow(color: .black, radius: 3)
920+
.foregroundColor(file.mime.starts(with: "text") ? .primary : .white)
921+
.shadow(color: .black, radius: file.mime.starts(with: "text") ? 0 : 3)
849922
Spacer()
850923
Menu {
851924
fileContextMenu(for: file, isPreviewing: true, isPrivate: file.private, expirationText: $expirationText, passwordText: $passwordText, fileNameText: $fileNameText)
852925
.padding()
853926
} label: {
854-
Image(systemName: "ellipsis.circle")
927+
Image(systemName: "ellipsis")
855928
.font(.system(size: 20))
856929
.padding()
857930
}
@@ -861,6 +934,15 @@ struct FilePreviewView: View {
861934
.cornerRadius(16)
862935
.padding(.trailing, 10)
863936
}
937+
.padding(.vertical, 8)
938+
.frame(maxWidth: .infinity)
939+
.background {
940+
if file.mime.starts(with: "text") {
941+
Rectangle()
942+
.fill(.ultraThinMaterial)
943+
.ignoresSafeArea()
944+
}
945+
}
864946
Spacer()
865947
HStack {
866948
Spacer()

0 commit comments

Comments
 (0)