Skip to content

Commit

Permalink
fix: ui ux minor changes & video player gesture improvements
Browse files Browse the repository at this point in the history
video player supports double-tap on screen, seeks on drag, adds bottom padding for playlist items
  • Loading branch information
danielbady committed Dec 13, 2023
1 parent f0793b7 commit 68fee9d
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 68 deletions.
1 change: 1 addition & 0 deletions Sources/Features/ContentCore/ContentCore+View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ public extension ContentCore {
}
.frame(maxWidth: .infinity)
.padding(.horizontal)
.padding(.bottom)
}
.onAppear {
proxy.scrollTo(selectedItemId, anchor: .center)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Features/ModuleLists/ModuleListsFeature+View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ extension ModuleListsFeature.View: View {
await viewStore.send(.onTask).finish()
}
}
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, minHeight: 300)
}
}

Expand Down
30 changes: 14 additions & 16 deletions Sources/Features/VideoPlayer/Components/ProgressBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ struct ProgressBar: View {
private var viewState: ViewStore<PlayerClient.Status.Playback?, VideoPlayerFeature.Action.ViewAction>

@SwiftUI.State
private var dragProgress: Double?
private var dragProgress: Double = 0
@SwiftUI.State
private var isDragging = false

@Dependency(\.dateComponentsFormatter)
var formatter
Expand All @@ -39,7 +41,7 @@ struct ProgressBar: View {
.preferredColorScheme(.dark)
Color.white
.frame(
width: proxy.size.width * (isDragging ? dragProgress ?? progress : progress),
width: proxy.size.width * dragProgress,
height: proxy.size.height,
alignment: .leading
)
Expand All @@ -60,18 +62,13 @@ struct ProgressBar: View {
.onChanged { value in
if !isDragging {
dragProgress = progress
isDragging = true
}

let locationX = value.location.x
let percentage = locationX / proxy.size.width

dragProgress = max(0, min(1.0, percentage))
dragProgress = max(0, min(1.0, value.location.x / proxy.size.width))
viewState.send(.didSkipTo(time: dragProgress))
}
.onEnded { _ in
if let dragProgress {
viewState.send(.didSkipTo(time: dragProgress))
}
dragProgress = nil
isDragging = false
}
)
.animation(.spring(response: 0.3), value: isDragging)
Expand All @@ -94,12 +91,17 @@ struct ProgressBar: View {
.font(.caption.monospacedDigit())
}
.disabled(!canUseControls)
.onAppear {
dragProgress = viewState.state?.progress ?? 0
}
// Stop initial bounce
.animation(.linear(duration: 0), value: dragProgress)
}

private var progressDisplayTime: String {
if canUseControls {
if isDragging {
let time = (dragProgress ?? .zero) * (viewState.state?.totalDuration ?? .zero)
let time = dragProgress * (viewState.state?.totalDuration ?? .zero)
return formatter.playbackTimestamp(time) ?? Self.defaultZeroTime
} else {
return formatter.playbackTimestamp(viewState.state?.duration ?? .zero) ?? Self.defaultZeroTime
Expand All @@ -122,10 +124,6 @@ private extension ProgressBar {
}
}

var isDragging: Bool {
dragProgress != nil
}

var canUseControls: Bool {
if let totalDuration = viewState.state?.totalDuration {
return !totalDuration.isNaN && !totalDuration.isInfinite && !totalDuration.isZero
Expand Down
11 changes: 7 additions & 4 deletions Sources/Features/VideoPlayer/VideoPlayerFeature+Reducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ extension VideoPlayerFeature: Reducer {
return state.clearForChangedLinkIfNeeded(linkId)

case let .view(.didSkipTo(time)):
return .run { _ in
await playerClient.seek(time)
}
return .merge(
state.delayDismissOverlayIfNeeded(),
.run { _ in
await playerClient.seek(time)
}
)

case .view(.didTogglePlayback):
let isPlaying = state.player.playback?.state == .playing
Expand All @@ -95,7 +98,7 @@ extension VideoPlayerFeature: Reducer {
await playerClient.setRate(.init(rate))
}

case .view(.didSkipFowards):
case .view(.didSkipForward):
let skipTime = state.playerSettings.skipTime // In seconds
let currentProgress = state.player.playback?.progress ?? .zero
let totalDuration = state.player.playback?.totalDuration ?? 1
Expand Down
130 changes: 84 additions & 46 deletions Sources/Features/VideoPlayer/VideoPlayerFeature+View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,57 @@ extension VideoPlayerFeature.View: View {
@MainActor
public var body: some View {
ZStack {
WithViewStore(store, observe: \.overlay == nil) { viewStore in
PlayerView(
player: player(),
gravity: gravity,
enablePIP: $enablePiP
)
// Reducer should not handle these properties, they should be binded to the view instead.
.pictureInPictureIsPossible { possible in
pipPossible = possible
}
.pictureInPictureIsSupported { supported in
pipSupported = supported
}
.pictureInPictureStatus { status in
pipStatus = status
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.ignoresSafeArea(.all, edges: .all)
.contentShape(Rectangle())
.gesture(
MagnificationGesture()
.onEnded { _ in
if gravity == .resizeAspect {
gravity = .resizeAspectFill
} else {
gravity = .resizeAspect
}
GeometryReader { proxy in
WithViewStore(store, observe: \.overlay == nil) { viewStore in
WithViewStore(store, observe: RateBufferingState.init) { rateBufferingState in
PlayerView(
player: player(),
gravity: gravity,
enablePIP: $enablePiP
)
// Reducer should not handle these properties, they should be binded to the view instead.
.pictureInPictureIsPossible { possible in
pipPossible = possible
}
.pictureInPictureIsSupported { supported in
pipSupported = supported
}
)
.gesture(
TapGesture()
.onEnded {
store.send(.view(.didTapPlayer))
.pictureInPictureStatus { status in
pipStatus = status
}
)
#if os(iOS)
.statusBarHidden(viewStore.state)
.animation(.easeInOut, value: viewStore.state)
#endif
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
.ignoresSafeArea(.all, edges: .all)
.contentShape(Rectangle())
.gesture(
MagnificationGesture()
.onEnded { scale in
if scale < 1 {
gravity = .resizeAspect
} else {
gravity = .resizeAspectFill
}
}
)
.gesture(SimultaneousGesture(TapGesture(count: 2), TapGesture(count: 1))
.simultaneously(with: DragGesture(minimumDistance: 0, coordinateSpace: .local)
).onEnded { value in
if (value.first?.first != nil) {
if proxy.size.width / 2 > (value.second?.location.x ?? .zero) {
rateBufferingState.send(.view(.didSkipBackwards))
} else {
rateBufferingState.send(.view(.didSkipForward))
}
} else {
store.send(.view(.didTapPlayer))
}
})
#if os(iOS)
.statusBarHidden(viewStore.state)
.animation(.easeInOut, value: viewStore.state)
#endif
}
}
}
}
.overlay {
Expand Down Expand Up @@ -112,13 +123,37 @@ extension VideoPlayerFeature.View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
if viewStore.state {
Color.black
.opacity(0.35)
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.onTapGesture {
store.send(.view(.didTapPlayer))
GeometryReader { proxy in
WithViewStore(store, observe: RateBufferingState.init) { rateBufferingState in
Color.black
.opacity(0.35)
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.gesture(
MagnificationGesture()
.onEnded { scale in
if scale < 1 {
gravity = .resizeAspect
} else {
gravity = .resizeAspectFill
}
}
)
.gesture(SimultaneousGesture(TapGesture(count: 2), TapGesture(count: 1))
.simultaneously(with: DragGesture(minimumDistance: 0, coordinateSpace: .local)
).onEnded { value in
if (value.first?.first != nil) {
if proxy.size.width / 2 > (value.second?.location.x ?? .zero) {
rateBufferingState.send(.view(.didSkipBackwards))
} else {
rateBufferingState.send(.view(.didSkipForward))
}
} else {
store.send(.view(.didTapPlayer))
}
})
}
}
}
}
.animation(.easeInOut, value: viewStore.state)
Expand Down Expand Up @@ -229,6 +264,9 @@ extension VideoPlayerFeature.View {
// Text(viewStore.playlist.title ?? "No title")
// .font(.footnote)
}
.onTapGesture {
store.send(.view(.didTapBackButton))
}
}

Spacer()
Expand Down Expand Up @@ -307,7 +345,7 @@ extension VideoPlayerFeature.View {
WithViewStore(store, observe: \.videoPlayerStatus == nil) { canShowControls in
if canShowControls.state {
WithViewStore(store, observe: RateBufferingState.init) { rateBufferingState in
HStack(spacing: 0) {
HStack(spacing: 10) {
Spacer()

Button {
Expand Down Expand Up @@ -341,7 +379,7 @@ extension VideoPlayerFeature.View {
.frame(width: 54, height: 54)

Button {
rateBufferingState.send(.view(.didSkipFowards))
rateBufferingState.send(.view(.didSkipForward))
} label: {
Image(systemName: "goforward")
.font(.title2.weight(.bold))
Expand Down
2 changes: 1 addition & 1 deletion Sources/Features/VideoPlayer/VideoPlayerFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public struct VideoPlayerFeature: Feature {
case didSelectMoreTab(State.Overlay.MoreTab)
case didTapCloseMoreOverlay
case didTogglePlayback
case didSkipFowards
case didSkipForward
case didSkipBackwards
case didChangePlaybackRate(Double)
case didSkipTo(time: CGFloat)
Expand Down

0 comments on commit 68fee9d

Please sign in to comment.