mirror of
https://github.com/yattee/yattee.git
synced 2025-08-05 18:24:02 +00:00
iOS 14/macOS Big Sur Support
This commit is contained in:
@@ -3,7 +3,8 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct PlaybackBar: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
@@ -64,18 +65,20 @@ struct PlaybackBar: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.alert(player.playerError?.localizedDescription ?? "", isPresented: $player.presentingErrorDetails) {
|
||||
Button("OK") {}
|
||||
.alert(isPresented: $player.presentingErrorDetails) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(player.playerError?.localizedDescription ?? "")
|
||||
)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.padding(4)
|
||||
.background(.black)
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
}
|
||||
|
||||
private var closeButton: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Label(
|
||||
"Close",
|
||||
@@ -105,10 +108,18 @@ struct PlaybackBar: View {
|
||||
return "less than a minute"
|
||||
}
|
||||
|
||||
let timeFinishAt = Date.now.addingTimeInterval(remainingSeconds)
|
||||
let timeFinishAtString = timeFinishAt.formatted(date: .omitted, time: .shortened)
|
||||
let timeFinishAt = Date().addingTimeInterval(remainingSeconds)
|
||||
|
||||
return "ends at \(timeFinishAtString)"
|
||||
return "ends at \(formattedTimeFinishAt(timeFinishAt))"
|
||||
}
|
||||
|
||||
private func formattedTimeFinishAt(_ date: Date) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
dateFormatter.dateStyle = .none
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
private var rateMenu: some View {
|
||||
|
@@ -44,16 +44,18 @@ struct PlayerQueueView: View {
|
||||
}
|
||||
|
||||
ForEach(player.queue) { item in
|
||||
PlayerQueueRow(item: item, fullScreen: $fullScreen)
|
||||
let row = PlayerQueueRow(item: item, fullScreen: $fullScreen)
|
||||
.contextMenu {
|
||||
removeButton(item, history: false)
|
||||
removeAllButton(history: false)
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
row.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: false)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,14 +65,18 @@ struct PlayerQueueView: View {
|
||||
if !player.history.isEmpty {
|
||||
Section(header: Text("Played Previously")) {
|
||||
ForEach(player.history) { item in
|
||||
PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen)
|
||||
let row = PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen)
|
||||
.contextMenu {
|
||||
removeButton(item, history: true)
|
||||
removeAllButton(history: true)
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: true)
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
row.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: true)
|
||||
}
|
||||
} else {
|
||||
row
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -100,28 +106,44 @@ struct PlayerQueueView: View {
|
||||
}
|
||||
|
||||
private func removeButton(_ item: PlayerQueueItem, history: Bool) -> some View {
|
||||
Button(role: .destructive) {
|
||||
if history {
|
||||
player.removeHistory(item)
|
||||
} else {
|
||||
player.remove(item)
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
return Button(role: .destructive) {
|
||||
removeButtonAction(item, history: history)
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
} else {
|
||||
return Button {
|
||||
removeButtonAction(item, history: history)
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
private func removeButtonAction(_ item: PlayerQueueItem, history: Bool) {
|
||||
_ = history ? player.removeHistory(item) : player.remove(item)
|
||||
}
|
||||
|
||||
private func removeAllButton(history: Bool) -> some View {
|
||||
Button(role: .destructive) {
|
||||
if history {
|
||||
player.removeHistoryItems()
|
||||
} else {
|
||||
player.removeQueueItems()
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
return Button(role: .destructive) {
|
||||
removeAllButtonAction(history: history)
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
} else {
|
||||
return Button {
|
||||
removeAllButtonAction(history: history)
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
}
|
||||
|
||||
private func removeAllButtonAction(history: Bool) {
|
||||
_ = history ? player.removeHistoryItems() : player.removeQueueItems()
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayerQueueView_Previews: PreviewProvider {
|
||||
|
@@ -11,14 +11,14 @@ struct VideoDetails: View {
|
||||
@Binding var fullScreen: Bool
|
||||
|
||||
@State private var subscribed = false
|
||||
@State private var confirmationShown = false
|
||||
@State private var presentingUnsubscribeAlert = false
|
||||
@State private var presentingAddToPlaylist = false
|
||||
@State private var presentingShareSheet = false
|
||||
@State private var shareURL: URL?
|
||||
|
||||
@State private var currentPage = Page.details
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@@ -82,7 +82,7 @@ struct VideoDetails: View {
|
||||
if fullScreen {
|
||||
fullScreen = false
|
||||
} else {
|
||||
self.dismiss()
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,19 +184,26 @@ struct VideoDetails: View {
|
||||
Section {
|
||||
if subscribed {
|
||||
Button("Unsubscribe") {
|
||||
confirmationShown = true
|
||||
presentingUnsubscribeAlert = true
|
||||
}
|
||||
#if os(iOS)
|
||||
.backport
|
||||
.tint(.gray)
|
||||
#endif
|
||||
.confirmationDialog("Are you you want to unsubscribe from \(video!.channel.name)?", isPresented: $confirmationShown) {
|
||||
Button("Unsubscribe") {
|
||||
subscriptions.unsubscribe(video!.channel.id)
|
||||
.alert(isPresented: $presentingUnsubscribeAlert) {
|
||||
Alert(
|
||||
title: Text(
|
||||
"Are you you want to unsubscribe from \(video!.channel.name)?"
|
||||
),
|
||||
primaryButton: .destructive(Text("Unsubscribe")) {
|
||||
subscriptions.unsubscribe(video!.channel.id)
|
||||
|
||||
withAnimation {
|
||||
subscribed.toggle()
|
||||
}
|
||||
}
|
||||
withAnimation {
|
||||
subscribed.toggle()
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Button("Subscribe") {
|
||||
@@ -206,12 +213,12 @@ struct VideoDetails: View {
|
||||
subscribed.toggle()
|
||||
}
|
||||
}
|
||||
.backport
|
||||
.tint(.blue)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 13))
|
||||
.buttonStyle(.borderless)
|
||||
.buttonBorderShape(.roundedRectangle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,13 +248,13 @@ struct VideoDetails: View {
|
||||
Text(published)
|
||||
}
|
||||
|
||||
if let publishedAt = video.publishedAt {
|
||||
if let date = video.publishedAt {
|
||||
if video.publishedDate != nil {
|
||||
Text("•")
|
||||
.foregroundColor(.secondary)
|
||||
.opacity(0.3)
|
||||
}
|
||||
Text(publishedAt.formatted(date: .abbreviated, time: .omitted))
|
||||
Text(formattedPublishedAt(date))
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
@@ -257,6 +264,15 @@ struct VideoDetails: View {
|
||||
}
|
||||
}
|
||||
|
||||
func formattedPublishedAt(_ date: Date) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
dateFormatter.dateStyle = .short
|
||||
dateFormatter.timeStyle = .none
|
||||
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
var countsSection: some View {
|
||||
Group {
|
||||
if let video = player.currentVideo {
|
||||
@@ -338,11 +354,17 @@ struct VideoDetails: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
if let description = video.description {
|
||||
Text(description)
|
||||
.textSelection(.enabled)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.caption)
|
||||
.padding(.bottom, 4)
|
||||
Group {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
Text(description)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(description)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.caption)
|
||||
.padding(.bottom, 4)
|
||||
} else {
|
||||
Text("No description")
|
||||
.foregroundColor(.secondary)
|
||||
|
@@ -16,8 +16,10 @@ struct VideoPlayerView: View {
|
||||
@State private var playerSize: CGSize = .zero
|
||||
@State private var fullScreen = false
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
#endif
|
||||
@@ -82,11 +84,11 @@ struct VideoPlayerView: View {
|
||||
fullScreen = true
|
||||
}
|
||||
},
|
||||
down: { dismiss() }
|
||||
down: { presentationMode.wrappedValue.dismiss() }
|
||||
)
|
||||
#endif
|
||||
|
||||
.background(.black)
|
||||
.background(Color.black)
|
||||
|
||||
Group {
|
||||
#if os(iOS)
|
||||
@@ -98,7 +100,7 @@ struct VideoPlayerView: View {
|
||||
VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreen)
|
||||
#endif
|
||||
}
|
||||
.background()
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
.modifier(VideoDetailsPaddingModifier(geometry: geometry, aspectRatio: player.controller?.aspectRatio, fullScreen: fullScreen))
|
||||
}
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user