fix scrolling logic for download pages (#258)
Some checks failed
Build and Release / Build IPA (push) Has been cancelled
Build and Release / Build macOS App (push) Has been cancelled

* fix scrolling in download series option menu homepage

* fix ai doodoo code, use .swipeactions

* fix
This commit is contained in:
Mousica 2025-12-27 21:02:09 +02:00 committed by GitHub
parent 0361eb9aed
commit 97b81186ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -37,27 +37,28 @@ struct DownloadView: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
VStack(spacing: 0) { Group {
Spacer()
.frame(height: 20)
CustomDownloadHeader(
selectedTab: $selectedTab,
searchText: $searchText,
isSearchActive: $isSearchActive,
sortOption: $sortOption,
showSortMenu: selectedTab == 1 && !jsController.savedAssets.isEmpty
)
if selectedTab == 0 { if selectedTab == 0 {
activeDownloadsView activeDownloadsView
.transition(.opacity)
} else { } else {
downloadedContentView downloadedContentView
.transition(.opacity)
} }
} }
.animation(.easeInOut(duration: 0.2), value: selectedTab) .safeAreaInset(edge: .top) {
VStack(spacing: 0) {
CustomDownloadHeader(
selectedTab: $selectedTab,
searchText: $searchText,
isSearchActive: $isSearchActive,
sortOption: $sortOption,
showSortMenu: selectedTab == 1 && !jsController.savedAssets.isEmpty
)
Divider()
}
.background(.ultraThinMaterial)
}
.navigationBarHidden(true) .navigationBarHidden(true)
.animation(.easeInOut(duration: 0.2), value: selectedTab)
.alert(NSLocalizedString("Delete Download", comment: ""), isPresented: $showDeleteAlert) { .alert(NSLocalizedString("Delete Download", comment: ""), isPresented: $showDeleteAlert) {
Button(NSLocalizedString("Delete", comment: ""), role: .destructive) { Button(NSLocalizedString("Delete", comment: ""), role: .destructive) {
if let asset = assetToDelete { if let asset = assetToDelete {
@ -117,7 +118,7 @@ struct DownloadView: View {
totalEpisodes: filteredAndSortedAssets.count, totalEpisodes: filteredAndSortedAssets.count,
totalSize: filteredAndSortedAssets.reduce(0) { $0 + $1.fileSize } totalSize: filteredAndSortedAssets.reduce(0) { $0 + $1.fileSize }
) )
DownloadedSection( DownloadedSection(
groups: groupedAssets, groups: groupedAssets,
onDelete: { asset in onDelete: { asset in
@ -130,6 +131,12 @@ struct DownloadView: View {
.padding(.top, 20) .padding(.top, 20)
.scrollViewBottomPadding() .scrollViewBottomPadding()
} }
.onAppear {
UIScrollView.appearance().bounces = false
}
.onDisappear {
UIScrollView.appearance().bounces = true
}
} }
} }
} }
@ -1043,6 +1050,7 @@ struct EnhancedShowEpisodesView: View {
var body: some View { var body: some View {
ZStack { ZStack {
heroImageSection
mainScrollView mainScrollView
.navigationBarHidden(true) .navigationBarHidden(true)
.ignoresSafeArea(.container, edges: .top) .ignoresSafeArea(.container, edges: .top)
@ -1050,12 +1058,12 @@ struct EnhancedShowEpisodesView: View {
} }
.onAppear { .onAppear {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first, let window = windowScene.windows.first,
let navigationController = window.rootViewController?.children.first as? UINavigationController { let navigationController = window.rootViewController?.children.first as? UINavigationController {
navigationController.interactivePopGestureRecognizer?.isEnabled = true navigationController.interactivePopGestureRecognizer?.isEnabled = true
navigationController.interactivePopGestureRecognizer?.delegate = nil navigationController.interactivePopGestureRecognizer?.delegate = nil
} }
NotificationCenter.default.post(name: .hideTabBar, object: nil) NotificationCenter.default.post(name: .hideTabBar, object: nil)
} }
.onDisappear { .onDisappear {
@ -1091,10 +1099,7 @@ struct EnhancedShowEpisodesView: View {
@ViewBuilder @ViewBuilder
private var mainScrollView: some View { private var mainScrollView: some View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
ZStack(alignment: .top) { contentContainer
heroImageSection
contentContainer
}
} }
.onAppear { .onAppear {
UIScrollView.appearance().bounces = false UIScrollView.appearance().bounces = false
@ -1103,22 +1108,24 @@ struct EnhancedShowEpisodesView: View {
@ViewBuilder @ViewBuilder
private var heroImageSection: some View { private var heroImageSection: some View {
Group { if let posterURL = group.posterURL {
if let posterURL = group.posterURL { LazyImage(url: posterURL) { state in
LazyImage(url: posterURL) { @MainActor state in if let uiImage = state.imageContainer?.image {
if let uiImage = state.imageContainer?.image { Image(uiImage: uiImage)
Image(uiImage: uiImage) .resizable()
.resizable() .aspectRatio(contentMode: .fill)
.aspectRatio(contentMode: .fill) } else {
.frame(width: UIScreen.main.bounds.width, height: 700) placeholderGradient
.clipped()
} else {
placeholderGradient
}
} }
} else {
placeholderGradient
} }
.ignoresSafeArea(.all)
.frame(maxWidth: .infinity, maxHeight: 400)
.clipped()
} else {
placeholderGradient
.ignoresSafeArea(.all)
.frame(maxWidth: .infinity, maxHeight: 400)
.clipped()
} }
} }
@ -1135,8 +1142,6 @@ struct EnhancedShowEpisodesView: View {
endPoint: .bottomTrailing endPoint: .bottomTrailing
) )
) )
.frame(width: UIScreen.main.bounds.width, height: 700)
.clipped()
} }
@ViewBuilder @ViewBuilder
@ -1318,55 +1323,15 @@ struct EnhancedEpisodeRow: View {
let showDivider: Bool let showDivider: Bool
let onPlay: (DownloadedAsset) -> Void let onPlay: (DownloadedAsset) -> Void
let onDelete: (DownloadedAsset) -> Void let onDelete: (DownloadedAsset) -> Void
@State private var swipeOffset: CGFloat = 0
@State private var isShowingActions: Bool = false
@State private var dragState = DragState.inactive
struct DragState {
var translation: CGSize
var isActive: Bool
static var inactive: DragState {
DragState(translation: .zero, isActive: false)
}
}
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
private var fillerBadgeOpacity: Double { colorScheme == .dark ? 0.18 : 0.12 } private var fillerBadgeOpacity: Double { colorScheme == .dark ? 0.18 : 0.12 }
var body: some View { var body: some View {
ZStack {
actionButtonsBackground
episodeCellContent
}
}
private var actionButtonsBackground: some View {
HStack {
Spacer()
Button(action: {
onDelete(asset)
}) {
VStack(spacing: 4) {
Image(systemName: "trash.fill")
.font(.title2)
.foregroundColor(.red)
Text("Delete")
.font(.caption)
.foregroundColor(.red)
}
.frame(width: 60)
}
.frame(height: 76)
}
.zIndex(0)
}
private var episodeCellContent: some View {
HStack { HStack {
// Thumbnail // Thumbnail
Group { Group {
if let backdropURL = asset.metadata?.backdropURL ?? asset.metadata?.posterURL { if let backdropURL = asset.metadata?.backdropURL ?? asset.metadata?.posterURL {
LazyImage(url: backdropURL) { @MainActor state in LazyImage(url: backdropURL) { state in
if let uiImage = state.imageContainer?.image { if let uiImage = state.imageContainer?.image {
Image(uiImage: uiImage) Image(uiImage: uiImage)
.resizable() .resizable()
@ -1391,7 +1356,7 @@ struct EnhancedEpisodeRow: View {
} }
.frame(width: 100, height: 56) .frame(width: 100, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack(spacing: 8) { HStack(spacing: 8) {
Text("Episode \(asset.metadata?.episode ?? 0)") Text("Episode \(asset.metadata?.episode ?? 0)")
@ -1416,9 +1381,9 @@ struct EnhancedEpisodeRow: View {
.lineLimit(1) .lineLimit(1)
} }
} }
Spacer() Spacer()
CircularProgressBar(progress: 0.0) CircularProgressBar(progress: 0.0)
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
.padding(.trailing, 4) .padding(.trailing, 4)
@ -1429,23 +1394,18 @@ struct EnhancedEpisodeRow: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(cellBackground) .background(cellBackground)
.clipShape(RoundedRectangle(cornerRadius: 15)) .clipShape(RoundedRectangle(cornerRadius: 15))
.offset(x: swipeOffset + dragState.translation.width) .swipeActions(edge: .trailing) {
.zIndex(1) Button(role: .destructive, action: {
.scaleEffect(dragState.isActive ? 0.98 : 1.0) onDelete(asset)
.animation(.spring(response: 0.4, dampingFraction: 0.8), value: swipeOffset) }) {
.animation(.spring(response: 0.3, dampingFraction: 0.6), value: dragState.isActive) Label("Delete", systemImage: "trash.fill")
.simultaneousGesture( }
DragGesture(coordinateSpace: .local) }
.onChanged { value in .onTapGesture {
handleDragChanged(value) onPlay(asset)
} }
.onEnded { value in
handleDragEnded(value)
}
)
.onTapGesture { handleTap() }
} }
private var cellBackground: some View { private var cellBackground: some View {
RoundedRectangle(cornerRadius: 15) RoundedRectangle(cornerRadius: 15)
.fill(Color(UIColor.systemBackground)) .fill(Color(UIColor.systemBackground))
@ -1468,78 +1428,6 @@ struct EnhancedEpisodeRow: View {
) )
) )
} }
private func handleDragChanged(_ value: DragGesture.Value) {
let translation = value.translation
let velocity = value.velocity
let isHorizontalGesture = abs(translation.width) > abs(translation.height)
let hasSignificantHorizontalMovement = abs(translation.width) > 10
if isHorizontalGesture && hasSignificantHorizontalMovement {
dragState = .inactive
let proposedOffset = swipeOffset + translation.width
let maxSwipe: CGFloat = 60 // Only one button
if translation.width < 0 {
let newOffset = max(proposedOffset, -maxSwipe)
if proposedOffset < -maxSwipe {
let resistance = abs(proposedOffset + maxSwipe) * 0.15
swipeOffset = -maxSwipe - resistance
} else {
swipeOffset = newOffset
}
} else if isShowingActions {
swipeOffset = min(max(proposedOffset, -maxSwipe), maxSwipe * 0.2)
}
} else if !hasSignificantHorizontalMovement {
dragState = .inactive
}
}
private func handleDragEnded(_ value: DragGesture.Value) {
let translation = value.translation
let velocity = value.velocity
dragState = .inactive
let isHorizontalGesture = abs(translation.width) > abs(translation.height)
let hasSignificantHorizontalMovement = abs(translation.width) > 10
if isHorizontalGesture && hasSignificantHorizontalMovement {
let maxSwipe: CGFloat = 60
let threshold = maxSwipe * 0.3
let velocityThreshold: CGFloat = 500
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
if translation.width < -threshold || velocity.width < -velocityThreshold {
swipeOffset = -maxSwipe
isShowingActions = true
} else if translation.width > threshold || velocity.width > velocityThreshold {
swipeOffset = 0
isShowingActions = false
} else {
swipeOffset = isShowingActions ? -maxSwipe : 0
}
}
} else {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
swipeOffset = isShowingActions ? -60 : 0
}
}
}
private func handleTap() {
if isShowingActions {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
swipeOffset = 0
isShowingActions = false
}
} else {
onPlay(asset)
}
}
} }
struct SearchableStyleModifier: ViewModifier { struct SearchableStyleModifier: ViewModifier {