Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sora/MediaUtils/CustomPlayer/CustomPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2021,12 +2021,12 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
let remainingPercentage = (self.duration - self.currentTimeVal) / self.duration
let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0

if remainingPercentage <= threshold {
if self.aniListID != 0 && !self.aniListUpdatedSuccessfully && !self.aniListUpdateImpossible {
self.tryAniListUpdate()
}

if let tmdbId = self.tmdbID, tmdbId > 0, !self.traktUpdateSent {
self.sendTraktUpdate(tmdbId: tmdbId)
}
Expand Down
4 changes: 2 additions & 2 deletions Sora/MediaUtils/NormalPlayer/VideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,12 +324,12 @@ class VideoPlayerViewController: UIViewController {
let remainingPercentage = (duration - currentTime) / duration
let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0

if remainingPercentage <= threshold {
if self.aniListID != 0 && !self.aniListUpdateSent {
self.sendAniListUpdate()
}

if let tmdbId = self.tmdbID, tmdbId > 0, !self.traktUpdateSent {
self.sendTraktUpdate(tmdbId: tmdbId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ struct CircularProgressBar: View {
.animation(.linear, value: progress)

let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0
let threshold = remainingTimePercentage / 100.0

if progress >= threshold {
Image(systemName: "checkmark")
.font(.system(size: 12))
Expand Down
4 changes: 2 additions & 2 deletions Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ private extension EpisodeCell {
}
.tint(.blue)

if progress <= remainingTimePercentage {
if progress >= remainingTimePercentage / 100.0 {
Button(action: { markAsWatched() }) {
Label("Watched", systemImage: "checkmark.circle")
}
Expand Down Expand Up @@ -325,7 +325,7 @@ private extension EpisodeCell {

var contextMenuContent: some View {
Group {
if progress <= remainingTimePercentage {
if progress >= remainingTimePercentage / 100.0 {
Button(action: markAsWatched) {
Label("Mark Episode as Watched", systemImage: "checkmark.circle")
}
Expand Down
67 changes: 44 additions & 23 deletions Sora/Views/SearchView/SearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct SearchView: View {
@State private var saveDebounceTimer: Timer?
@State private var searchDebounceTimer: Timer?
@State private var isActive: Bool = false
@State private var currentSearchTask: Task<Void, Never>?

init(searchQuery: Binding<String>) {
self._searchQuery = searchQuery
Expand Down Expand Up @@ -244,40 +245,60 @@ struct SearchView: View {
hasNoResults = false
return
}

isSearchFieldFocused = false


currentSearchTask?.cancel()
currentSearchTask = nil

isSearching = true
hasNoResults = false
searchItems = []

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
Task {
do {
let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent)
if module.metadata.asyncJS == true {
jsController.fetchJsSearchResults(keyword: searchQuery, module: module) { items in
DispatchQueue.main.async {
searchItems = items
hasNoResults = items.isEmpty
isSearching = false
}

currentSearchTask = Task {
do {
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
guard !Task.isCancelled else { return }

let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent)

guard !Task.isCancelled else { return }

if module.metadata.asyncJS == true {
jsController.fetchJsSearchResults(keyword: searchQuery, module: module) { items in
guard !Task.isCancelled else { return }
DispatchQueue.main.async {
let uniqueItems = items.reduce(into: [String: SearchItem]()) { dict, item in
dict[item.href] = item
}.values
searchItems = Array(uniqueItems)
hasNoResults = uniqueItems.isEmpty
isSearching = false
currentSearchTask = nil
}
} else {
jsController.fetchSearchResults(keyword: searchQuery, module: module) { items in
DispatchQueue.main.async {
searchItems = items
hasNoResults = items.isEmpty
isSearching = false
}
}
} else {
jsController.fetchSearchResults(keyword: searchQuery, module: module) { items in
guard !Task.isCancelled else { return }
DispatchQueue.main.async {
let uniqueItems = items.reduce(into: [String: SearchItem]()) { dict, item in
dict[item.href] = item
}.values
searchItems = Array(uniqueItems)
hasNoResults = uniqueItems.isEmpty
isSearching = false
currentSearchTask = nil
}
}
} catch {
}
} catch {
if !Task.isCancelled {
Logger.shared.log("Error loading module: \(error)", type: "Error")
DispatchQueue.main.async {
isSearching = false
hasNoResults = true
currentSearchTask = nil
}
}
}
Expand Down
72 changes: 62 additions & 10 deletions Sora/Views/SettingsView/SettingsSubViews/SettingsViewPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ fileprivate struct SettingsStepperRow: View {
let step: Double
var formatter: (Double) -> String = { "\(Int($0))" }
var showDivider: Bool = true

init(icon: String, title: String, value: Binding<Double>, range: ClosedRange<Double>, step: Double, formatter: @escaping (Double) -> String = { "\(Int($0))" }, showDivider: Bool = true) {
self.icon = icon
self.title = title
Expand All @@ -166,24 +166,77 @@ fileprivate struct SettingsStepperRow: View {
self.formatter = formatter
self.showDivider = showDivider
}

var body: some View {
VStack(spacing: 0) {
HStack {
Image(systemName: icon)
.frame(width: 24, height: 24)
.foregroundStyle(.primary)

Text(title)
.foregroundStyle(.primary)

Spacer()

Stepper(formatter(value), value: $value, in: range, step: step)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)


if showDivider {
Divider()
.padding(.horizontal, 16)
}
}
}
}

fileprivate struct SettingsTextFieldRow: View {
let icon: String
let title: String
@Binding var value: Double
let range: ClosedRange<Double>
var showDivider: Bool = true

init(icon: String, title: String, value: Binding<Double>, range: ClosedRange<Double>, showDivider: Bool = true) {
self.icon = icon
self.title = title
self._value = value
self.range = range
self.showDivider = showDivider
}

var body: some View {
VStack(spacing: 0) {
HStack {
Image(systemName: icon)
.frame(width: 24, height: 24)
.foregroundStyle(.primary)

Text(title)
.foregroundStyle(.primary)

Spacer()

TextField("", value: $value, format: .number)
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
.frame(width: 60)
.onChange(of: value) { newValue in
if newValue < range.lowerBound {
value = range.lowerBound
} else if newValue > range.upperBound {
value = range.upperBound
}
}

Text("%")
.foregroundStyle(.gray)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)

if showDivider {
Divider()
.padding(.horizontal, 16)
Expand Down Expand Up @@ -247,12 +300,11 @@ struct SettingsViewPlayer: View {
showDivider: true
)

SettingsPickerRow(
SettingsTextFieldRow(
icon: "timer",
title: NSLocalizedString("Completion Percentage", comment: ""),
options: [60.0, 70.0, 80.0, 90.0, 95.0, 100.0],
optionToString: { "\(Int($0))%" },
selection: $remainingTimePercentage,
value: $remainingTimePercentage,
range: 0...100,
showDivider: false
)
}
Expand Down