mirror of
https://github.com/cranci1/Sora.git
synced 2026-03-11 17:45:37 +00:00
add swiftlint, add more german translation, add fetchExploreResults to JSController, clenaup skeleton cells, ...
This commit is contained in:
parent
ca15d27456
commit
aed5630c4c
9 changed files with 493 additions and 51 deletions
120
.swiftlint.yml
Normal file
120
.swiftlint.yml
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Directory and file filters
|
||||
included:
|
||||
- Plugins
|
||||
- Source
|
||||
- Tests
|
||||
- Package.swift
|
||||
excluded:
|
||||
- Tests/BuiltInRulesTests/Resources
|
||||
- Tests/FrameworkTests/Resources
|
||||
|
||||
# Enabled/disabled rules
|
||||
analyzer_rules:
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
opt_in_rules:
|
||||
- all
|
||||
disabled_rules:
|
||||
- anonymous_argument_in_multiline_closure
|
||||
- async_without_await
|
||||
- conditional_returns_on_newline
|
||||
- contrasted_opening_brace
|
||||
- convenience_type
|
||||
- discouraged_optional_collection
|
||||
- explicit_acl
|
||||
- explicit_enum_raw_value
|
||||
- explicit_top_level_acl
|
||||
- explicit_type_interface
|
||||
- file_types_order
|
||||
- force_unwrapping
|
||||
- function_default_parameter_at_end
|
||||
- indentation_width
|
||||
- missing_docs
|
||||
- multiline_arguments
|
||||
- multiline_arguments_brackets
|
||||
- multiline_function_chains
|
||||
- multiline_parameters_brackets
|
||||
- no_extension_access_modifier
|
||||
- no_grouping_extension
|
||||
- no_magic_numbers
|
||||
- one_declaration_per_file
|
||||
- prefer_key_path # Re-enable once we are on Swift 6.
|
||||
- prefer_nimble
|
||||
- prefixed_toplevel_constant
|
||||
- required_deinit
|
||||
- sorted_enum_cases
|
||||
- strict_fileprivate
|
||||
- switch_case_on_newline
|
||||
- todo
|
||||
- trailing_closure
|
||||
- type_contents_order
|
||||
- vertical_whitespace_between_cases
|
||||
|
||||
# Configurations
|
||||
attributes:
|
||||
always_on_line_above:
|
||||
- "@ConfigurationElement"
|
||||
- "@OptionGroup"
|
||||
- "@RuleConfigurationDescriptionBuilder"
|
||||
balanced_xctest_lifecycle: &unit_test_configuration
|
||||
test_parent_classes:
|
||||
- SwiftLintTestCase
|
||||
- XCTestCase
|
||||
closure_body_length:
|
||||
warning: 50
|
||||
error: 100
|
||||
empty_xctest_method: *unit_test_configuration
|
||||
file_name:
|
||||
excluded:
|
||||
- Exports.swift
|
||||
- GeneratedTests.swift
|
||||
- Macros.swift
|
||||
- Reporters+Register.swift
|
||||
- Rules+Register.swift
|
||||
- Rules+Template.swift
|
||||
- RuleConfigurationMacros.swift
|
||||
- SwiftSyntax+SwiftLint.swift
|
||||
- TestHelpers.swift
|
||||
final_test_case: *unit_test_configuration
|
||||
function_body_length: 60
|
||||
identifier_name:
|
||||
excluded:
|
||||
- id
|
||||
large_tuple: 3
|
||||
number_separator:
|
||||
minimum_length: 5
|
||||
redundant_type_annotation:
|
||||
consider_default_literal_types_redundant: true
|
||||
single_test_class: *unit_test_configuration
|
||||
trailing_comma:
|
||||
mandatory_comma: true
|
||||
type_body_length: 400
|
||||
unneeded_override:
|
||||
affect_initializers: true
|
||||
unused_import:
|
||||
always_keep_imports:
|
||||
- SwiftSyntaxBuilder # we can't detect uses of string interpolation of swift syntax nodes
|
||||
- SwiftLintFramework # now that this is a wrapper around other modules, don't treat as unused
|
||||
|
||||
# Custom rules
|
||||
custom_rules:
|
||||
rule_id:
|
||||
included: Source/SwiftLintBuiltInRules/Rules/.+/\w+\.swift
|
||||
name: Rule ID
|
||||
message: Rule IDs must be all lowercase, snake case and not end with `rule`
|
||||
regex: ^\s+identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*")
|
||||
severity: error
|
||||
fatal_error:
|
||||
name: Fatal Error
|
||||
excluded: "Tests/*"
|
||||
message: Prefer using `queuedFatalError` over `fatalError` to avoid leaking compiler host machine paths.
|
||||
regex: \bfatalError\b
|
||||
match_kinds:
|
||||
- identifier
|
||||
severity: error
|
||||
rule_test_function:
|
||||
included: Tests/SwiftLintFrameworkTests/RulesTests.swift
|
||||
name: Rule Test Function
|
||||
message: Rule Test Function mustn't end with `rule`
|
||||
regex: func\s*test\w+(r|R)ule\(\)
|
||||
severity: error
|
||||
|
|
@ -1109,6 +1109,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"No Content Available" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kein Inhalt verfügbar"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No data received" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -1689,6 +1699,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Try updating the Module" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Versuche das Modul zu updaten"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Two Finger Hold for Pause" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<dict>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.de.devsforge.sulfur.fork</string>
|
||||
<string>iCloud.de.devsforge.sulfur</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</array>
|
||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.de.devsforge.sulfur.fork</string>
|
||||
<string>iCloud.de.devsforge.sulfur</string>
|
||||
</array>
|
||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
||||
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
|
||||
|
|
|
|||
133
Sora/Utils/JSLoader/JSController-Explore.swift
Normal file
133
Sora/Utils/JSLoader/JSController-Explore.swift
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//
|
||||
// JSController-Search.swift
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Dominic on 24.04.25.
|
||||
//
|
||||
|
||||
import JavaScriptCore
|
||||
|
||||
// TODO: implement and test
|
||||
extension JSController {
|
||||
|
||||
func fetchExploreResults(module: ScrapingModule, completion: @escaping ([ExploreItem]) -> Void) {
|
||||
/*let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")
|
||||
|
||||
guard let url = URL(string: searchUrl) else {
|
||||
completion([])
|
||||
return
|
||||
}
|
||||
|
||||
URLSession.custom.dataTask(with: url) { [weak self] data, _, error in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let error = error {
|
||||
Logger.shared.log("Network error: \(error)",type: "Error")
|
||||
DispatchQueue.main.async { completion([]) }
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data, let html = String(data: data, encoding: .utf8) else {
|
||||
Logger.shared.log("Failed to decode HTML",type: "Error")
|
||||
DispatchQueue.main.async { completion([]) }
|
||||
return
|
||||
}
|
||||
|
||||
Logger.shared.log(html,type: "HTMLStrings")
|
||||
if let parseFunction = self.context.objectForKeyedSubscript("searchResults"),
|
||||
let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] {
|
||||
let resultItems = results.map { item in
|
||||
SearchItem(
|
||||
title: item["title"] ?? "",
|
||||
imageUrl: item["image"] ?? "",
|
||||
href: item["href"] ?? ""
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(resultItems)
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Failed to parse results",type: "Error")
|
||||
DispatchQueue.main.async { completion([]) }
|
||||
}
|
||||
}.resume()
|
||||
*/
|
||||
}
|
||||
|
||||
func fetchJsExploreResults(module: ScrapingModule, completion: @escaping ([ExploreItem]) -> Void) {
|
||||
/*
|
||||
if let exception = context.exception {
|
||||
Logger.shared.log("JavaScript exception: \(exception)",type: "Error")
|
||||
completion([])
|
||||
return
|
||||
}
|
||||
|
||||
guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else {
|
||||
Logger.shared.log("No JavaScript function searchResults found",type: "Error")
|
||||
completion([])
|
||||
return
|
||||
}
|
||||
|
||||
let promiseValue = searchResultsFunction.call(withArguments: [keyword])
|
||||
guard let promise = promiseValue else {
|
||||
Logger.shared.log("searchResults did not return a Promise",type: "Error")
|
||||
completion([])
|
||||
return
|
||||
}
|
||||
|
||||
let thenBlock: @convention(block) (JSValue) -> Void = { result in
|
||||
|
||||
Logger.shared.log(result.toString(),type: "HTMLStrings")
|
||||
if let jsonString = result.toString(),
|
||||
let data = jsonString.data(using: .utf8) {
|
||||
do {
|
||||
if let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
|
||||
let resultItems = array.compactMap { item -> SearchItem? in
|
||||
guard let title = item["title"] as? String,
|
||||
let imageUrl = item["image"] as? String,
|
||||
let href = item["href"] as? String else {
|
||||
Logger.shared.log("Missing or invalid data in search result item: \(item)", type: "Error")
|
||||
return nil
|
||||
}
|
||||
return SearchItem(title: title, imageUrl: imageUrl, href: href)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(resultItems)
|
||||
}
|
||||
|
||||
} else {
|
||||
Logger.shared.log("Failed to parse JSON",type: "Error")
|
||||
DispatchQueue.main.async {
|
||||
completion([])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("JSON parsing error: \(error)",type: "Error")
|
||||
DispatchQueue.main.async {
|
||||
completion([])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.shared.log("Result is not a string",type: "Error")
|
||||
DispatchQueue.main.async {
|
||||
completion([])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let catchBlock: @convention(block) (JSValue) -> Void = { error in
|
||||
Logger.shared.log("Promise rejected: \(String(describing: error.toString()))",type: "Error")
|
||||
DispatchQueue.main.async {
|
||||
completion([])
|
||||
}
|
||||
}
|
||||
|
||||
let thenFunction = JSValue(object: thenBlock, in: context)
|
||||
let catchFunction = JSValue(object: catchBlock, in: context)
|
||||
|
||||
promise.invokeMethod("then", withArguments: [thenFunction as Any])
|
||||
promise.invokeMethod("catch", withArguments: [catchFunction as Any])
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
@ -7,38 +7,30 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct HomeSkeletonCell: View {
|
||||
enum SkeletonCellType {
|
||||
// unused !? ( legacy code from HomeSkeletonCell )
|
||||
case home
|
||||
|
||||
case search
|
||||
case explore
|
||||
}
|
||||
|
||||
struct SkeletonCell: View {
|
||||
let type: SkeletonCellType
|
||||
let cellWidth: CGFloat
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(alignment: type == .home ? .center : .leading, spacing: type == .home ? 0 : 8) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: cellWidth, height: cellWidth * 1.5)
|
||||
.cornerRadius(10)
|
||||
.shimmering()
|
||||
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: cellWidth, height: 20)
|
||||
.padding(.top, 4)
|
||||
.shimmering()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchSkeletonCell: View {
|
||||
let cellWidth: CGFloat
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: cellWidth, height: cellWidth * 1.5)
|
||||
.shimmering()
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(Color.gray.opacity(0.3))
|
||||
.frame(width: cellWidth, height: 20)
|
||||
.padding(.top, type == .home ? 4 : 0)
|
||||
.shimmering()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,52 @@
|
|||
//
|
||||
// LibraryView.swift
|
||||
// Sora
|
||||
// Sulfur
|
||||
//
|
||||
// Created by Francesco on 05/01/25.
|
||||
// Created by Dominic on 24.04.25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ExploreView: View {
|
||||
@EnvironmentObject private var moduleManager: ModuleManager
|
||||
@EnvironmentObject private var profileStore: ProfileStore
|
||||
struct ExploreItem: Identifiable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let imageUrl: String
|
||||
let href: String
|
||||
}
|
||||
|
||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||
struct ExploreView: View {
|
||||
@AppStorage("hideEmptySections") private var hideEmptySections: Bool?
|
||||
@AppStorage("selectedModuleId") private var selectedModuleId: String?
|
||||
@AppStorage("mediaColumnsPortrait") private var mediaColumnsPortrait: Int = 2
|
||||
@AppStorage("mediaColumnsLandscape") private var mediaColumnsLandscape: Int = 4
|
||||
|
||||
|
||||
@StateObject private var jsController = JSController()
|
||||
@EnvironmentObject private var moduleManager: ModuleManager
|
||||
@EnvironmentObject private var profileStore: ProfileStore
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass
|
||||
|
||||
@State private var exploreItems: [ExploreItem] = []
|
||||
@State private var selectedExploreItem: ExploreItem?
|
||||
@State private var hasNoResults = false
|
||||
@State private var isLandscape: Bool = UIDevice.current.orientation.isLandscape
|
||||
@State private var isModuleSelectorPresented = false
|
||||
@State private var showProfileSettings = false
|
||||
|
||||
@State private var isLoading = false
|
||||
|
||||
private var selectedModule: ScrapingModule? {
|
||||
guard let id = selectedModuleId else { return nil }
|
||||
return moduleManager.modules.first { $0.id.uuidString == id }
|
||||
}
|
||||
|
||||
private let columns = [
|
||||
GridItem(.adaptive(minimum: 150), spacing: 12)
|
||||
private var loadingMessages: [String] = [
|
||||
"Exploring the depths...",
|
||||
"Looking for results...",
|
||||
"Fetching data...",
|
||||
"Please wait...",
|
||||
"Almost there..."
|
||||
]
|
||||
|
||||
|
||||
private var columnsCount: Int {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
let isLandscape = UIScreen.main.bounds.width > UIScreen.main.bounds.height
|
||||
|
|
@ -38,7 +55,7 @@ struct ExploreView: View {
|
|||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var cellWidth: CGFloat {
|
||||
let keyWindow = UIApplication.shared.connectedScenes
|
||||
.compactMap { ($0 as? UIWindowScene)?.windows.first(where: { $0.isKeyWindow }) }
|
||||
|
|
@ -49,15 +66,83 @@ struct ExploreView: View {
|
|||
let availableWidth = safeWidth - totalSpacing
|
||||
return availableWidth / CGFloat(columnsCount)
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
//TODO: add explore content views
|
||||
let columnsCount = determineColumns()
|
||||
VStack(spacing: 0) {
|
||||
|
||||
if !(hideEmptySections ?? false) && selectedModule == nil {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "questionmark.app")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.secondary)
|
||||
Text("No Module Selected")
|
||||
.font(.headline)
|
||||
Text("Please select a module from settings")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(.systemBackground))
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||
SkeletonCell(type: .explore, cellWidth: cellWidth)
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
.padding()
|
||||
} else if hasNoResults {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "star")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.secondary)
|
||||
Text("No Content Available")
|
||||
.font(.headline)
|
||||
Text("Try updating the Module")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top)
|
||||
} else {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(exploreItems) { item in
|
||||
NavigationLink(destination: MediaInfoView(title: item.title, imageUrl: item.imageUrl, href: item.href, module: selectedModule!)) {
|
||||
VStack {
|
||||
KFImage(URL(string: item.imageUrl))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(height: cellWidth * 3 / 2)
|
||||
.frame(maxWidth: cellWidth)
|
||||
.cornerRadius(10)
|
||||
.clipped()
|
||||
Text(item.title)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.primary)
|
||||
.padding([.leading, .bottom], 8)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
updateOrientation()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
updateOrientation()
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
|
||||
|
||||
NavigationLink(
|
||||
destination: SettingsViewProfile(),
|
||||
isActive: $showProfileSettings,
|
||||
|
|
@ -66,6 +151,7 @@ struct ExploreView: View {
|
|||
.hidden()
|
||||
}
|
||||
.navigationTitle("Explore")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Menu {
|
||||
|
|
@ -100,6 +186,7 @@ struct ExploreView: View {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Menu {
|
||||
ForEach(getModuleLanguageGroups(), id: \.self) { language in
|
||||
|
|
@ -143,22 +230,65 @@ struct ExploreView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.onAppear {
|
||||
updateOrientation()
|
||||
//TODO: fetch explore content
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
updateOrientation()
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.onChange(of: selectedModuleId) { _ in
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func fetchData() {
|
||||
Logger.shared.log("Fetching Explore Data", type: "General")
|
||||
guard let module = selectedModule else {
|
||||
exploreItems = []
|
||||
hasNoResults = false
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
hasNoResults = false
|
||||
exploreItems = []
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
Task {
|
||||
do {
|
||||
let jsContent = try moduleManager.getModuleContent(module)
|
||||
jsController.loadScript(jsContent)
|
||||
if module.metadata.asyncJS == true {
|
||||
jsController.fetchJsExploreResults(module: module) { items in
|
||||
exploreItems = items
|
||||
hasNoResults = items.isEmpty
|
||||
isLoading = false
|
||||
}
|
||||
} else {
|
||||
jsController.fetchExploreResults(module: module) { items in
|
||||
exploreItems = items
|
||||
hasNoResults = items.isEmpty
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.shared.log("Error loading module: \(error)", type: "Error")
|
||||
isLoading = false
|
||||
hasNoResults = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateOrientation() {
|
||||
DispatchQueue.main.async {
|
||||
isLandscape = UIDevice.current.orientation.isLandscape
|
||||
}
|
||||
}
|
||||
|
||||
private func determineColumns() -> Int {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
return isLandscape ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||
} else {
|
||||
return verticalSizeClass == .compact ? mediaColumnsLandscape : mediaColumnsPortrait
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanLanguageName(_ language: String?) -> String {
|
||||
guard let language = language else { return "Unknown" }
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ struct SearchView: View {
|
|||
if isSearching {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: columnsCount), spacing: 16) {
|
||||
ForEach(0..<columnsCount*4, id: \.self) { _ in
|
||||
SearchSkeletonCell(cellWidth: cellWidth)
|
||||
SkeletonCell(type: .search, cellWidth: cellWidth)
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
120D3C722DBA40AB0093D596 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 120D3C712DBA40A40093D596 /* .swiftlint.yml */; };
|
||||
120D3C732DBA40AB0093D596 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 120D3C712DBA40A40093D596 /* .swiftlint.yml */; };
|
||||
126C428D2DB99627006BC27D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 126C428C2DB99627006BC27D /* Localizable.xcstrings */; };
|
||||
126C428E2DB99627006BC27D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 126C428C2DB99627006BC27D /* Localizable.xcstrings */; };
|
||||
132E351D2D959DDB0007800E /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = 132E351C2D959DDB0007800E /* Drops */; };
|
||||
|
|
@ -17,6 +19,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
120764652DB6F6E0003621E9 /* SulfurTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SulfurTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
120D3C712DBA40A40093D596 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
|
||||
126C428A2DB9921C006BC27D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
126C428C2DB99627006BC27D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
133D7C6A2D2BE2500075467E /* Sulfur.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sulfur.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -77,6 +80,7 @@
|
|||
133D7C612D2BE2500075467E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
120D3C712DBA40A40093D596 /* .swiftlint.yml */,
|
||||
126C428C2DB99627006BC27D /* Localizable.xcstrings */,
|
||||
126C42F62DB9AA97006BC27D /* Sora */,
|
||||
120764662DB6F6E0003621E9 /* SulfurTV */,
|
||||
|
|
@ -108,6 +112,7 @@
|
|||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
120D3C702DBA3DF90093D596 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
120764662DB6F6E0003621E9 /* SulfurTV */,
|
||||
|
|
@ -130,6 +135,7 @@
|
|||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
120D3C6E2DBA3DF30093D596 /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
126C42F62DB9AA97006BC27D /* Sora */,
|
||||
|
|
@ -178,6 +184,7 @@
|
|||
132E351E2D959E1D0007800E /* XCRemoteSwiftPackageReference "FFmpeg-iOS-Lame" */,
|
||||
132E35212D959E410007800E /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
13B77E172DA44F8300126FDF /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
|
||||
120D3C6C2DBA3D790093D596 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */,
|
||||
);
|
||||
productRefGroup = 133D7C6B2D2BE2500075467E /* Products */;
|
||||
projectDirPath = "";
|
||||
|
|
@ -194,6 +201,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
120D3C722DBA40AB0093D596 /* .swiftlint.yml in Resources */,
|
||||
126C428E2DB99627006BC27D /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -202,6 +210,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
120D3C732DBA40AB0093D596 /* .swiftlint.yml in Resources */,
|
||||
126C428D2DB99627006BC27D /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -225,6 +234,17 @@
|
|||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
120D3C6E2DBA3DF30093D596 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 120D3C6D2DBA3DF30093D596 /* SwiftLintBuildToolPlugin */;
|
||||
};
|
||||
120D3C702DBA3DF90093D596 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
productRef = 120D3C6F2DBA3DF90093D596 /* SwiftLintBuildToolPlugin */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
1207646D2DB6F6E1003621E9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
|
@ -555,6 +575,14 @@
|
|||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
120D3C6C2DBA3D790093D596 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SimplyDanny/SwiftLintPlugins";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/omaralbeik/Drops.git";
|
||||
|
|
@ -590,6 +618,16 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
120D3C6D2DBA3DF30093D596 /* SwiftLintBuildToolPlugin */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 120D3C6C2DBA3D790093D596 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */;
|
||||
productName = "plugin:SwiftLintBuildToolPlugin";
|
||||
};
|
||||
120D3C6F2DBA3DF90093D596 /* SwiftLintBuildToolPlugin */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 120D3C6C2DBA3D790093D596 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */;
|
||||
productName = "plugin:SwiftLintBuildToolPlugin";
|
||||
};
|
||||
132E351C2D959DDB0007800E /* Drops */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 132E351B2D959DDB0007800E /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "e772caa8d6a8793d24bf04e3d77695cd5ac695f3605d2b657e40115caedf8863",
|
||||
"originHash" : "c4909124df3eb22bfcc539fb1f3936eb79c309605d2f34c5b02efa1fe3f48447",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "drops",
|
||||
|
|
@ -45,6 +45,15 @@
|
|||
"branch" : "master",
|
||||
"revision" : "18e4787f4dc1c26d2d581c4bc9aeae34686eeeae"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftlintplugins",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SimplyDanny/SwiftLintPlugins",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "8545ddf4de043e6f2051c5cf204f39ef778ebf6b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
|
|
|||
Loading…
Reference in a new issue