From eaa6a6d9e08825ae322399fc8a34186cb549477a Mon Sep 17 00:00:00 2001 From: 50/50 <80717571+50n50@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:37:28 +0200 Subject: [PATCH] Fixes (#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed episodecells getting stuck sliding * Enabled device scaling for ipad not good enough yet, not applied everywhere cuz idk where to apply exactly 💯 * Fixed blur in continue watching cells * Keyboard controls player * fixed downloadview buttons * Reduced tab bar outline opacity * Increased module selector hitbox * Fixed module add view * Fixed mediainfoview issues (description) + changed settingsviewdata footer medainfoview: 1: no swipe to go back 2: image shadows were fucked * Fixes --- Sora/Localizable.xcstrings | 9 +- .../Modules/ModuleAdditionSettingsView.swift | 74 ++++--- .../EpisodeCell/EpisodeCell.swift | 207 +++++++++++------- Sora/Views/MediaInfoView/MediaInfoView.swift | 36 +-- .../SearchView/SearchViewComponents.swift | 4 + .../SettingsSubViews/SettingsViewData.swift | 2 +- .../SettingsViewTrackers.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 3 +- 8 files changed, 211 insertions(+), 126 deletions(-) diff --git a/Sora/Localizable.xcstrings b/Sora/Localizable.xcstrings index 1b90ac5..bc0c7de 100644 --- a/Sora/Localizable.xcstrings +++ b/Sora/Localizable.xcstrings @@ -318,6 +318,9 @@ }, "Open in AniList" : { + }, + "Original Poster" : { + }, "Play" : { @@ -339,9 +342,6 @@ }, "Remove" : { - }, - "Remove All Caches" : { - }, "Remove Documents" : { @@ -366,9 +366,6 @@ }, "Reset Progress" : { - }, - "Revert Module Poster" : { - }, "Running Sora %@ - cranci1" : { diff --git a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift index 11929c4..c05c734 100644 --- a/Sora/Utils/Modules/ModuleAdditionSettingsView.swift +++ b/Sora/Utils/Modules/ModuleAdditionSettingsView.swift @@ -22,20 +22,20 @@ struct ModuleAdditionSettingsView: View { ZStack { LinearGradient( gradient: Gradient(colors: [ - colorScheme == .light ? Color.black : Color.white, - Color.accentColor.opacity(0.08) + colorScheme == .dark ? Color.black : Color.white, + Color.accentColor.opacity(0.05) ]), startPoint: .top, endPoint: .bottom ) - .ignoresSafeArea() + .ignoresSafeArea() VStack(spacing: 0) { HStack { Spacer() Capsule() .frame(width: 40, height: 5) - .foregroundColor(Color(.systemGray4)) + .foregroundColor(Color(.systemGray3)) .padding(.top, 10) Spacer() } @@ -57,17 +57,22 @@ struct ModuleAdditionSettingsView: View { } .frame(width: 90, height: 90) .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous)) - .shadow(color: Color.accentColor.opacity(0.18), radius: 10, x: 0, y: 6) + .shadow( + color: colorScheme == .dark + ? Color.black.opacity(0.3) + : Color.accentColor.opacity(0.15), + radius: 10, x: 0, y: 6 + ) .overlay( RoundedRectangle(cornerRadius: 22) - .stroke(Color.accentColor, lineWidth: 2) + .stroke(Color.accentColor.opacity(0.8), lineWidth: 2) ) .padding(.top, 10) VStack(spacing: 6) { Text(metadata.sourceName) .font(.system(size: 28, weight: .bold, design: .rounded)) - .foregroundColor(.primary) + .foregroundColor(colorScheme == .dark ? .white : .black) .multilineTextAlignment(.center) .padding(.top, 6) @@ -84,14 +89,19 @@ struct ModuleAdditionSettingsView: View { } .frame(width: 32, height: 32) .clipShape(Circle()) - .shadow(radius: 2) + .shadow( + color: colorScheme == .dark + ? Color.black.opacity(0.4) + : Color.gray.opacity(0.3), + radius: 2 + ) VStack(alignment: .leading, spacing: 0) { Text(metadata.author.name) .font(.headline) - .foregroundColor(.primary) + .foregroundColor(colorScheme == .dark ? .white : .black) Text("Author") .font(.caption2) - .foregroundColor(.secondary) + .foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.6)) } Spacer() } @@ -99,7 +109,11 @@ struct ModuleAdditionSettingsView: View { .padding(.vertical, 8) .background( Capsule() - .fill(Color.accentColor.opacity(colorScheme == .dark ? 0.13 : 0.08)) + .fill( + colorScheme == .dark + ? Color.accentColor.opacity(0.15) + : Color.accentColor.opacity(0.08) + ) ) .padding(.top, 2) } @@ -125,7 +139,7 @@ struct ModuleAdditionSettingsView: View { } .background( RoundedRectangle(cornerRadius: 22) - .fill(Color(.systemGray6).opacity(colorScheme == .dark ? 0.18 : 0.8)) + .fill(colorScheme == .dark ? Color.white.opacity(0.1) : Color.black.opacity(0.05)) ) .padding(.top, 18) .padding(.horizontal, 2) @@ -142,7 +156,7 @@ struct ModuleAdditionSettingsView: View { .padding(16) .background( RoundedRectangle(cornerRadius: 18) - .fill(Color(.systemGray6).opacity(colorScheme == .dark ? 0.13 : 0.85)) + .fill(colorScheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.04)) ) .padding(.top, 18) } @@ -152,8 +166,10 @@ struct ModuleAdditionSettingsView: View { VStack(spacing: 20) { ProgressView() .scaleEffect(1.5) + .tint(.accentColor) Text("Loading module information...") - .foregroundColor(.secondary) + .foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.6)) + .font(.body) } .frame(maxHeight: .infinity) .padding(.top, 100) @@ -165,6 +181,7 @@ struct ModuleAdditionSettingsView: View { Text(errorMessage) .foregroundColor(.red) .multilineTextAlignment(.center) + .font(.body) } .frame(maxHeight: .infinity) .padding(.top, 100) @@ -180,21 +197,26 @@ struct ModuleAdditionSettingsView: View { Text("Add Module") } .font(.headline) - .foregroundColor(colorScheme == .light ? .black : .white) + .foregroundColor(Color.accentColor) .frame(maxWidth: .infinity) .padding(.vertical, 14) .background( LinearGradient( gradient: Gradient(colors: [ - Color.accentColor.opacity(0.95), - Color.accentColor.opacity(0.7) + colorScheme == .dark ? Color.white : Color.black, + colorScheme == .dark ? Color.white.opacity(0.9) : Color.black.opacity(0.9) ]), startPoint: .leading, endPoint: .trailing ) .clipShape(RoundedRectangle(cornerRadius: 18)) ) - .shadow(color: Color.accentColor.opacity(0.18), radius: 8, x: 0, y: 4) + .shadow( + color: colorScheme == .dark + ? Color.black.opacity(0.3) + : Color.accentColor.opacity(0.25), + radius: 8, x: 0, y: 4 + ) .padding(.horizontal, 20) } .disabled(isLoading || moduleMetadata == nil) @@ -203,7 +225,7 @@ struct ModuleAdditionSettingsView: View { Button(action: { presentationMode.wrappedValue.dismiss() }) { Text("Cancel") .font(.body) - .foregroundColor(.secondary) + .foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.6)) .padding(.vertical, 8) } } @@ -271,18 +293,19 @@ struct FancyInfoTile: View { let icon: String let label: String let value: String + @Environment(\.colorScheme) var colorScheme var body: some View { VStack(spacing: 4) { Image(systemName: icon) .font(.system(size: 18, weight: .semibold)) - .foregroundColor(.accentColor) + .foregroundColor(colorScheme == .dark ? .white : .black) Text(label) .font(.caption2) - .foregroundColor(.secondary) + .foregroundColor(colorScheme == .dark ? Color.white.opacity(0.6) : Color.black.opacity(0.5)) Text(value) .font(.system(size: 15, weight: .semibold, design: .rounded)) - .foregroundColor(.primary) + .foregroundColor(colorScheme == .dark ? .white : .black) .lineLimit(1) .minimumScaleFactor(0.7) } @@ -294,16 +317,17 @@ struct FancyInfoTile: View { struct FancyUrlRow: View { let title: String let value: String + @Environment(\.colorScheme) var colorScheme var body: some View { HStack(spacing: 8) { Text(title) .font(.subheadline) - .foregroundColor(.secondary) + .foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.6)) Spacer() Text(value) .font(.footnote.monospaced()) - .foregroundColor(.accentColor) + .foregroundColor(colorScheme == .dark ? .white : .black) .lineLimit(1) .truncationMode(.middle) .onLongPressGesture { @@ -311,7 +335,7 @@ struct FancyUrlRow: View { DropManager.shared.showDrop(title: "Copied to Clipboard", subtitle: "", duration: 1.0, icon: UIImage(systemName: "doc.on.clipboard.fill")) } Image(systemName: "doc.on.clipboard") - .foregroundColor(.accentColor) + .foregroundColor(colorScheme == .dark ? .white : .black) .font(.system(size: 14)) .onTapGesture { UIPasteboard.general.string = value diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index 0707d64..e53e394 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -44,10 +44,12 @@ struct EpisodeCell: View { @State private var lastLoggedStatus: EpisodeDownloadStatus? @State private var downloadAnimationScale: CGFloat = 1.0 + @State private var isActionsVisible = false + @State private var panGesture = UIPanGestureRecognizer() + @State private var swipeOffset: CGFloat = 0 @State private var isShowingActions: Bool = false @State private var actionButtonWidth: CGFloat = 60 - @State private var dragState: DragState = .inactive @State private var retryAttempts: Int = 0 private let maxRetryAttempts: Int = 3 @@ -186,28 +188,73 @@ struct EpisodeCell: View { ) ) .clipShape(RoundedRectangle(cornerRadius: 15)) - .offset(x: swipeOffset + dragState.translation.width) + .offset(x: swipeOffset) .zIndex(1) - .scaleEffect(dragState.isActive ? 0.98 : 1.0) - .animation(.spring(response: 0.4, dampingFraction: 0.8), value: swipeOffset) - .animation(.spring(response: 0.3, dampingFraction: 0.6), value: dragState.isActive) + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: swipeOffset) .contextMenu { contextMenuContent } - .simultaneousGesture( - DragGesture(coordinateSpace: .local) + .highPriorityGesture( + DragGesture(minimumDistance: 10) .onChanged { value in - handleDragChanged(value) + let horizontalTranslation = value.translation.width + let verticalTranslation = value.translation.height + + // Only handle if it's a clear horizontal swipe + if abs(horizontalTranslation) > abs(verticalTranslation) * 1.5 { + if horizontalTranslation < 0 { + let maxSwipe = calculateMaxSwipeDistance() + swipeOffset = max(horizontalTranslation, -maxSwipe) + } else if isShowingActions { + let maxSwipe = calculateMaxSwipeDistance() + swipeOffset = max(horizontalTranslation - maxSwipe, -maxSwipe) + } + } } .onEnded { value in - handleDragEnded(value) + let horizontalTranslation = value.translation.width + let verticalTranslation = value.translation.height + + // Only handle if it was a clear horizontal swipe + if abs(horizontalTranslation) > abs(verticalTranslation) * 1.5 { + let maxSwipe = calculateMaxSwipeDistance() + let threshold = maxSwipe * 0.2 + + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + if horizontalTranslation < -threshold && !isShowingActions { + swipeOffset = -maxSwipe + isShowingActions = true + } else if horizontalTranslation > threshold && isShowingActions { + swipeOffset = 0 + isShowingActions = false + } else { + swipeOffset = isShowingActions ? -maxSwipe : 0 + } + } + } } ) } .onTapGesture { - handleTap() + if isShowingActions { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + swipeOffset = 0 + isShowingActions = false + } + } else if isMultiSelectMode { + onSelectionChanged?(!isSelected) + } else { + let imageUrl = episodeImageUrl.isEmpty ? defaultBannerImage : episodeImageUrl + onTap(imageUrl) + } } .onAppear { + // Configure the pan gesture + panGesture.delegate = nil + panGesture.cancelsTouchesInView = false + panGesture.delaysTouchesBegan = false + panGesture.delaysTouchesEnded = false + updateProgress() updateDownloadStatus() if UserDefaults.standard.string(forKey: "metadataProviders") ?? "TMDB" == "TMDB" { @@ -968,72 +1015,10 @@ struct EpisodeCell: View { .padding(.horizontal, 8) } - 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 = .dragging(translation: .zero) - - let proposedOffset = swipeOffset + translation.width - let maxSwipe = calculateMaxSwipeDistance() - - 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 = max(proposedOffset, -maxSwipe) - } - } 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 = calculateMaxSwipeDistance() - 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 ? -calculateMaxSwipeDistance() : 0 - } - } - } - private func handleTap() { - if isShowingActions { + if isActionsVisible { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - swipeOffset = 0 - isShowingActions = false + isActionsVisible = false } } else if isMultiSelectMode { onSelectionChanged?(!isSelected) @@ -1044,18 +1029,16 @@ struct EpisodeCell: View { } private func closeActionsIfNeeded() { - if isShowingActions { + if isActionsVisible { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - swipeOffset = 0 - isShowingActions = false + isActionsVisible = false } } } private func closeActionsAndPerform(action: @escaping () -> Void) { withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { - swipeOffset = 0 - isShowingActions = false + isActionsVisible = false } DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { @@ -1063,3 +1046,69 @@ struct EpisodeCell: View { } } } + +struct UIViewWrapper: UIViewRepresentable { + let panGesture: UIPanGestureRecognizer + let onSwipe: (SwipeDirection) -> Void + + enum SwipeDirection { + case left, right, none + } + + func makeUIView(context: Context) -> UIView { + let view = UIView() + view.isUserInteractionEnabled = true + view.backgroundColor = .clear + + // Remove any existing gesture recognizers + if let existingGestures = view.gestureRecognizers { + for gesture in existingGestures { + view.removeGestureRecognizer(gesture) + } + } + + // Add the pan gesture + panGesture.addTarget(context.coordinator, action: #selector(Coordinator.handlePan(_:))) + view.addGestureRecognizer(panGesture) + + return view + } + + func updateUIView(_ uiView: UIView, context: Context) { + // Ensure the view is user interaction enabled + uiView.isUserInteractionEnabled = true + } + + func makeCoordinator() -> Coordinator { + Coordinator(onSwipe: onSwipe) + } + + class Coordinator: NSObject { + let onSwipe: (SwipeDirection) -> Void + + init(onSwipe: @escaping (SwipeDirection) -> Void) { + self.onSwipe = onSwipe + } + + @objc func handlePan(_ gesture: UIPanGestureRecognizer) { + let translation = gesture.translation(in: gesture.view) + let velocity = gesture.velocity(in: gesture.view) + + if gesture.state == .ended { + if abs(velocity.x) > abs(velocity.y) && abs(velocity.x) > 500 { + if velocity.x < 0 { + onSwipe(.left) + } else { + onSwipe(.right) + } + } else if abs(translation.x) > abs(translation.y) && abs(translation.x) > 50 { + if translation.x < 0 { + onSwipe(.left) + } else { + onSwipe(.right) + } + } + } + } + } +} diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 0476211..5ebae74 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -124,6 +124,13 @@ struct MediaInfoView: View { .onAppear { buttonRefreshTrigger.toggle() tabBarController.hideTabBar() + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + let navigationController = window.rootViewController?.children.first as? UINavigationController { + navigationController.interactivePopGestureRecognizer?.isEnabled = true + navigationController.interactivePopGestureRecognizer?.delegate = nil + } } .onChange(of: selectedRange) { newValue in UserDefaults.standard.set(newValue.lowerBound, forKey: selectedRangeKey) @@ -224,16 +231,8 @@ struct MediaInfoView: View { Rectangle() .fill(Color.clear) .frame(height: 400) - VStack(alignment: .leading, spacing: 16) { - headerSection - if !episodeLinks.isEmpty { - episodesSection - } else { - noEpisodesSection - } - } - .padding() - .background( + + ZStack(alignment: .top) { LinearGradient( gradient: Gradient(stops: [ .init(color: (colorScheme == .dark ? Color.black : Color.white).opacity(0.0), location: 0.0), @@ -244,9 +243,20 @@ struct MediaInfoView: View { startPoint: .top, endPoint: .bottom ) - .clipShape(RoundedRectangle(cornerRadius: 0)) - .shadow(color: (colorScheme == .dark ? Color.black : Color.white).opacity(1), radius: 10, x: 0, y: 10) - ) + .frame(height: 300) + .clipShape(RoundedRectangle(cornerRadius: 0)) + .shadow(color: (colorScheme == .dark ? Color.black : Color.white).opacity(1), radius: 10, x: 0, y: 10) + + VStack(alignment: .leading, spacing: 16) { + headerSection + if !episodeLinks.isEmpty { + episodesSection + } else { + noEpisodesSection + } + } + .padding() + } } } } diff --git a/Sora/Views/SearchView/SearchViewComponents.swift b/Sora/Views/SearchView/SearchViewComponents.swift index 49c1201..95ebab5 100644 --- a/Sora/Views/SearchView/SearchViewComponents.swift +++ b/Sora/Views/SearchView/SearchViewComponents.swift @@ -117,6 +117,10 @@ struct ModuleSelectorMenu: View { ) } } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(Color(.systemGray6).opacity(0)) + .cornerRadius(12) } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift index c77f327..1aed8fa 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift @@ -154,7 +154,7 @@ struct SettingsViewData: View { VStack(spacing: 24) { SettingsSection( title: "App Storage", - footer: "The app cache allow the app to sho immages faster.\n\nClearing the documents folder will remove all the modules.\n\nThe App Data should never be erased if you don't know what that will cause." + footer: "The app cache helps the app load images faster.\n\nClearing the Documents folder will delete all downloaded modules.\n\nDo not erase App Data unless you understand the consequences — it may cause the app to malfunction." ) { VStack(spacing: 0) { SettingsButtonRow( diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift index 8dff56f..28f2cbd 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewTrackers.swift @@ -297,7 +297,7 @@ struct SettingsViewTrackers: View { SettingsSection( title: "Info", - footer: "Sora and cranci1 are not affiliated with AniList nor Trakt in any way.\n\nAlso note that progresses update may not be 100% accurate." + footer: "Sora and Cranci1 are not affiliated with AniList or Trakt in any way.\n\nAlso note that progress updates may not be 100% accurate." ) {} } .padding(.vertical, 20) diff --git a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0b5a161..d8a331d 100644 --- a/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sulfur.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "e12f82ce5205016ea66a114308acd41450cfe950ccb1aacfe0e26181d2036fa4", "pins" : [ { "identity" : "drops", @@ -28,5 +29,5 @@ } } ], - "version" : 2 + "version" : 3 }