✨ 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