Skip to content

Commit 1b3775b

Browse files
committed
layout: side-by-side drone map/list in landscape iPad
1 parent ae08426 commit 1b3775b

1 file changed

Lines changed: 108 additions & 56 deletions

File tree

WarDragon/UI/ContentView.swift

Lines changed: 108 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -317,35 +317,57 @@ struct ContentView: View {
317317
}
318318

319319
private var bothDetectionsList: some View {
320-
VStack(spacing: 0) {
321-
unifiedMapView(showAircraft: true)
322-
.frame(height: mapHeight)
323-
.padding(.vertical, 4)
320+
GeometryReader { geo in
321+
if useSideBySideMapList(for: geo.size) {
322+
HStack(spacing: 0) {
323+
unifiedMapView(showAircraft: true)
324+
.frame(maxWidth: .infinity, maxHeight: .infinity)
325+
.padding(.vertical, 4)
326+
.padding(.leading, 4)
327+
.layoutPriority(0)
324328

325-
List {
326-
if hasDrones {
327-
Section("Drones (\(cotViewModel.parsedMessages.count))") {
328-
ForEach(cotViewModel.parsedMessages) { item in
329-
MessageRow(
330-
message: item,
331-
cotViewModel: cotViewModel,
332-
isCompact: true
333-
)
334-
.id(item.uid)
335-
}
329+
Divider()
330+
331+
bothList
332+
.frame(minWidth: 340, idealWidth: 420, maxWidth: 520)
333+
.layoutPriority(1)
334+
}
335+
} else {
336+
VStack(spacing: 0) {
337+
unifiedMapView(showAircraft: true)
338+
.frame(height: mapHeight)
339+
.padding(.vertical, 4)
340+
341+
bothList
342+
}
343+
}
344+
}
345+
}
346+
347+
private var bothList: some View {
348+
List {
349+
if hasDrones {
350+
Section("Drones (\(cotViewModel.parsedMessages.count))") {
351+
ForEach(cotViewModel.parsedMessages) { item in
352+
MessageRow(
353+
message: item,
354+
cotViewModel: cotViewModel,
355+
isCompact: true
356+
)
357+
.id(item.uid)
336358
}
337359
}
360+
}
338361

339-
if hasAircraft {
340-
Section("Aircraft (\(cotViewModel.aircraftTracks.count))") {
341-
ForEach(cotViewModel.aircraftTracks) { aircraft in
342-
AircraftRow(aircraft: aircraft)
343-
}
362+
if hasAircraft {
363+
Section("Aircraft (\(cotViewModel.aircraftTracks.count))") {
364+
ForEach(cotViewModel.aircraftTracks) { aircraft in
365+
AircraftRow(aircraft: aircraft)
344366
}
345367
}
346368
}
347-
.scrollContentBackground(.visible)
348369
}
370+
.scrollContentBackground(.visible)
349371
}
350372

351373
private func getValidFlightPath(for uid: String) -> [CLLocationCoordinate2D] {
@@ -516,17 +538,23 @@ struct ContentView: View {
516538
#if targetEnvironment(macCatalyst)
517539
return 200
518540
#else
519-
// Check if we're on iPad in landscape
520-
let isIPad = UIDevice.current.userInterfaceIdiom == .pad
521-
let isLandscape = UIApplication.shared.connectedScenes
522-
.compactMap { $0 as? UIWindowScene }
523-
.first?.interfaceOrientation.isLandscape ?? false
524-
525-
if isIPad && isLandscape {
526-
return 280 // Fits map + bottom controls + "Tap to view full map" capsule
527-
} else {
528-
return 320 // Portrait: extra room for stats panel + map controls + capsule
529-
}
541+
// On iPad-landscape we render map+list side-by-side, so height is
542+
// driven by the container, not this constant. Vertical layouts only.
543+
return 320
544+
#endif
545+
}
546+
547+
/// True when we should render map and list side-by-side instead of stacked.
548+
/// Decision is geometry-driven so it reacts to rotation, split-view, and
549+
/// Stage Manager resizing in real time.
550+
private func useSideBySideMapList(for size: CGSize) -> Bool {
551+
#if targetEnvironment(macCatalyst)
552+
return false
553+
#else
554+
// Wider than tall AND wide enough to fit both panes comfortably.
555+
// 900pt threshold filters out iPhone-Plus landscape (~736pt) and
556+
// narrow Stage-Manager iPad windows.
557+
return size.width > size.height && size.width >= 900
530558
#endif
531559
}
532560

@@ -608,41 +636,65 @@ struct ContentView: View {
608636

609637
private var droneList: some View {
610638
ScrollViewReader { proxy in
611-
VStack(spacing: 0) {
612-
unifiedMapView(showAircraft: false)
613-
.frame(height: mapHeight)
614-
.padding(.vertical, 4)
639+
GeometryReader { geo in
640+
Group {
641+
if useSideBySideMapList(for: geo.size) {
642+
HStack(spacing: 0) {
643+
unifiedMapView(showAircraft: false)
644+
.frame(maxWidth: .infinity, maxHeight: .infinity)
645+
.padding(.vertical, 4)
646+
.padding(.leading, 4)
647+
.layoutPriority(0)
615648

616-
List {
617-
Section {
618-
ForEach(cotViewModel.parsedMessages) { item in
619-
MessageRow(
620-
message: item,
621-
cotViewModel: cotViewModel,
622-
isCompact: true
623-
)
624-
.id(item.uid)
649+
Divider()
650+
651+
droneOnlyList
652+
.frame(minWidth: 340, idealWidth: 420, maxWidth: 520)
653+
.layoutPriority(1)
654+
}
655+
} else {
656+
VStack(spacing: 0) {
657+
unifiedMapView(showAircraft: false)
658+
.frame(height: mapHeight)
659+
.padding(.vertical, 4)
660+
661+
droneOnlyList
625662
}
626663
}
627664
}
628-
.listStyle(.inset)
629-
.scrollContentBackground(.visible)
630-
}
631-
.onChange(of: cotViewModel.parsedMessages) { oldMessages, newMessages in
632-
if oldMessages.count < newMessages.count {
633-
if let latest = newMessages.last {
634-
if !oldMessages.contains(where: { $0.id == latest.id }) {
635-
latestMessage = latest
636-
showAlert = false
637-
withAnimation {
638-
proxy.scrollTo(latest.id, anchor: .bottom)
665+
.onChange(of: cotViewModel.parsedMessages) { oldMessages, newMessages in
666+
if oldMessages.count < newMessages.count {
667+
if let latest = newMessages.last {
668+
if !oldMessages.contains(where: { $0.id == latest.id }) {
669+
latestMessage = latest
670+
showAlert = false
671+
withAnimation {
672+
proxy.scrollTo(latest.id, anchor: .bottom)
673+
}
639674
}
640675
}
641676
}
642677
}
643678
}
644679
}
645680
}
681+
682+
private var droneOnlyList: some View {
683+
List {
684+
Section {
685+
ForEach(cotViewModel.parsedMessages) { item in
686+
MessageRow(
687+
message: item,
688+
cotViewModel: cotViewModel,
689+
isCompact: true
690+
)
691+
.id(item.uid)
692+
}
693+
}
694+
}
695+
.listStyle(.inset)
696+
.scrollContentBackground(.visible)
697+
}
646698

647699
private func unifiedMapView(showAircraft: Bool = true) -> some View {
648700
LiveMapPreview(cotViewModel: cotViewModel, droneCount: cotViewModel.parsedMessages.count, showAircraft: showAircraft, showUnifiedMap: $showUnifiedMap)

0 commit comments

Comments
 (0)