* 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
This commit is contained in:
50/50 2025-06-11 16:37:28 +02:00 committed by GitHub
parent 75c9d6bf07
commit eaa6a6d9e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 211 additions and 126 deletions

View file

@ -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" : {

View file

@ -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

View file

@ -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)
}
}
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -117,6 +117,10 @@ struct ModuleSelectorMenu: View {
)
}
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color(.systemGray6).opacity(0))
.cornerRadius(12)
}
}
}

View file

@ -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(

View file

@ -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)

View file

@ -1,4 +1,5 @@
{
"originHash" : "e12f82ce5205016ea66a114308acd41450cfe950ccb1aacfe0e26181d2036fa4",
"pins" : [
{
"identity" : "drops",
@ -28,5 +29,5 @@
}
}
],
"version" : 2
"version" : 3
}