Status detail info view + better single image preview

This commit is contained in:
Thomas Ricouard 2022-12-24 08:01:35 +01:00
parent 840461caeb
commit 25a80eea65
2 changed files with 91 additions and 26 deletions

View file

@ -36,8 +36,9 @@ public struct StatusDetailView: View {
} }
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isEmbed: false))
.id(status.id) .id(status.id)
makeStatusInfoDetailView(status: status)
Divider() Divider()
.padding(.vertical, DS.Constants.dividerPadding) .padding(.vertical, DS.Constants.dividerPadding * 2)
if !context.descendants.isEmpty { if !context.descendants.isEmpty {
ForEach(context.descendants) { descendant in ForEach(context.descendants) { descendant in
StatusRowView(viewModel: .init(status: descendant, isEmbed: false)) StatusRowView(viewModel: .init(status: descendant, isEmbed: false))
@ -65,4 +66,15 @@ public struct StatusDetailView: View {
.navigationTitle(viewModel.title) .navigationTitle(viewModel.title)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
@ViewBuilder
private func makeStatusInfoDetailView(status: Status) -> some View {
HStack {
Text(status.createdAt.asDate, style: .date)
Text(status.createdAt.asDate, style: .time)
Spacer()
Text(status.application?.name ?? "")
}
.font(.caption)
}
} }

View file

@ -15,6 +15,16 @@ private class VideoPlayerViewModel: ObservableObject {
func preparePlayer() { func preparePlayer() {
player = .init(url: url) player = .init(url: url)
player?.play() player?.play()
guard let player else { return }
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: CMTime.zero)
self?.player?.play()
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: self.player)
} }
} }
@ -42,33 +52,77 @@ public struct StatusMediaPreviewView: View {
@StateObject private var selectedMediaSheetManager = SelectedMediaSheetManager() @StateObject private var selectedMediaSheetManager = SelectedMediaSheetManager()
@State private var isQuickLookLoading: Bool = false @State private var isQuickLookLoading: Bool = false
private var imageMaxHeight: CGFloat {
if attachements.count == 1 {
return 300
}
return attachements.count > 2 ? 100 : 200
}
public var body: some View { public var body: some View {
VStack { Group {
HStack { if attachements.count == 1, let attachement = attachements.first {
if let firstAttachement = attachements.first { makeFeaturedImagePreview(attachement: attachement)
makePreview(attachement: firstAttachement) .onTapGesture {
} Task {
if attachements.count > 1, let secondAttachement = attachements[1] { await quickLook.prepareFor(urls: attachements.map{ $0.url }, selectedURL: attachement.url)
makePreview(attachement: secondAttachement) }
} }
} } else {
HStack { VStack {
if attachements.count > 2, let secondAttachement = attachements[2] { HStack {
makePreview(attachement: secondAttachement) if let firstAttachement = attachements.first {
} makePreview(attachement: firstAttachement)
if attachements.count > 3, let secondAttachement = attachements[3] { }
makePreview(attachement: secondAttachement) if attachements.count > 1, let secondAttachement = attachements[1] {
makePreview(attachement: secondAttachement)
}
}
HStack {
if attachements.count > 2, let secondAttachement = attachements[2] {
makePreview(attachement: secondAttachement)
}
if attachements.count > 3, let secondAttachement = attachements[3] {
makePreview(attachement: secondAttachement)
}
}
} }
} }
} }
.overlay { .overlay {
if quickLook.isPreparing { if quickLook.isPreparing {
quickLookLoadingView quickLookLoadingView
.transition(.opacity) .transition(.opacity)
} }
} }
}
@ViewBuilder
private func makeFeaturedImagePreview(attachement: MediaAttachement) -> some View {
switch attachement.supportedType {
case .image:
AsyncImage(
url: attachement.url,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.cornerRadius(4)
},
placeholder: {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray)
.frame(height: imageMaxHeight)
.shimmering()
}
)
case .gifv:
VideoPlayerView(viewModel: .init(url: attachement.url))
.frame(height: imageMaxHeight)
case .none:
EmptyView()
}
} }
@ViewBuilder @ViewBuilder
@ -83,14 +137,15 @@ public struct StatusMediaPreviewView: View {
content: { image in content: { image in
image image
.resizable() .resizable()
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: attachements.count == 1 ? .fit : .fill)
.frame(height: attachements.count > 2 ? 100 : 200) .frame(height: imageMaxHeight)
.frame(width: proxy.frame(in: .local).width) .frame(width: proxy.frame(in: .local).width)
.cornerRadius(4)
}, },
placeholder: { placeholder: {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
.fill(Color.gray) .fill(Color.gray)
.frame(height: attachements.count > 2 ? 100 : 200) .frame(maxHeight: imageMaxHeight)
.frame(width: proxy.frame(in: .local).width) .frame(width: proxy.frame(in: .local).width)
.shimmering() .shimmering()
} }
@ -98,13 +153,11 @@ public struct StatusMediaPreviewView: View {
case .gifv: case .gifv:
VideoPlayerView(viewModel: .init(url: attachement.url)) VideoPlayerView(viewModel: .init(url: attachement.url))
.frame(width: proxy.frame(in: .local).width) .frame(width: proxy.frame(in: .local).width)
.frame(height: attachements.count > 2 ? 100 : 200) .frame(height: imageMaxHeight)
} }
} }
.frame(height: attachements.count > 2 ? 100 : 200) .frame(height: imageMaxHeight)
} }
.cornerRadius(4)
.contentShape(Rectangle())
.onTapGesture { .onTapGesture {
Task { Task {
await quickLook.prepareFor(urls: attachements.map{ $0.url }, selectedURL: attachement.url) await quickLook.prepareFor(urls: attachements.map{ $0.url }, selectedURL: attachement.url)