Model improvements

This commit is contained in:
Arkadiusz Fal
2022-08-31 21:24:46 +02:00
parent b220f212df
commit 0d3ccc00ce
23 changed files with 190 additions and 133 deletions

View File

@@ -278,6 +278,14 @@ enum VisibleSection: String, CaseIterable, Comparable, Defaults.Serializable {
enum WatchedVideoStyle: String, Defaults.Serializable {
case nothing, badge, decreasedOpacity, both
var isShowingBadge: Bool {
self == .badge || self == .both
}
var isDecreasingOpacity: Bool {
self == .decreasedOpacity || self == .both
}
}
enum WatchedVideoBadgeColor: String, Defaults.Serializable {

View File

@@ -12,10 +12,8 @@ struct ContentView: View {
@EnvironmentObject<CommentsModel> private var comments
@EnvironmentObject<InstancesModel> private var instances
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<NetworkStateModel> private var networkState
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlayerControlsModel> private var playerControls
@EnvironmentObject<PlayerTimeModel> private var playerTime
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@@ -60,9 +58,7 @@ struct ContentView: View {
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(networkState)
.environmentObject(player)
.environmentObject(playerTime)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(search)

View File

@@ -31,19 +31,28 @@ struct ChapterView: View {
}
@ViewBuilder func smallImage(_ chapter: Chapter) -> some View {
WebImage(url: chapter.image)
.resizable()
.placeholder {
ProgressView()
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: chapter.image) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.indicator(.activity)
#if os(tvOS)
.frame(width: thumbnailWidth, height: 140)
.mask(RoundedRectangle(cornerRadius: 12))
#else
.frame(width: thumbnailWidth, height: 60)
.mask(RoundedRectangle(cornerRadius: 6))
#endif
} else {
WebImage(url: chapter.image)
.resizable()
.placeholder {
ProgressView()
}
.indicator(.activity)
#if os(tvOS)
.frame(width: thumbnailWidth, height: 140)
.mask(RoundedRectangle(cornerRadius: 12))
#else
.frame(width: thumbnailWidth, height: 60)
.mask(RoundedRectangle(cornerRadius: 6))
#endif
}
}
private var thumbnailWidth: Double {

View File

@@ -72,7 +72,7 @@ struct PlayerControls: View {
}
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
if model.presentingControls, !model.presentingOverlays {
Section {
#if !os(tvOS)
HStack {
seekBackwardButton
@@ -160,7 +160,8 @@ struct PlayerControls: View {
.offset(y: -playerControlsLayout.timelineHeight - 5)
#endif
}
}
}.opacity(model.presentingControls && !model.presentingOverlays ? 1 : 0)
}
}
.frame(maxWidth: .infinity)
@@ -219,14 +220,23 @@ struct PlayerControls: View {
let video = item.video,
let url = thumbnails.best(video)
{
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: url) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.indicator(.activity)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.indicator(.activity)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}

View File

@@ -60,7 +60,7 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable {
case .veryLarge:
return 40
case .large:
return 30
return 25
case .medium:
return 25
case .small:

View File

@@ -44,9 +44,10 @@ struct TimelineView: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass
#endif
@ObservedObject private var playerTime = PlayerTimeModel.shared
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlayerControlsModel> private var controls
@EnvironmentObject<PlayerTimeModel> private var playerTime
@Default(.playerControlsLayout) private var regularPlayerControlsLayout
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
@@ -392,7 +393,7 @@ struct TimelineView_Previews: PreviewProvider {
static var previews: some View {
let playerModel = PlayerModel()
playerModel.currentItem = .init(Video.fixture)
let playerTimeModel = PlayerTimeModel()
let playerTimeModel = PlayerTimeModel.shared
playerTimeModel.player = playerModel
playerTimeModel.currentTime = .secondsInDefaultTimescale(33)
playerTimeModel.duration = .secondsInDefaultTimescale(100)
@@ -400,7 +401,6 @@ struct TimelineView_Previews: PreviewProvider {
TimelineView()
}
.environmentObject(playerModel)
.environmentObject(playerTimeModel)
.environmentObject(PlayerControlsModel())
.padding()
}

View File

@@ -50,14 +50,20 @@ extension VideoPlayerView {
return
}
if orientation.isLandscape {
playerControls.presentingControls = false
player.enterFullScreen(showControls: false)
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
player.exitFullScreen(showControls: false)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
orientationDebouncer.callback = {
DispatchQueue.main.async {
if orientation.isLandscape {
playerControls.presentingControls = false
player.enterFullScreen(showControls: false)
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
player.exitFullScreen(showControls: false)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
}
}
orientationDebouncer.call()
}
}
}

View File

@@ -3,6 +3,7 @@ import AVKit
import CoreMotion
#endif
import Defaults
import Repeat
import Siesta
import SwiftUI
@@ -41,6 +42,7 @@ struct VideoPlayerView: View {
@State internal var orientation = UIInterfaceOrientation.portrait
@State internal var lastOrientation: UIInterfaceOrientation?
@State internal var orientationDebouncer = Debouncer(.milliseconds(300))
#elseif os(macOS)
var hoverThrottle = Throttle(interval: 0.5)
var mouseLocation: CGPoint { NSEvent.mouseLocation }

View File

@@ -27,6 +27,7 @@ struct SearchView: View {
@EnvironmentObject<SearchModel> private var state
private var favorites = FavoritesModel.shared
@Default(.recentlyOpened) private var recentlyOpened
@Default(.saveRecents) private var saveRecents
private var videos = [Video]()
@@ -287,11 +288,11 @@ struct SearchView: View {
VStack {
List {
Section(header: Text("Recents")) {
if recentItems.isEmpty {
if recentlyOpened.isEmpty {
Text("Search history is empty")
.foregroundColor(.secondary)
}
ForEach(recentItems) { item in
ForEach(recentlyOpened, id: \.tag) { item in
recentItemButton(item)
}
}
@@ -347,7 +348,6 @@ struct SearchView: View {
item.type == .channel ? RecentsModel.symbolSystemImage(item.title) :
"list.and.film"
Label(item.title, systemImage: systemImage)
.lineLimit(1)
}
.contextMenu {
removeButton(item)
@@ -391,10 +391,6 @@ struct SearchView: View {
searchDate != .any || searchDuration != .any
}
private var recentItems: [RecentItem] {
Defaults[.recentlyOpened].reversed()
}
private var searchSortOrderPicker: some View {
Picker("Sort", selection: $searchSortOrder) {
ForEach(SearchQuery.SortOrder.allCases) { sortOrder in

View File

@@ -70,20 +70,37 @@ struct VideoBanner: View {
#endif
}
private var smallThumbnail: some View {
WebImage(url: video?.thumbnailURL(quality: .medium))
.resizable()
.placeholder {
ProgressView()
@ViewBuilder private var smallThumbnail: some View {
let url = video?.thumbnailURL(quality: .medium)
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: url) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.indicator(.activity)
#if os(tvOS)
.frame(width: thumbnailWidth, height: 140)
.mask(RoundedRectangle(cornerRadius: 12))
#else
.frame(width: thumbnailWidth, height: 60)
.mask(RoundedRectangle(cornerRadius: 6))
#endif
#if os(tvOS)
.frame(width: thumbnailWidth, height: 140)
.mask(RoundedRectangle(cornerRadius: 12))
#else
.frame(width: thumbnailWidth, height: 60)
.mask(RoundedRectangle(cornerRadius: 6))
#endif
} else {
WebImage(url: url)
.resizable()
.placeholder {
ProgressView()
}
.indicator(.activity)
#if os(tvOS)
.frame(width: thumbnailWidth, height: 140)
.mask(RoundedRectangle(cornerRadius: 12))
#else
.frame(width: thumbnailWidth, height: 60)
.mask(RoundedRectangle(cornerRadius: 6))
#endif
}
}
private var thumbnailWidth: Double {

View File

@@ -382,7 +382,7 @@ struct VideoCell: View {
HStack(alignment: .center) {
if saveHistory,
watchedVideoStyle == .badge || watchedVideoStyle == .both,
watchedVideoStyle.isShowingBadge,
watch?.finished ?? false
{
Image(systemName: "checkmark.circle.fill")
@@ -419,27 +419,32 @@ struct VideoCell: View {
private var thumbnailImage: some View {
Group {
if let url = thumbnails.best(video) {
let url = thumbnails.best(video)
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: url) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
#if os(tvOS)
.frame(minHeight: 320)
#endif
} else {
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.onFailure { _ in
guard let url = url else { return }
thumbnails.insertUnloadable(url)
}
.indicator(.activity)
#if os(tvOS)
#if os(tvOS)
.frame(minHeight: 320)
#endif
} else {
ZStack {
Color("PlaceholderColor")
Image(systemName: "exclamationmark.triangle")
}
.font(.system(size: 30))
#endif
}
}
.mask(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))

View File

@@ -37,15 +37,23 @@ struct ChannelCell: View {
.opacity(0.6)
}
.foregroundColor(.secondary)
WebImage(url: channel.thumbnailURL)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: channel.thumbnailURL) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.indicator(.activity)
.frame(width: 88, height: 88)
.clipShape(Circle())
} else {
WebImage(url: channel.thumbnailURL)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
.indicator(.activity)
.frame(width: 88, height: 88)
.clipShape(Circle())
}
DetailBadge(text: channel.name, style: .prominent)

View File

@@ -37,15 +37,23 @@ struct ChannelPlaylistCell: View {
}
.foregroundColor(.secondary)
WebImage(url: playlist.thumbnailURL)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: playlist.thumbnailURL) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.indicator(.activity)
.frame(width: 165, height: 88)
.clipShape(RoundedRectangle(cornerRadius: 10))
} else {
WebImage(url: playlist.thumbnailURL)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
.indicator(.activity)
.frame(width: 165, height: 88)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
Group {
DetailBadge(text: playlist.title, style: .prominent)
.lineLimit(2)

View File

@@ -12,7 +12,6 @@ struct ControlsBar: View {
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerControlsModel> private var playerControls
@EnvironmentObject<PlayerModel> private var model
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<RecentsModel> private var recents
@@ -63,8 +62,8 @@ struct ControlsBar: View {
}
} else if detailsToggleFullScreen {
Button {
playerControls.presentingControlsOverlay = false
playerControls.presentingControls = false
model.controls.presentingControlsOverlay = false
model.controls.presentingControls = false
withAnimation {
fullScreen.toggle()
}
@@ -83,7 +82,7 @@ struct ControlsBar: View {
var controls: some View {
HStack(spacing: 4) {
Group {
if playerControls.isPlaying {
if model.controls.isPlaying {
Button(action: {
model.pause()
}) {
@@ -103,7 +102,7 @@ struct ControlsBar: View {
}
}
}
.disabled(playerControls.isLoadingVideo || model.currentItem.isNil)
.disabled(model.controls.isLoadingVideo || model.currentItem.isNil)
Button(action: { model.advanceToNextItem() }) {
Label("Next", systemImage: "forward.fill")
@@ -268,13 +267,22 @@ struct ControlsBar: View {
private var authorAvatar: some View {
Group {
if let video = model.currentItem?.video, let url = video.channel.thumbnailURL {
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
if #available(iOS 15, macOS 12, *) {
AsyncImage(url: url) { image in
image
.resizable()
} placeholder: {
Rectangle().foregroundColor(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.indicator(.activity)
} else {
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.indicator(.activity)
}
} else {
ZStack {
Color(white: 0.6)

View File

@@ -40,7 +40,6 @@ struct YatteeApp: App {
@StateObject private var networkState = NetworkStateModel()
@StateObject private var player = PlayerModel()
@StateObject private var playerControls = PlayerControlsModel()
@StateObject private var playerTime = PlayerTimeModel()
@StateObject private var playlists = PlaylistsModel()
@StateObject private var recents = RecentsModel()
@StateObject private var search = SearchModel()
@@ -63,7 +62,6 @@ struct YatteeApp: App {
.environmentObject(networkState)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playerTime)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(seek)
@@ -137,7 +135,6 @@ struct YatteeApp: App {
.environmentObject(networkState)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playerTime)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(search)
@@ -205,9 +202,10 @@ struct YatteeApp: App {
player.controls = playerControls
player.navigation = navigation
player.networkState = networkState
player.playerTime = playerTime
player.seek = seek
PlayerTimeModel.shared.player = player
if !accounts.current.isNil {
player.restoreQueue()
}