✨ watchOS showing video
This commit is contained in:
		
							
								
								
									
										96
									
								
								ios/WatchRunner Watch App/Views/AttachmentView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								ios/WatchRunner Watch App/Views/AttachmentView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| // | ||||
| //  AttachmentImageView.swift | ||||
| //  WatchRunner Watch App | ||||
| // | ||||
| //  Created by LittleSheep on 2025/10/29. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import AVKit | ||||
| import AVFoundation | ||||
|  | ||||
| struct AttachmentView: View { | ||||
|     let attachment: SnCloudFile | ||||
|     @EnvironmentObject var appState: AppState | ||||
|     @StateObject private var imageLoader = ImageLoader() | ||||
|  | ||||
|     var body: some View { | ||||
|         Group { | ||||
|             if let mimeType = attachment.mimeType { | ||||
|                 if mimeType.starts(with: "image") { | ||||
|                     if let serverUrl = appState.serverUrl, let imageUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) { | ||||
|                         NavigationLink( | ||||
|                             destination: ImageViewer(imageUrl: imageUrl).environmentObject(appState) | ||||
|                         ) { | ||||
|                             if imageLoader.isLoading { | ||||
|                                 ProgressView() | ||||
|                             } else if let image = imageLoader.image { | ||||
|                                 image | ||||
|                                     .resizable() | ||||
|                                     .aspectRatio(contentMode: .fit) | ||||
|                                     .frame(maxWidth: .infinity) | ||||
|                                     .cornerRadius(8) | ||||
|                             } else if let errorMessage = imageLoader.errorMessage { | ||||
|                                 Text("Failed to load attachment: \(errorMessage)") | ||||
|                                     .font(.caption) | ||||
|                                     .foregroundColor(.red) | ||||
|                                     .cornerRadius(8) | ||||
|                             } else { | ||||
|                                 Text("File: \(attachment.id)") | ||||
|                                     .cornerRadius(8) | ||||
|                             } | ||||
|                         } | ||||
|                         .buttonStyle(PlainButtonStyle()) | ||||
|                     } else { | ||||
|                         Text("Image URL not available.") | ||||
|                     } | ||||
|                 } else if mimeType.starts(with: "video") { | ||||
|                     if let serverUrl = appState.serverUrl, let videoUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) { | ||||
|                         let thumbnailUrl = videoUrl.appendingPathComponent("thumbnail") // Construct thumbnail URL | ||||
|                         NavigationLink(destination: VideoPlayerView(videoUrl: videoUrl)) { | ||||
|                             AsyncImage(url: thumbnailUrl) { phase in | ||||
|                                 if let image = phase.image { | ||||
|                                     image | ||||
|                                         .resizable() | ||||
|                                         .aspectRatio(contentMode: .fit) | ||||
|                                         .frame(maxWidth: .infinity) | ||||
|                                         .cornerRadius(8) | ||||
|                                 } else if phase.error != nil { | ||||
|                                     Image(systemName: "play.rectangle.fill") // Placeholder for video thumbnail | ||||
|                                         .resizable() | ||||
|                                         .aspectRatio(contentMode: .fit) | ||||
|                                         .frame(maxWidth: .infinity) | ||||
|                                         .foregroundColor(.gray) | ||||
|                                         .cornerRadius(8) | ||||
|                                 } else { | ||||
|                                     ProgressView() | ||||
|                                         .cornerRadius(8) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         .buttonStyle(PlainButtonStyle()) | ||||
|                     } else { | ||||
|                         Text("Video URL not available.") | ||||
|                     } | ||||
|                 } else if mimeType.starts(with: "audio") { | ||||
|                     if let serverUrl = appState.serverUrl, let audioUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl) { | ||||
|                         AudioPlayerView(audioUrl: audioUrl) | ||||
|                     } else { | ||||
|                         Text("Cannot play audio: URL not available.") | ||||
|                     } | ||||
|                 } else { | ||||
|                     Text("Unsupported media type: \(mimeType)") | ||||
|                 } | ||||
|             } else { | ||||
|                 Text("File: \(attachment.id) (No MIME type)") | ||||
|             } | ||||
|         } | ||||
|         .task(id: attachment.id) { | ||||
|             if let serverUrl = appState.serverUrl, let attachmentUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl), let token = appState.token { | ||||
|                 if attachment.mimeType?.starts(with: "image") == true { | ||||
|                     await imageLoader.loadImage(from: attachmentUrl, token: token) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										47
									
								
								ios/WatchRunner Watch App/Views/AudioPlayerView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								ios/WatchRunner Watch App/Views/AudioPlayerView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
|  | ||||
| // | ||||
| //  AudioPlayerView.swift | ||||
| //  WatchRunner Watch App | ||||
| // | ||||
| //  Created by LittleSheep on 2025/10/29. | ||||
| // | ||||
|  | ||||
| import SwiftUI | ||||
| import AVFoundation | ||||
|  | ||||
| struct AudioPlayerView: View { | ||||
|     let audioUrl: URL | ||||
|     @State private var player: AVPlayer? | ||||
|     @State private var isPlaying: Bool = false | ||||
|  | ||||
|     var body: some View { | ||||
|         VStack { | ||||
|             if player != nil { | ||||
|                 Button(action: togglePlayPause) { | ||||
|                     Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill") | ||||
|                         .font(.largeTitle) | ||||
|                 } | ||||
|                 .buttonStyle(.plain) | ||||
|             } else { | ||||
|                 Text("Loading audio...") | ||||
|             } | ||||
|         } | ||||
|         .onAppear { | ||||
|             player = AVPlayer(url: audioUrl) | ||||
|         } | ||||
|         .onDisappear { | ||||
|             player?.pause() | ||||
|             player = nil | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private func togglePlayPause() { | ||||
|         guard let player = player else { return } | ||||
|         if isPlaying { | ||||
|             player.pause() | ||||
|         } else { | ||||
|             player.play() | ||||
|         } | ||||
|         isPlaying.toggle() | ||||
|     } | ||||
| } | ||||
| @@ -1,14 +1,7 @@ | ||||
| // | ||||
| //  AttachmentImageView.swift | ||||
| //  WatchRunner Watch App | ||||
| // | ||||
| //  Created by LittleSheep on 2025/10/29. | ||||
| // | ||||
| 
 | ||||
| import SwiftUI | ||||
| 
 | ||||
| struct AttachmentImageView: View { | ||||
|     let attachment: SnCloudFile | ||||
| struct ImageViewer: View { | ||||
|     let imageUrl: URL | ||||
|     @EnvironmentObject var appState: AppState | ||||
|     @StateObject private var imageLoader = ImageLoader() | ||||
| 
 | ||||
| @@ -20,19 +13,22 @@ struct AttachmentImageView: View { | ||||
|                 image | ||||
|                     .resizable() | ||||
|                     .aspectRatio(contentMode: .fit) | ||||
|                     .frame(maxWidth: .infinity) | ||||
|                     .frame(maxWidth: .infinity, maxHeight: .infinity) | ||||
|                     .scaledToFit() | ||||
|             } else if let errorMessage = imageLoader.errorMessage { | ||||
|                 Text("Failed to load attachment: \(errorMessage)") | ||||
|                 Text("Failed to load image: \(errorMessage)") | ||||
|                     .font(.caption) | ||||
|                     .foregroundColor(.red) | ||||
|             } else { | ||||
|                 Text("File: \(attachment.id)") | ||||
|                 Text("Failed to load image.") | ||||
|             } | ||||
|         } | ||||
|         .task(id: attachment.id) { | ||||
|             if let serverUrl = appState.serverUrl, let imageUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl), let token = appState.token, attachment.mimeType?.starts(with: "image") == true { | ||||
|         .task(id: imageUrl) { | ||||
|             if let token = appState.token { | ||||
|                 await imageLoader.loadImage(from: imageUrl, token: token) | ||||
|             } | ||||
|         } | ||||
|         .navigationTitle("Image") | ||||
|         .navigationBarTitleDisplayMode(.inline) | ||||
|     } | ||||
| } | ||||
| @@ -116,7 +116,7 @@ struct PostDetailView: View { | ||||
|                     Divider() | ||||
|                     Text("Attachments").font(.headline) | ||||
|                     ForEach(post.attachments) { attachment in | ||||
|                         AttachmentImageView(attachment: attachment) | ||||
|                         AttachmentView(attachment: attachment) | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|   | ||||
							
								
								
									
										12
									
								
								ios/WatchRunner Watch App/Views/VideoPlayerView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ios/WatchRunner Watch App/Views/VideoPlayerView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import SwiftUI | ||||
| import AVKit | ||||
| import AVFoundation | ||||
|  | ||||
| struct VideoPlayerView: View { | ||||
|     let videoUrl: URL | ||||
|  | ||||
|     var body: some View { | ||||
|         VideoPlayer(player: AVPlayer(url: videoUrl)) | ||||
|             .edgesIgnoringSafeArea(.all) // Make it full screen | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user