✨ 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 | import SwiftUI | ||||||
| 
 | 
 | ||||||
| struct AttachmentImageView: View { | struct ImageViewer: View { | ||||||
|     let attachment: SnCloudFile |     let imageUrl: URL | ||||||
|     @EnvironmentObject var appState: AppState |     @EnvironmentObject var appState: AppState | ||||||
|     @StateObject private var imageLoader = ImageLoader() |     @StateObject private var imageLoader = ImageLoader() | ||||||
| 
 | 
 | ||||||
| @@ -20,19 +13,22 @@ struct AttachmentImageView: View { | |||||||
|                 image |                 image | ||||||
|                     .resizable() |                     .resizable() | ||||||
|                     .aspectRatio(contentMode: .fit) |                     .aspectRatio(contentMode: .fit) | ||||||
|                     .frame(maxWidth: .infinity) |                     .frame(maxWidth: .infinity, maxHeight: .infinity) | ||||||
|  |                     .scaledToFit() | ||||||
|             } else if let errorMessage = imageLoader.errorMessage { |             } else if let errorMessage = imageLoader.errorMessage { | ||||||
|                 Text("Failed to load attachment: \(errorMessage)") |                 Text("Failed to load image: \(errorMessage)") | ||||||
|                     .font(.caption) |                     .font(.caption) | ||||||
|                     .foregroundColor(.red) |                     .foregroundColor(.red) | ||||||
|             } else { |             } else { | ||||||
|                 Text("File: \(attachment.id)") |                 Text("Failed to load image.") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         .task(id: attachment.id) { |         .task(id: imageUrl) { | ||||||
|             if let serverUrl = appState.serverUrl, let imageUrl = getAttachmentUrl(for: attachment.id, serverUrl: serverUrl), let token = appState.token, attachment.mimeType?.starts(with: "image") == true { |             if let token = appState.token { | ||||||
|                 await imageLoader.loadImage(from: imageUrl, token: token) |                 await imageLoader.loadImage(from: imageUrl, token: token) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         .navigationTitle("Image") | ||||||
|  |         .navigationBarTitleDisplayMode(.inline) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -116,7 +116,7 @@ struct PostDetailView: View { | |||||||
|                     Divider() |                     Divider() | ||||||
|                     Text("Attachments").font(.headline) |                     Text("Attachments").font(.headline) | ||||||
|                     ForEach(post.attachments) { attachment in |                     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