Skip to content

Commit e4fa1ca

Browse files
authored
Fixes split view crash, restores rotation functionality to iphone (#32)
* Fixes split view crash, restores rotation functionality to iphone * cleanup * fix and format
1 parent bea6d51 commit e4fa1ca

File tree

4 files changed

+284
-225
lines changed

4 files changed

+284
-225
lines changed

Django Files.xcodeproj/project.pbxproj

+4-2
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@
436436
ENABLE_PREVIEWS = YES;
437437
GENERATE_INFOPLIST_FILE = YES;
438438
INFOPLIST_FILE = "Django Files/Info.plist";
439+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
439440
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
440441
INFOPLIST_KEY_NSCameraUsageDescription = "This lets you capture and upload photos or video files to your Django Files.";
441442
INFOPLIST_KEY_NSFileProviderDomainUsageDescription = "This lets you upload files to your Django Files.";
@@ -444,7 +445,7 @@
444445
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "This lets you save or upload photos to your Django Files";
445446
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
446447
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
447-
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
448+
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
448449
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
449450
LD_RUNPATH_SEARCH_PATHS = (
450451
"$(inherited)",
@@ -481,6 +482,7 @@
481482
ENABLE_PREVIEWS = YES;
482483
GENERATE_INFOPLIST_FILE = YES;
483484
INFOPLIST_FILE = "Django Files/Info.plist";
485+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
484486
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
485487
INFOPLIST_KEY_NSCameraUsageDescription = "This lets you capture and upload photos or video files to your Django Files.";
486488
INFOPLIST_KEY_NSFileProviderDomainUsageDescription = "This lets you upload files to your Django Files.";
@@ -489,7 +491,7 @@
489491
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "This lets you save or upload photos to your Django Files";
490492
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
491493
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
492-
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
494+
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
493495
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
494496
LD_RUNPATH_SEARCH_PATHS = (
495497
"$(inherited)",

Django Files/Info.plist

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
</array>
1616
</dict>
1717
</array>
18-
<key>ITSAppUsesNonExemptEncryption</key>
19-
<false/>
2018
<key>NSAppTransportSecurity</key>
2119
<dict>
2220
<key>NSAllowsArbitraryLoads</key>

Django Files/Views/ContentView.swift

+118-101
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,38 @@
55
// Created by Michael on 2/14/25.
66
//
77

8-
import SwiftUI
98
import SwiftData
9+
import SwiftUI
1010

1111
struct ContentView: View {
1212
@Environment(\.modelContext) private var modelContext
1313
@Environment(\.dismiss) private var dismiss
14-
14+
1515
@Query private var items: [DjangoFilesSession]
1616
@State private var showingEditor = false
17-
@State private var columnVisibility = NavigationSplitViewVisibility.detailOnly
17+
@State private var columnVisibility = NavigationSplitViewVisibility
18+
.detailOnly
1819
@State private var selectedServer: DjangoFilesSession?
19-
@State private var selectedSession: DjangoFilesSession? // Track session for settings
20+
@State private var selectedSession: DjangoFilesSession? // Track session for settings
2021
@State private var needsRefresh = false // Added to handle refresh after adding server
21-
@State private var itemToDelete: DjangoFilesSession? // Track item to be deleted
22-
@State private var showingDeleteAlert = false // Track if delete alert is showing
23-
22+
@State private var itemToDelete: DjangoFilesSession? // Track item to be deleted
23+
@State private var showingDeleteAlert = false // Track if delete alert is showing
24+
2425
@State private var token: String?
25-
26+
2627
@State private var viewingSettings: Bool = false
27-
28-
28+
29+
// stupid work around where we have to show the toolbar on ipad so splitview does not crash due to toolbar state
30+
let toolbarVisibility: Visibility =
31+
UIDevice.current.userInterfaceIdiom == .pad ? .visible : .hidden
32+
2933
var body: some View {
3034
NavigationSplitView(columnVisibility: $columnVisibility) {
3135
List(selection: $selectedServer) {
3236
ForEach(items, id: \.self) { item in
3337
NavigationLink(value: item) {
3438
Text(item.url)
35-
.swipeActions() {
39+
.swipeActions {
3640
Button(role: .destructive) {
3741
itemToDelete = item
3842
showingDeleteAlert = true
@@ -54,8 +58,7 @@ struct ContentView: View {
5458
ToolbarItem {
5559
Button(action: {
5660
self.showingEditor.toggle()
57-
})
58-
{
61+
}) {
5962
Label("Add Item", systemImage: "plus")
6063
}
6164
}
@@ -74,7 +77,7 @@ struct ContentView: View {
7477
.onAppear {
7578
columnVisibility = .detailOnly
7679
}
77-
.toolbarVisibility(.hidden, for: .navigationBar)
80+
.toolbar(toolbarVisibility)
7881
} else {
7982
LoginView(
8083
selectedServer: server,
@@ -86,10 +89,11 @@ struct ContentView: View {
8689
.onAppear {
8790
columnVisibility = .detailOnly
8891
}
92+
.toolbarBackground(.hidden, for: .navigationBar)
8993
}
9094
}
9195
}
92-
.sheet(isPresented: $showingEditor){
96+
.sheet(isPresented: $showingEditor) {
9397
SessionEditor(session: nil)
9498
.onDisappear {
9599
if items.count > 0 {
@@ -101,18 +105,21 @@ struct ContentView: View {
101105
.sheet(item: $selectedSession) { session in
102106
SessionSelector(session: session)
103107
}
104-
.onAppear() {
105-
selectedServer = items.first(where: { $0.defaultSession }) ?? items.first
106-
if items.count == 0{
108+
.onAppear {
109+
selectedServer =
110+
items.first(where: { $0.defaultSession }) ?? items.first
111+
if items.count == 0 {
107112
self.showingEditor.toggle()
108113
}
109114
}
110115
.frame(maxWidth: .infinity, maxHeight: .infinity)
111116
.edgesIgnoringSafeArea(.all)
112117
.alert("Delete Server", isPresented: $showingDeleteAlert) {
113-
Button("Cancel", role: .cancel) { }
118+
Button("Cancel", role: .cancel) {}
114119
Button("Delete", role: .destructive) {
115-
if let item = itemToDelete, let index = items.firstIndex(of: item) {
120+
if let item = itemToDelete,
121+
let index = items.firstIndex(of: item)
122+
{
116123
deleteItems(offsets: [index])
117124
if selectedServer == item {
118125
needsRefresh = true
@@ -121,10 +128,12 @@ struct ContentView: View {
121128
}
122129
}
123130
} message: {
124-
Text("Are you sure you want to delete \(URL(string: itemToDelete?.url ?? "")?.host ?? "this server")? This action cannot be undone.")
131+
Text(
132+
"Are you sure you want to delete \(URL(string: itemToDelete?.url ?? "")?.host ?? "this server")? This action cannot be undone."
133+
)
125134
}
126135
}
127-
136+
128137
private func deleteItems(offsets: IndexSet) {
129138
withAnimation {
130139
for index in offsets {
@@ -137,107 +146,111 @@ struct ContentView: View {
137146
public struct AuthViewContainer: View {
138147
@Environment(\.modelContext) private var modelContext
139148
@Environment(\.dismiss) private var dismiss
140-
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
149+
@Environment(\.presentationMode) private var presentationMode:
150+
Binding<PresentationMode>
141151
@Query private var items: [DjangoFilesSession]
142-
152+
143153
@State private var isAuthViewLoading: Bool = true
144-
154+
145155
var viewingSettings: Binding<Bool>
146156
let selectedServer: DjangoFilesSession
147157
var columnVisibility: Binding<NavigationSplitViewVisibility>
148158
var showingEditor: Binding<Bool>
149159
var needsRefresh: Binding<Bool>
150-
160+
151161
@State private var authController: AuthController = AuthController()
152-
162+
153163
public var body: some View {
154-
if viewingSettings.wrappedValue{
155-
SessionSelector(session: selectedServer, viewingSelect: viewingSettings)
156-
.onAppear(){
157-
columnVisibility.wrappedValue = .automatic
158-
}
164+
if viewingSettings.wrappedValue {
165+
SessionSelector(
166+
session: selectedServer,
167+
viewingSelect: viewingSettings
168+
)
169+
.onAppear {
170+
columnVisibility.wrappedValue = .automatic
159171
}
160-
else if selectedServer.url != "" {
161-
Color.djangoFilesBackground.ignoresSafeArea()
162-
.overlay{
172+
} else if selectedServer.url != "" {
173+
Color.djangoFilesBackground.ignoresSafeArea()
174+
.overlay {
163175
AuthView(
164176
authController: authController,
165177
httpsUrl: selectedServer.url,
166-
doReset: authController.url?.absoluteString ?? "" != selectedServer.url || !selectedServer.auth,
178+
doReset: authController.url?.absoluteString ?? ""
179+
!= selectedServer.url || !selectedServer.auth,
167180
session: selectedServer
168-
)
169-
.onStartedLoading {
170-
isAuthViewLoading = true
181+
)
182+
.onStartedLoading {
183+
isAuthViewLoading = true
184+
}
185+
.onCancelled {
186+
isAuthViewLoading = false
187+
dismiss()
188+
}
189+
.onAppear {
190+
if needsRefresh.wrappedValue {
191+
authController.reset()
192+
needsRefresh.wrappedValue = false
193+
}
194+
195+
authController.onStartedLoadingAction = {
171196
}
172-
.onCancelled {
197+
198+
authController.onLoadedAction = {
199+
isAuthViewLoading = false
200+
201+
}
202+
authController.onCancelledAction = {
173203
isAuthViewLoading = false
174204
dismiss()
175205
}
176-
.onAppear(){
177-
columnVisibility.wrappedValue = .detailOnly
178-
if needsRefresh.wrappedValue {
179-
authController.reset()
180-
needsRefresh.wrappedValue = false
181-
}
182-
183-
authController.onStartedLoadingAction = {
184-
}
185-
186-
authController.onLoadedAction = {
187-
isAuthViewLoading = false
188206

207+
authController.onSchemeRedirectAction = {
208+
isAuthViewLoading = false
209+
guard let resolve = authController.schemeURL else {
210+
return
189211
}
190-
authController.onCancelledAction = {
191-
isAuthViewLoading = false
192-
dismiss()
193-
}
194-
195-
authController.onSchemeRedirectAction = {
196-
isAuthViewLoading = false
197-
guard let resolve = authController.schemeURL else{
198-
return
199-
}
200-
switch resolve{
201-
case "serverlist":
202-
if UIDevice.current.userInterfaceIdiom == .phone{
203-
self.presentationMode.wrappedValue.dismiss()
204-
}
205-
columnVisibility.wrappedValue = .all
206-
break
207-
case "serversettings":
208-
viewingSettings.wrappedValue = true
209-
break
210-
case "logout":
211-
selectedServer.auth = false
212-
columnVisibility.wrappedValue = .automatic
213-
modelContext.insert(selectedServer)
214-
do {
215-
try modelContext.save()
216-
} catch {
217-
print("Error saving session: \(error)")
218-
}
212+
switch resolve {
213+
case "serverlist":
214+
if UIDevice.current.userInterfaceIdiom == .phone
215+
{
219216
self.presentationMode.wrappedValue.dismiss()
220-
break
221-
default:
222-
return
223217
}
218+
columnVisibility.wrappedValue = .all
219+
break
220+
case "serversettings":
221+
viewingSettings.wrappedValue = true
222+
break
223+
case "logout":
224+
selectedServer.auth = false
225+
columnVisibility.wrappedValue = .all
226+
modelContext.insert(selectedServer)
227+
do {
228+
try modelContext.save()
229+
} catch {
230+
print("Error saving session: \(error)")
231+
}
232+
self.presentationMode.wrappedValue.dismiss()
233+
break
234+
default:
235+
return
224236
}
225237
}
226-
.overlay {
227-
if isAuthViewLoading {
228-
LoadingView().frame(width: 100, height: 100)
229-
}
238+
}
239+
.overlay {
240+
if isAuthViewLoading {
241+
LoadingView().frame(width: 100, height: 100)
230242
}
243+
}
231244
}
245+
.ignoresSafeArea()
232246
.edgesIgnoringSafeArea(.all)
233247
.frame(maxWidth: .infinity, maxHeight: .infinity)
234-
}
235-
else {
236-
Text("Loading...")
237-
.onAppear(){
238-
columnVisibility.wrappedValue = .all
239-
}
240-
}
248+
} else {
249+
Text("Loading...")
250+
.onAppear {
251+
columnVisibility.wrappedValue = .all
252+
}
253+
}
241254
}
242255
}
243256

@@ -250,21 +263,25 @@ struct LoadingView: View {
250263
.stroke(Color.launchScreenBackground, lineWidth: 5)
251264
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
252265
.opacity(firstAppear ? 1 : 0)
253-
.onAppear(){
266+
.onAppear {
254267
DispatchQueue.main.async {
255-
if isLoading == false{
256-
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)){
268+
if isLoading == false {
269+
withAnimation(
270+
.linear(duration: 1).repeatForever(
271+
autoreverses: false
272+
)
273+
) {
257274
isLoading.toggle()
258275
}
259276
}
260-
withAnimation(.easeInOut(duration: 0.25)){
277+
withAnimation(.easeInOut(duration: 0.25)) {
261278
firstAppear = true
262279
}
263280
}
264281
}
265-
.onDisappear(){
282+
.onDisappear {
266283
DispatchQueue.main.async {
267-
withAnimation(.easeInOut(duration: 0.25)){
284+
withAnimation(.easeInOut(duration: 0.25)) {
268285
firstAppear = true
269286
}
270287
}

0 commit comments

Comments
 (0)