@@ -17,7 +17,9 @@ struct DeviceScannerView: View {
1717 ZStack {
1818 AppTheme . backgroundGradient
1919
20- if viewModel. hasPeers {
20+ if viewModel. role == . camera {
21+ cameraWaitingState
22+ } else if viewModel. hasPeers {
2123 peerList
2224 } else {
2325 emptyState
@@ -80,75 +82,159 @@ struct DeviceScannerView: View {
8082 }
8183 }
8284
83- // MARK: - Empty State
85+ // MARK: - Camera Waiting State
8486
85- private var emptyState : some View {
86- VStack ( spacing: 24 ) {
87- Spacer ( )
87+ private var cameraWaitingState : some View {
88+ ScrollView {
89+ VStack ( spacing: 20 ) {
90+ // Camera icon
91+ ZStack {
92+ Circle ( )
93+ . fill ( AppTheme . accent. opacity ( 0.12 ) )
94+ . frame ( width: 100 , height: 100 )
95+
96+ Circle ( )
97+ . strokeBorder ( AppTheme . accent. opacity ( 0.25 ) , lineWidth: 1.5 )
98+ . frame ( width: 100 , height: 100 )
99+
100+ Image ( systemName: " camera.fill " )
101+ . font ( . system( size: 40 , weight: . medium) )
102+ . foregroundColor ( AppTheme . accent)
103+ }
104+ . padding ( . top, 24 )
105+
106+ VStack ( spacing: 8 ) {
107+ Text ( NSLocalizedString ( " Camera Mode " , comment: " " ) )
108+ . font ( . title2)
109+ . fontWeight ( . bold)
110+ . foregroundColor ( . primary)
111+
112+ Text ( NSLocalizedString ( " On another device, open Remote Shutter and select Remote. This camera will appear in their device list. " , comment: " " ) )
113+ . font ( . subheadline)
114+ . foregroundColor ( . secondary)
115+ . multilineTextAlignment ( . center)
116+ . lineLimit ( nil )
117+ . fixedSize ( horizontal: false , vertical: true )
118+ }
119+ . padding ( . horizontal, 20 )
88120
89- // QR code
90- VStack ( spacing: 16 ) {
91- if let qrImage = qrCodeImage {
92- Image ( uiImage: qrImage)
93- . interpolation ( . none)
94- . resizable ( )
95- . aspectRatio ( contentMode: . fit)
96- . frame ( width: 160 , height: 160 )
97- . padding ( 12 )
98- . background ( . ultraThinMaterial)
99- . clipShape ( RoundedRectangle ( cornerRadius: 18 ) )
100- . overlay (
101- RoundedRectangle ( cornerRadius: 18 )
102- . strokeBorder ( AppTheme . glassBorder, lineWidth: 0.5 )
103- )
104- } else {
105- Image ( " AppLogo " )
106- . resizable ( )
107- . aspectRatio ( contentMode: . fit)
108- . frame ( width: 80 , height: 80 )
109- . clipShape ( RoundedRectangle ( cornerRadius: 18 ) )
110- . shadow ( color: . black. opacity ( 0.1 ) , radius: 8 , y: 4 )
121+ // Status
122+ statusBadge
123+
124+ // Actions
125+ VStack ( spacing: 12 ) {
126+ if !viewModel. hasLocalNetworkAccess {
127+ settingsButton
128+ } else if viewModel. isScanning {
129+ cameraAdvertisingIndicator
130+ goOfflineButton
131+ } else {
132+ goOnlineButton
133+ }
134+
135+ shareButton
111136 }
137+ . padding ( . horizontal, 20 )
112138
113- Text ( NSLocalizedString ( " You need at least 2 devices running Remote Shutter " , comment: " " ) )
114- . font ( . subheadline)
115- . foregroundColor ( . secondary)
116- . multilineTextAlignment ( . center)
117- . padding ( . horizontal, 40 )
139+ // QR code + tip
140+ qrCodeSection
118141 }
142+ }
143+ }
119144
120- // Status
121- statusBadge
145+ // MARK: - Empty State
122146
123- // Actions
124- VStack ( spacing: 12 ) {
125- if !viewModel. hasLocalNetworkAccess {
126- settingsButton
127- } else if viewModel. isScanning {
128- scanningIndicator
129- stopScanButton
130- } else {
131- startScanButton
147+ private var emptyState : some View {
148+ ScrollView {
149+ VStack ( spacing: 20 ) {
150+ // Remote icon
151+ ZStack {
152+ Circle ( )
153+ . fill ( AppTheme . secondary. opacity ( 0.12 ) )
154+ . frame ( width: 100 , height: 100 )
155+
156+ Circle ( )
157+ . strokeBorder ( AppTheme . secondary. opacity ( 0.25 ) , lineWidth: 1.5 )
158+ . frame ( width: 100 , height: 100 )
159+
160+ Image ( systemName: " antenna.radiowaves.left.and.right " )
161+ . font ( . system( size: 40 , weight: . medium) )
162+ . foregroundColor ( AppTheme . secondary)
163+ }
164+ . padding ( . top, 24 )
165+
166+ VStack ( spacing: 8 ) {
167+ Text ( NSLocalizedString ( " Remote Mode " , comment: " " ) )
168+ . font ( . title2)
169+ . fontWeight ( . bold)
170+ . foregroundColor ( . primary)
171+
172+ Text ( NSLocalizedString ( " You need at least 2 devices running Remote Shutter " , comment: " " ) )
173+ . font ( . subheadline)
174+ . foregroundColor ( . secondary)
175+ . multilineTextAlignment ( . center)
176+ . lineLimit ( nil )
177+ . fixedSize ( horizontal: false , vertical: true )
132178 }
179+ . padding ( . horizontal, 20 )
180+
181+ // Status
182+ statusBadge
183+
184+ // Actions
185+ VStack ( spacing: 12 ) {
186+ if !viewModel. hasLocalNetworkAccess {
187+ settingsButton
188+ } else if viewModel. isScanning {
189+ scanningIndicator
190+ stopScanButton
191+ } else {
192+ startScanButton
193+ }
133194
134- shareButton
195+ shareButton
196+ }
197+ . padding ( . horizontal, 20 )
198+
199+ // QR code + tip
200+ qrCodeSection
135201 }
136- . padding ( . horizontal, 20 )
202+ }
203+ }
137204
138- Spacer ( )
205+ // MARK: - QR Code Section
206+
207+ private var qrCodeSection : some View {
208+ VStack ( spacing: 12 ) {
209+ if let qrImage = qrCodeImage {
210+ Image ( uiImage: qrImage)
211+ . interpolation ( . none)
212+ . resizable ( )
213+ . aspectRatio ( contentMode: . fit)
214+ . frame ( width: 120 , height: 120 )
215+ . padding ( 8 )
216+ . background ( . ultraThinMaterial)
217+ . clipShape ( RoundedRectangle ( cornerRadius: 14 ) )
218+ . overlay (
219+ RoundedRectangle ( cornerRadius: 14 )
220+ . strokeBorder ( AppTheme . glassBorder, lineWidth: 0.5 )
221+ )
222+ }
139223
140- // Tip
141- HStack ( spacing: 8 ) {
224+ HStack ( alignment: . top, spacing: 8 ) {
142225 Image ( systemName: " qrcode " )
143226 . font ( . caption)
144227 . foregroundColor ( AppTheme . accent)
228+ . padding ( . top, 2 )
145229 Text ( NSLocalizedString ( " Scan the QR code on another device to download Remote Shutter " , comment: " " ) )
146230 . font ( . caption)
147231 . foregroundColor ( . secondary)
232+ . lineLimit ( nil )
233+ . fixedSize ( horizontal: false , vertical: true )
148234 }
149235 . padding ( . horizontal, 20 )
150- . padding ( . bottom, 20 )
151236 }
237+ . padding ( . bottom, 20 )
152238 }
153239
154240 // MARK: - Components
@@ -225,12 +311,16 @@ struct DeviceScannerView: View {
225311 private var scanningIndicator : some View {
226312 HStack ( spacing: 12 ) {
227313 ProgressView ( )
228- Text ( NSLocalizedString ( " Scanning for nearby devices ... " , comment: " " ) )
314+ Text ( NSLocalizedString ( " Scanning for nearby cameras ... " , comment: " " ) )
229315 . font ( . subheadline)
230316 . foregroundColor ( . secondary)
317+ . lineLimit ( nil )
318+ . fixedSize ( horizontal: false , vertical: true )
231319 }
232320 . frame ( maxWidth: . infinity)
233- . frame ( height: 54 )
321+ . frame ( minHeight: 54 )
322+ . padding ( . vertical, 8 )
323+ . padding ( . horizontal, 16 )
234324 . background ( . ultraThinMaterial)
235325 . clipShape ( RoundedRectangle ( cornerRadius: 14 ) )
236326 . overlay (
@@ -239,6 +329,67 @@ struct DeviceScannerView: View {
239329 )
240330 }
241331
332+ private var cameraAdvertisingIndicator : some View {
333+ HStack ( spacing: 12 ) {
334+ ProgressView ( )
335+ Text ( NSLocalizedString ( " Waiting for a remote to connect... " , comment: " " ) )
336+ . font ( . subheadline)
337+ . foregroundColor ( . secondary)
338+ . lineLimit ( nil )
339+ . fixedSize ( horizontal: false , vertical: true )
340+ }
341+ . frame ( maxWidth: . infinity)
342+ . frame ( minHeight: 54 )
343+ . padding ( . vertical, 8 )
344+ . padding ( . horizontal, 16 )
345+ . background ( . ultraThinMaterial)
346+ . clipShape ( RoundedRectangle ( cornerRadius: 14 ) )
347+ . overlay (
348+ RoundedRectangle ( cornerRadius: 14 )
349+ . strokeBorder ( AppTheme . glassBorder, lineWidth: 0.5 )
350+ )
351+ }
352+
353+ private var goOnlineButton : some View {
354+ Button ( action: onStartScanning) {
355+ HStack {
356+ Image ( systemName: " antenna.radiowaves.left.and.right " )
357+ . font ( . headline)
358+ Text ( NSLocalizedString ( " Go Online " , comment: " " ) )
359+ . font ( . headline)
360+ }
361+ . foregroundColor ( . black)
362+ . frame ( maxWidth: . infinity)
363+ . frame ( height: 54 )
364+ . background (
365+ LinearGradient (
366+ colors: [ AppTheme . accent, AppTheme . accentLight] ,
367+ startPoint: . leading,
368+ endPoint: . trailing
369+ )
370+ )
371+ . clipShape ( RoundedRectangle ( cornerRadius: 14 ) )
372+ . shadow ( color: AppTheme . accent. opacity ( 0.3 ) , radius: 8 , y: 4 )
373+ }
374+ }
375+
376+ private var goOfflineButton : some View {
377+ Button ( action: onStopScanning) {
378+ HStack {
379+ Image ( systemName: " stop.fill " )
380+ . font ( . subheadline)
381+ Text ( NSLocalizedString ( " Go Offline " , comment: " " ) )
382+ . font ( . subheadline)
383+ . fontWeight ( . medium)
384+ }
385+ . foregroundColor ( . secondary)
386+ . frame ( maxWidth: . infinity)
387+ . frame ( height: 44 )
388+ . background ( Color ( . secondarySystemGroupedBackground) )
389+ . clipShape ( RoundedRectangle ( cornerRadius: 12 ) )
390+ }
391+ }
392+
242393 private var settingsButton : some View {
243394 Button ( action: onOpenSettings) {
244395 HStack {
0 commit comments