From a774564212aba5f3785ed69880ddd903eb5763a0 Mon Sep 17 00:00:00 2001 From: kingbri Date: Fri, 18 Nov 2022 17:10:42 -0500 Subject: [PATCH] Ferrite: Improve overall UI - Make history buttons have 2 lines of text to give more context. If a batch is given, the full episode name is shown - Add a "now playing" section to player choices to show what the user will play in the event of a misclick - Make the maximum line limit in search results 4 lines to prevent long title results from taking up the entire cell - Fix light theme appearance with library since the picker and list weren't aligned right Signed-off-by: kingbri --- .../FerriteDB.xcdatamodel/contents | 2 +- .../PersistenceController.swift | 4 +- Ferrite/Models/RealDebridModels.swift | 212 +++++++++--------- Ferrite/ViewModels/NavigationViewModel.swift | 5 + Ferrite/ViewModels/ScrapingViewModel.swift | 2 +- Ferrite/Views/BatchChoiceView.swift | 1 + Ferrite/Views/LibraryView.swift | 5 +- .../Views/LibraryViews/BookmarksView.swift | 2 +- .../LibraryViews/HistoryButtonView.swift | 8 +- Ferrite/Views/MagnetChoiceView.swift | 14 ++ .../SearchResultButtonView.swift | 3 +- 11 files changed, 142 insertions(+), 116 deletions(-) diff --git a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents index 5407971..b89a6fc 100644 --- a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents +++ b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/Ferrite/DataManagement/PersistenceController.swift b/Ferrite/DataManagement/PersistenceController.swift index 85934ec..a3ac7f0 100644 --- a/Ferrite/DataManagement/PersistenceController.swift +++ b/Ferrite/DataManagement/PersistenceController.swift @@ -114,7 +114,6 @@ struct PersistenceController { newBookmark.leechers = bookmarkJson.leechers } - // TODO: Change timestamp to use a date instead of a double func createHistory(entryJson: HistoryEntryJson, date: Double?) { let historyDate = date.map { Date(timeIntervalSince1970: $0) } ?? Date() let historyDateString = DateFormatter.historyDateFormatter.string(from: historyDate) @@ -124,7 +123,8 @@ struct PersistenceController { newHistoryEntry.source = entryJson.source newHistoryEntry.name = entryJson.name newHistoryEntry.url = entryJson.url - newHistoryEntry.subName = entryJson.source + newHistoryEntry.subName = entryJson.subName + newHistoryEntry.timeStamp = entryJson.timeStamp ?? Date().timeIntervalSince1970 let historyRequest = History.fetchRequest() historyRequest.predicate = NSPredicate(format: "dateString = %@", historyDateString) diff --git a/Ferrite/Models/RealDebridModels.swift b/Ferrite/Models/RealDebridModels.swift index 208f253..3d6487e 100644 --- a/Ferrite/Models/RealDebridModels.swift +++ b/Ferrite/Models/RealDebridModels.swift @@ -11,182 +11,184 @@ import Foundation // MARK: - device code endpoint public struct DeviceCodeResponse: Codable, Sendable { - let deviceCode, userCode: String - let interval, expiresIn: Int - let verificationURL, directVerificationURL: String + let deviceCode, userCode: String + let interval, expiresIn: Int + let verificationURL, directVerificationURL: String - enum CodingKeys: String, CodingKey { - case deviceCode = "device_code" - case userCode = "user_code" - case interval - case expiresIn = "expires_in" - case verificationURL = "verification_url" - case directVerificationURL = "direct_verification_url" - } + enum CodingKeys: String, CodingKey { + case deviceCode = "device_code" + case userCode = "user_code" + case interval + case expiresIn = "expires_in" + case verificationURL = "verification_url" + case directVerificationURL = "direct_verification_url" + } } // MARK: - device credentials endpoint public struct DeviceCredentialsResponse: Codable, Sendable { - let clientID, clientSecret: String? + let clientID, clientSecret: String? - enum CodingKeys: String, CodingKey { - case clientID = "client_id" - case clientSecret = "client_secret" - } + enum CodingKeys: String, CodingKey { + case clientID = "client_id" + case clientSecret = "client_secret" + } } // MARK: - token endpoint public struct TokenResponse: Codable, Sendable { - let accessToken: String - let expiresIn: Int - let refreshToken, tokenType: String + let accessToken: String + let expiresIn: Int + let refreshToken, tokenType: String - enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case expiresIn = "expires_in" - case refreshToken = "refresh_token" - case tokenType = "token_type" - } + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case expiresIn = "expires_in" + case refreshToken = "refresh_token" + case tokenType = "token_type" + } } // MARK: - instantAvailability endpoint // Thanks Skitty! public struct InstantAvailabilityResponse: Codable, Sendable { - var data: InstantAvailabilityData? + var data: InstantAvailabilityData? - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() - if let data = try? container.decode(InstantAvailabilityData.self) { - self.data = data - } - } + if let data = try? container.decode(InstantAvailabilityData.self) { + self.data = data + } + } } struct InstantAvailabilityData: Codable, Sendable { - var rd: [[String: InstantAvailabilityInfo]] + var rd: [[String: InstantAvailabilityInfo]] } struct InstantAvailabilityInfo: Codable, Sendable { - var filename: String - var filesize: Int + var filename: String + var filesize: Int } // MARK: - Instant Availability client side structures public struct RealDebridIA: Codable, Hashable, Sendable { - let hash: String - let expiryTimeStamp: Double - var files: [RealDebridIAFile] = [] - var batches: [RealDebridIABatch] = [] + let hash: String + let expiryTimeStamp: Double + var files: [RealDebridIAFile] = [] + var batches: [RealDebridIABatch] = [] } public struct RealDebridIABatch: Codable, Hashable, Sendable { - let files: [RealDebridIABatchFile] + let files: [RealDebridIABatchFile] } public struct RealDebridIABatchFile: Codable, Hashable, Sendable { - let id: Int - let fileName: String + let id: Int + let fileName: String } public struct RealDebridIAFile: Codable, Hashable, Sendable { - let name: String - let batchIndex: Int - let batchFileIndex: Int + let name: String + let batchIndex: Int + let batchFileIndex: Int } public enum RealDebridIAStatus: Codable, Hashable, Sendable { - case full - case partial - case none + case full + case partial + case none } // MARK: - addMagnet endpoint public struct AddMagnetResponse: Codable, Sendable { - let id: String - let uri: String + let id: String + let uri: String } // MARK: - torrentInfo endpoint struct TorrentInfoResponse: Codable, Sendable { - let id, filename, originalFilename, hash: String - let bytes, originalBytes: Int - let host: String - let split, progress: Int - let status, added: String - let files: [TorrentInfoFile] - let links: [String] - let ended: String? - let speed: Int? - let seeders: Int? + let id, filename, originalFilename, hash: String + let bytes, originalBytes: Int + let host: String + let split, progress: Int + let status, added: String + let files: [TorrentInfoFile] + let links: [String] + let ended: String? + let speed: Int? + let seeders: Int? - enum CodingKeys: String, CodingKey { - case id, filename - case originalFilename = "original_filename" - case hash, bytes - case originalBytes = "original_bytes" - case host, split, progress, status, added, files, links, ended, speed, seeders - } + enum CodingKeys: String, CodingKey { + case id, filename + case originalFilename = "original_filename" + case hash, bytes + case originalBytes = "original_bytes" + case host, split, progress, status, added, files, links, ended, speed, seeders + } } struct TorrentInfoFile: Codable, Sendable { - let id: Int - let path: String - let bytes, selected: Int + let id: Int + let path: String + let bytes, selected: Int } public struct UserTorrentsResponse: Codable, Sendable { - let id, filename, hash: String - let bytes: Int - let host: String - let split, progress: Int - let status, added: String - let links: [String] - let speed, seeders: Int? - let ended: String? + let id, filename, hash: String + let bytes: Int + let host: String + let split, progress: Int + let status, added: String + let links: [String] + let speed, seeders: Int? + let ended: String? } // MARK: - unrestrictLink endpoint struct UnrestrictLinkResponse: Codable, Sendable { - let id, filename, mimeType: String - let filesize: Int - let link: String - let host: String - let hostIcon: String - let chunks, crc: Int - let download: String - let streamable: Int + let id, filename: String + let mimeType: String? + let filesize: Int + let link: String + let host: String + let hostIcon: String + let chunks, crc: Int + let download: String + let streamable: Int - enum CodingKeys: String, CodingKey { - case id, filename, mimeType, filesize, link, host - case hostIcon = "host_icon" - case chunks, crc, download, streamable - } + enum CodingKeys: String, CodingKey { + case id, filename, mimeType, filesize, link, host + case hostIcon = "host_icon" + case chunks, crc, download, streamable + } } // MARK: - User downloads list public struct UserDownloadsResponse: Codable, Sendable { - let id, filename, mimeType: String - let filesize: Int - let link: String - let host: String - let hostIcon: String - let chunks: Int - let download: String - let streamable: Int - let generated: String + let id, filename: String + let mimeType: String? + let filesize: Int + let link: String + let host: String + let hostIcon: String + let chunks: Int + let download: String + let streamable: Int + let generated: String - enum CodingKeys: String, CodingKey { - case id, filename, mimeType, filesize, link, host - case hostIcon = "host_icon" - case chunks, download, streamable, generated - } + enum CodingKeys: String, CodingKey { + case id, filename, mimeType, filesize, link, host + case hostIcon = "host_icon" + case chunks, download, streamable, generated + } } diff --git a/Ferrite/ViewModels/NavigationViewModel.swift b/Ferrite/ViewModels/NavigationViewModel.swift index 186dd55..1767d93 100644 --- a/Ferrite/ViewModels/NavigationViewModel.swift +++ b/Ferrite/ViewModels/NavigationViewModel.swift @@ -34,6 +34,10 @@ class NavigationViewModel: ObservableObject { @Published var selectedSearchResult: SearchResult? + // For giving information in magnet choice sheet + @Published var selectedTitle: String? + @Published var selectedBatchTitle: String? + @Published var hideNavigationBar = false @Published var currentChoiceSheet: ChoiceSheetType? @@ -123,6 +127,7 @@ class NavigationViewModel: ObservableObject { public func addToHistory(name: String?, source: String?, url: String?, subName: String? = nil) { let backgroundContext = PersistenceController.shared.backgroundContext + // The timeStamp and date are nil because the create function will make them automatically PersistenceController.shared.createHistory( entryJson: HistoryEntryJson( name: name ?? "", diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index b64ac54..2e217d3 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -901,7 +901,7 @@ class ScrapingViewModel: ObservableObject { } } - await toastModel?.updateToastDescription(responseArray.joined()) + await toastModel?.updateToastDescription(responseArray.joined(separator: " ")) PersistenceController.shared.save(backgroundContext) } diff --git a/Ferrite/Views/BatchChoiceView.swift b/Ferrite/Views/BatchChoiceView.swift index 72fa4d0..0a70ac0 100644 --- a/Ferrite/Views/BatchChoiceView.swift +++ b/Ferrite/Views/BatchChoiceView.swift @@ -28,6 +28,7 @@ struct BatchChoiceView: View { if !debridManager.realDebridDownloadUrl.isEmpty { // The download may complete before this sheet dismisses try? await Task.sleep(seconds: 1) + navModel.selectedBatchTitle = file.name navModel.addToHistory(name: searchResult.title, source: searchResult.source, url: debridManager.realDebridDownloadUrl, subName: file.name) navModel.runDebridAction(urlString: debridManager.realDebridDownloadUrl) } diff --git a/Ferrite/Views/LibraryView.swift b/Ferrite/Views/LibraryView.swift index 3d1b88e..39c475f 100644 --- a/Ferrite/Views/LibraryView.swift +++ b/Ferrite/Views/LibraryView.swift @@ -36,14 +36,13 @@ struct LibraryView: View { var body: some View { NavView { - VStack(spacing: 0) { + VStack { Picker("Segments", selection: $selectedSegment) { Text("Bookmarks").tag(LibraryPickerSegment.bookmarks) Text("History").tag(LibraryPickerSegment.history) } .pickerStyle(.segmented) - .padding(.horizontal) - .padding(.top) + .padding() switch selectedSegment { case .bookmarks: diff --git a/Ferrite/Views/LibraryViews/BookmarksView.swift b/Ferrite/Views/LibraryViews/BookmarksView.swift index aea6c9e..d668b62 100644 --- a/Ferrite/Views/LibraryViews/BookmarksView.swift +++ b/Ferrite/Views/LibraryViews/BookmarksView.swift @@ -49,7 +49,7 @@ struct BookmarksView: View { PersistenceController.shared.save() } } - .id(UUID()) + .inlinedList() .listStyle(.insetGrouped) } } diff --git a/Ferrite/Views/LibraryViews/HistoryButtonView.swift b/Ferrite/Views/LibraryViews/HistoryButtonView.swift index a6576e1..b359799 100644 --- a/Ferrite/Views/LibraryViews/HistoryButtonView.swift +++ b/Ferrite/Views/LibraryViews/HistoryButtonView.swift @@ -16,6 +16,9 @@ struct HistoryButtonView: View { var body: some View { Button { + navModel.selectedTitle = entry.name + navModel.selectedBatchTitle = entry.subName + if let url = entry.url { if url.starts(with: "https://") { Task { @@ -37,11 +40,13 @@ struct HistoryButtonView: View { VStack(alignment: .leading, spacing: 3) { Text(entry.name ?? "Unknown title") .font(entry.subName == nil ? .body : .subheadline) + .lineLimit(entry.subName == nil ? 2 : 1) if let subName = entry.subName { Text(subName) .foregroundColor(.gray) .font(.subheadline) + .lineLimit(2) } } @@ -67,10 +72,9 @@ struct HistoryButtonView: View { } .font(.caption) } - .lineLimit(1) .disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2)) } - .backport.tint(.white) + .backport.tint(.primary) .disableInteraction(navModel.currentChoiceSheet != nil) } } diff --git a/Ferrite/Views/MagnetChoiceView.swift b/Ferrite/Views/MagnetChoiceView.swift index df9e253..1ad0c77 100644 --- a/Ferrite/Views/MagnetChoiceView.swift +++ b/Ferrite/Views/MagnetChoiceView.swift @@ -23,6 +23,20 @@ struct MagnetChoiceView: View { var body: some View { NavView { Form { + Section(header: "Now Playing") { + VStack(alignment: .leading, spacing: 5) { + Text(navModel.selectedTitle ?? "No title") + .font(.callout) + .lineLimit(navModel.selectedBatchTitle == nil ? .max : 1) + + if let batchTitle = navModel.selectedBatchTitle { + Text(batchTitle) + .foregroundColor(.gray) + .font(.subheadline) + } + } + } + if !debridManager.realDebridDownloadUrl.isEmpty { Section(header: "Real Debrid options") { ListRowButtonView("Play on Outplayer", systemImage: "arrow.up.forward.app.fill") { diff --git a/Ferrite/Views/SearchResultViews/SearchResultButtonView.swift b/Ferrite/Views/SearchResultViews/SearchResultButtonView.swift index e1504de..9d9d984 100644 --- a/Ferrite/Views/SearchResultViews/SearchResultButtonView.swift +++ b/Ferrite/Views/SearchResultViews/SearchResultButtonView.swift @@ -7,7 +7,6 @@ import SwiftUI -// BUG: iOS 15 cannot refresh the context menu. Debating using swipe actions or adopting a workaround. struct SearchResultButtonView: View { let backgroundContext = PersistenceController.shared.backgroundContext @@ -25,6 +24,7 @@ struct SearchResultButtonView: View { Button { if debridManager.currentDebridTask == nil { navModel.selectedSearchResult = result + navModel.selectedTitle = result.title switch debridManager.matchSearchResult(result: result) { case .full: @@ -56,6 +56,7 @@ struct SearchResultButtonView: View { Text(result.title ?? "No title") .font(.callout) .fixedSize(horizontal: false, vertical: true) + .lineLimit(4) SearchResultRDView(result: result) }