diff --git a/Sora-JS.xcodeproj/project.pbxproj b/Sora-JS.xcodeproj/project.pbxproj new file mode 100644 index 0000000..766ee9e --- /dev/null +++ b/Sora-JS.xcodeproj/project.pbxproj @@ -0,0 +1,362 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 1329D5C72D298198008AEDA2 /* Sora_JSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1329D5C62D298198008AEDA2 /* Sora_JSApp.swift */; }; + 1329D5C92D298198008AEDA2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1329D5C82D298198008AEDA2 /* ContentView.swift */; }; + 1329D5CB2D298199008AEDA2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CA2D298199008AEDA2 /* Assets.xcassets */; }; + 1329D5CE2D298199008AEDA2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */; }; + 13AEE6192D2A75110096D953 /* Modules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE6182D2A75110096D953 /* Modules.swift */; }; + 13AEE61B2D2A78050096D953 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61A2D2A78050096D953 /* Settings.swift */; }; + 13AEE61D2D2A78160096D953 /* JSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE61C2D2A78160096D953 /* JSController.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1329D5C32D298198008AEDA2 /* Sora-JS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Sora-JS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1329D5C62D298198008AEDA2 /* Sora_JSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sora_JSApp.swift; sourceTree = ""; }; + 1329D5C82D298198008AEDA2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1329D5CA2D298199008AEDA2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 1329D5DA2D29821B008AEDA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 13AEE6182D2A75110096D953 /* Modules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modules.swift; sourceTree = ""; }; + 13AEE61A2D2A78050096D953 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + 13AEE61C2D2A78160096D953 /* JSController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSController.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1329D5C02D298198008AEDA2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1329D5BA2D298198008AEDA2 = { + isa = PBXGroup; + children = ( + 1329D5C52D298198008AEDA2 /* Sora-JS */, + 1329D5C42D298198008AEDA2 /* Products */, + ); + sourceTree = ""; + }; + 1329D5C42D298198008AEDA2 /* Products */ = { + isa = PBXGroup; + children = ( + 1329D5C32D298198008AEDA2 /* Sora-JS.app */, + ); + name = Products; + sourceTree = ""; + }; + 1329D5C52D298198008AEDA2 /* Sora-JS */ = { + isa = PBXGroup; + children = ( + 1329D5DA2D29821B008AEDA2 /* Info.plist */, + 1329D5C62D298198008AEDA2 /* Sora_JSApp.swift */, + 1329D5C82D298198008AEDA2 /* ContentView.swift */, + 13AEE6182D2A75110096D953 /* Modules.swift */, + 1329D5CA2D298199008AEDA2 /* Assets.xcassets */, + 1329D5CC2D298199008AEDA2 /* Preview Content */, + 13AEE61A2D2A78050096D953 /* Settings.swift */, + 13AEE61C2D2A78160096D953 /* JSController.swift */, + ); + path = "Sora-JS"; + sourceTree = ""; + }; + 1329D5CC2D298199008AEDA2 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 1329D5CD2D298199008AEDA2 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1329D5C22D298198008AEDA2 /* Sora-JS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1329D5D12D298199008AEDA2 /* Build configuration list for PBXNativeTarget "Sora-JS" */; + buildPhases = ( + 1329D5BF2D298198008AEDA2 /* Sources */, + 1329D5C02D298198008AEDA2 /* Frameworks */, + 1329D5C12D298198008AEDA2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Sora-JS"; + productName = "Sora-JS"; + productReference = 1329D5C32D298198008AEDA2 /* Sora-JS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1329D5BB2D298198008AEDA2 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1320; + LastUpgradeCheck = 1320; + TargetAttributes = { + 1329D5C22D298198008AEDA2 = { + CreatedOnToolsVersion = 13.2.1; + }; + }; + }; + buildConfigurationList = 1329D5BE2D298198008AEDA2 /* Build configuration list for PBXProject "Sora-JS" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 1329D5BA2D298198008AEDA2; + productRefGroup = 1329D5C42D298198008AEDA2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1329D5C22D298198008AEDA2 /* Sora-JS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1329D5C12D298198008AEDA2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1329D5CE2D298199008AEDA2 /* Preview Assets.xcassets in Resources */, + 1329D5CB2D298199008AEDA2 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1329D5BF2D298198008AEDA2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1329D5C92D298198008AEDA2 /* ContentView.swift in Sources */, + 13AEE61D2D2A78160096D953 /* JSController.swift in Sources */, + 13AEE6192D2A75110096D953 /* Modules.swift in Sources */, + 13AEE61B2D2A78050096D953 /* Settings.swift in Sources */, + 1329D5C72D298198008AEDA2 /* Sora_JSApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1329D5CF2D298199008AEDA2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 1329D5D02D298199008AEDA2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 1329D5D22D298199008AEDA2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sora-JS/Preview Content\""; + DEVELOPMENT_TEAM = 399LMK6Q2Y; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Sora-JS/Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "me.cranci.Sora-JS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1329D5D32D298199008AEDA2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Sora-JS/Preview Content\""; + DEVELOPMENT_TEAM = 399LMK6Q2Y; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Sora-JS/Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "me.cranci.Sora-JS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1329D5BE2D298198008AEDA2 /* Build configuration list for PBXProject "Sora-JS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1329D5CF2D298199008AEDA2 /* Debug */, + 1329D5D02D298199008AEDA2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1329D5D12D298199008AEDA2 /* Build configuration list for PBXNativeTarget "Sora-JS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1329D5D22D298199008AEDA2 /* Debug */, + 1329D5D32D298199008AEDA2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 1329D5BB2D298198008AEDA2 /* Project object */; +} diff --git a/Sora.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Sora-JS.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Sora.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Sora-JS.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Sora.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Sora-JS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Sora.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to Sora-JS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json b/Sora-JS/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 54% rename from Sora/Assets.xcassets/AccentColor.colorset/Contents.json rename to Sora-JS/Assets.xcassets/AccentColor.colorset/Contents.json index bb57667..eb87897 100644 --- a/Sora/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Sora-JS/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,10 +1,6 @@ { "colors" : [ { - "color" : { - "platform" : "universal", - "reference" : "systemMintColor" - }, "idiom" : "universal" } ], diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sora-JS/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 74% rename from Sora/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Sora-JS/Assets.xcassets/AppIcon.appiconset/Contents.json index 1fa06d6..9221b9b 100644 --- a/Sora/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sora-JS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,109 +1,91 @@ { "images" : [ { - "filename" : "40-2.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { - "filename" : "60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { - "filename" : "58.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { - "filename" : "87.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { - "filename" : "80.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { - "filename" : "120-1.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { - "filename" : "120.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { - "filename" : "180.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { - "filename" : "20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { - "filename" : "40-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { - "filename" : "29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { - "filename" : "58-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { - "filename" : "40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { - "filename" : "80-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { - "filename" : "76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { - "filename" : "152.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { - "filename" : "167.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { - "filename" : "1024.jpg", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/Sora/Assets.xcassets/Contents.json b/Sora-JS/Assets.xcassets/Contents.json similarity index 100% rename from Sora/Assets.xcassets/Contents.json rename to Sora-JS/Assets.xcassets/Contents.json diff --git a/Sora-JS/ContentView.swift b/Sora-JS/ContentView.swift new file mode 100644 index 0000000..ea663bd --- /dev/null +++ b/Sora-JS/ContentView.swift @@ -0,0 +1,178 @@ +// +// ContentView.swift +// Sora-JS +// +// Created by Francesco on 04/01/25. +// + +import SwiftUI + +struct AnimeItem: Identifiable { + let id = UUID() + let title: String + let imageUrl: String +} + +struct ContentView: View { + @StateObject private var jsController = JSController() + @EnvironmentObject var moduleManager: ModuleManager + @State private var searchText = "" + @State private var animeItems: [AnimeItem] = [] + @State private var isSearching = false + @AppStorage("selectedModuleId") private var selectedModuleId: String? + + private var selectedModule: ScrapingModule? { + guard let id = selectedModuleId else { return nil } + return moduleManager.modules.first { $0.id.uuidString == id } + } + + var body: some View { + NavigationView { + VStack(spacing: 0) { + if let selectedModule = selectedModule { + HStack { + AsyncImage(url: URL(string: selectedModule.metadata.iconUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + } placeholder: { + Color.gray + } + .frame(width: 30, height: 30) + .cornerRadius(6) + + VStack(alignment: .leading) { + Text(selectedModule.metadata.mediaType) + .font(.headline) + Text(selectedModule.metadata.language) + .font(.caption) + .foregroundColor(.gray) + } + + Spacer() + + Menu { + ForEach(moduleManager.modules) { module in + Button { + selectedModuleId = module.id.uuidString + } label: { + HStack { + AsyncImage(url: URL(string: module.metadata.iconUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + } placeholder: { + Color.gray + } + .frame(width: 20, height: 20) + .cornerRadius(4) + + Text(module.metadata.mediaType) + Spacer() + if module.id.uuidString == selectedModuleId { + Image(systemName: "checkmark") + } + } + } + } + } label: { + Image(systemName: "chevron.up.chevron.down") + .foregroundColor(.gray) + } + } + .padding() + .background(Color(.systemBackground)) + .shadow(color: Color.black.opacity(0.1), radius: 2, y: 1) + } else { + VStack(spacing: 8) { + Text("No Module Selected") + .font(.headline) + Text("Please select a module from settings") + .font(.caption) + .foregroundColor(.gray) + } + .padding() + .frame(maxWidth: .infinity) + .background(Color(.systemBackground)) + .shadow(color: Color.black.opacity(0.1), radius: 2, y: 1) + } + + TextField("Search...", text: $searchText) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding() + .onChange(of: searchText) { newValue in + guard !newValue.isEmpty, let module = selectedModule else { + animeItems = [] + return + } + + isSearching = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + Task { + do { + let jsContent = try moduleManager.getModuleContent(module) + jsController.loadScript(jsContent) + jsController.scrapeAnime(keyword: newValue, module: module) { items in + animeItems = items + isSearching = false + } + } catch { + print("Error loading module: \(error)") + isSearching = false + } + } + } + } + + if isSearching { + ProgressView() + .padding() + } + + ScrollView { + LazyVGrid(columns: [ + GridItem(.flexible(), spacing: 16), + GridItem(.flexible(), spacing: 16) + ], spacing: 16) { + ForEach(animeItems) { item in + VStack(alignment: .leading) { + if let url = URL(string: item.imageUrl) { + AsyncImage(url: url) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + Rectangle() + .fill(Color.gray.opacity(0.2)) + .overlay( + ProgressView() + ) + } + .frame(height: 200) + .clipped() + .cornerRadius(8) + } + + Text(item.title) + .font(.headline) + .lineLimit(2) + .padding(.vertical, 4) + } + .frame(maxWidth: .infinity) + .background(Color(.secondarySystemBackground)) + .cornerRadius(12) + } + } + .padding() + } + } + .navigationTitle("Search") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + NavigationLink(destination: SettingsView()) { + Image(systemName: "gear") + } + } + } + } +} diff --git a/Sora/Sora.entitlements b/Sora-JS/Info.plist similarity index 61% rename from Sora/Sora.entitlements rename to Sora-JS/Info.plist index ee95ab7..cf5e9dd 100644 --- a/Sora/Sora.entitlements +++ b/Sora-JS/Info.plist @@ -2,9 +2,10 @@ - com.apple.security.app-sandbox - - com.apple.security.network.client - + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + diff --git a/Sora-JS/JSController.swift b/Sora-JS/JSController.swift new file mode 100644 index 0000000..7c21259 --- /dev/null +++ b/Sora-JS/JSController.swift @@ -0,0 +1,74 @@ +// +// JSController.swift +// Sora-JS +// +// Created by Francesco on 05/01/25. +// + +import JavaScriptCore + +class JSController: ObservableObject { + private var context: JSContext + + init() { + self.context = JSContext() + setupContext() + } + + private func setupContext() { + let logFunction: @convention(block) (String) -> Void = { message in + print("JavaScript log: \(message)") + } + context.setObject(logFunction, forKeyedSubscript: "log" as NSString) + } + + func loadScript(_ script: String) { + context = JSContext() + setupContext() + context.evaluateScript(script) + } + + func scrapeAnime(keyword: String, module: ScrapingModule, completion: @escaping ([AnimeItem]) -> Void) { + let searchUrl = module.metadata.searchBaseUrl.replacingOccurrences(of: "%s", with: keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") + + guard let url = URL(string: searchUrl) else { + completion([]) + return + } + + var request = URLRequest(url: url) + request.setValue("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", forHTTPHeaderField: "User-Agent") + + URLSession.shared.dataTask(with: request) { [weak self] data, response, error in + guard let self = self else { return } + + if let error = error { + print("Network error: \(error)") + DispatchQueue.main.async { completion([]) } + return + } + + guard let data = data, let html = String(data: data, encoding: .utf8) else { + print("Failed to decode HTML") + DispatchQueue.main.async { completion([]) } + return + } + + if let parseFunction = self.context.objectForKeyedSubscript("parseHTML"), + let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { + let animeItems = results.map { item in + AnimeItem( + title: item["title"] ?? "", + imageUrl: item["image"] ?? "" + ) + } + DispatchQueue.main.async { + completion(animeItems) + } + } else { + print("Failed to parse results") + DispatchQueue.main.async { completion([]) } + } + }.resume() + } +} diff --git a/Sora-JS/Modules.swift b/Sora-JS/Modules.swift new file mode 100644 index 0000000..67938e2 --- /dev/null +++ b/Sora-JS/Modules.swift @@ -0,0 +1,118 @@ +// +// Modules.swift +// Sora-JS +// +// Created by Francesco on 05/01/25. +// + +import Foundation + +struct ModuleMetadata: Codable, Hashable { + let author: String + let iconUrl: String + let language: String + let mediaType: String + let searchBaseUrl: String + let scriptUrl: String + let version: String + let description: String +} + +struct ScrapingModule: Codable, Identifiable, Hashable { + let id: UUID + let metadata: ModuleMetadata + let localPath: String + var isActive: Bool + + init(id: UUID = UUID(), metadata: ModuleMetadata, localPath: String, isActive: Bool = false) { + self.id = id + self.metadata = metadata + self.localPath = localPath + self.isActive = isActive + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + static func == (lhs: ScrapingModule, rhs: ScrapingModule) -> Bool { + lhs.id == rhs.id + } +} + +class ModuleManager: ObservableObject { + @Published var modules: [ScrapingModule] = [] + private let fileManager = FileManager.default + private let modulesFileName = "modules.json" + + init() { + loadModules() + } + + private func getDocumentsDirectory() -> URL { + fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] + } + + private func getModulesFilePath() -> URL { + getDocumentsDirectory().appendingPathComponent(modulesFileName) + } + + func loadModules() { + let url = getModulesFilePath() + guard let data = try? Data(contentsOf: url) else { return } + modules = (try? JSONDecoder().decode([ScrapingModule].self, from: data)) ?? [] + } + + private func saveModules() { + let url = getModulesFilePath() + guard let data = try? JSONEncoder().encode(modules) else { return } + try? data.write(to: url) + } + + func addModule(metadataUrl: String) async throws -> ScrapingModule { + guard let url = URL(string: metadataUrl) else { + throw NSError(domain: "Invalid metadata URL", code: -1) + } + + let (metadataData, _) = try await URLSession.shared.data(from: url) + let metadata = try JSONDecoder().decode(ModuleMetadata.self, from: metadataData) + + guard let scriptUrl = URL(string: metadata.scriptUrl) else { + throw NSError(domain: "Invalid script URL", code: -1) + } + + let (scriptData, _) = try await URLSession.shared.data(from: scriptUrl) + guard let jsContent = String(data: scriptData, encoding: .utf8) else { + throw NSError(domain: "Invalid script encoding", code: -1) + } + + let fileName = "\(UUID().uuidString).js" + let localUrl = getDocumentsDirectory().appendingPathComponent(fileName) + try jsContent.write(to: localUrl, atomically: true, encoding: .utf8) + + let module = ScrapingModule( + metadata: metadata, + localPath: fileName + ) + + DispatchQueue.main.async { + self.modules.append(module) + self.saveModules() + } + + return module + } + + func deleteModule(_ module: ScrapingModule) { + let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + try? fileManager.removeItem(at: localUrl) + + modules.removeAll { $0.id == module.id } + saveModules() + } + + func getModuleContent(_ module: ScrapingModule) throws -> String { + let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + return try String(contentsOf: localUrl, encoding: .utf8) + } +} diff --git a/Sora/Preview Content/Preview Assets.xcassets/Contents.json b/Sora-JS/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from Sora/Preview Content/Preview Assets.xcassets/Contents.json rename to Sora-JS/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/Sora-JS/Settings.swift b/Sora-JS/Settings.swift new file mode 100644 index 0000000..4e4022c --- /dev/null +++ b/Sora-JS/Settings.swift @@ -0,0 +1,133 @@ +// +// Settings.swift +// Sora-JS +// +// Created by Francesco on 05/01/25. +// + +import SwiftUI + +struct SettingsView: View { + @EnvironmentObject var moduleManager: ModuleManager + @AppStorage("selectedModuleId") private var selectedModuleId: String? + @State private var showingAddModule = false + @State private var newModuleUrl = "" + @State private var isLoading = false + @State private var errorMessage: String? + + var body: some View { + List { + ForEach(moduleManager.modules) { module in + ModuleRow(module: module, isSelected: module.id.uuidString == selectedModuleId) + .contentShape(Rectangle()) + .onTapGesture { + selectedModuleId = module.id.uuidString + } + .swipeActions { + Button(role: .destructive) { + if selectedModuleId == module.id.uuidString { + selectedModuleId = nil + } + moduleManager.deleteModule(module) + } label: { + Label("Delete", systemImage: "trash") + } + } + } + } + .navigationTitle("Scraping Modules") + .toolbar { + Button { + showingAddModule = true + } label: { + Label("Add Module", systemImage: "plus") + } + } + .sheet(isPresented: $showingAddModule) { + NavigationView { + Form { + Section(header: Text("New Module")) { + TextField("Module JSON URL", text: $newModuleUrl) + .autocapitalization(.none) + .keyboardType(.URL) + } + + if let error = errorMessage { + Section { + Text(error) + .foregroundColor(.red) + } + } + } + .navigationTitle("Add Module") + .navigationBarItems( + leading: Button("Cancel") { + showingAddModule = false + }, + trailing: Button("Add") { + addModule() + } + .disabled(newModuleUrl.isEmpty || isLoading) + ) + } + } + } + + private func addModule() { + isLoading = true + errorMessage = nil + + Task { + do { + _ = try await moduleManager.addModule(metadataUrl: newModuleUrl) + DispatchQueue.main.async { + isLoading = false + showingAddModule = false + newModuleUrl = "" + } + } catch { + DispatchQueue.main.async { + isLoading = false + errorMessage = error.localizedDescription + } + } + } + } +} + +struct ModuleRow: View { + let module: ScrapingModule + let isSelected: Bool + + var body: some View { + HStack { + AsyncImage(url: URL(string: module.metadata.iconUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + } placeholder: { + Color.gray + } + .frame(width: 40, height: 40) + .cornerRadius(8) + + VStack(alignment: .leading) { + Text(module.metadata.mediaType) + .font(.headline) + Text("by \(module.metadata.author)") + .font(.caption) + .foregroundColor(.gray) + Text("\(module.metadata.language) • v\(module.metadata.version)") + .font(.caption2) + .foregroundColor(.gray) + } + + Spacer() + + if isSelected { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.blue) + } + } + } +} diff --git a/Sora-JS/Sora_JSApp.swift b/Sora-JS/Sora_JSApp.swift new file mode 100644 index 0000000..9d33576 --- /dev/null +++ b/Sora-JS/Sora_JSApp.swift @@ -0,0 +1,20 @@ +// +// Sora_JSApp.swift +// Sora-JS +// +// Created by Francesco on 04/01/25. +// + +import SwiftUI + +@main +struct Sora_JSApp: App { + @StateObject private var moduleManager = ModuleManager() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(moduleManager) + } + } +} diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj deleted file mode 100644 index f1a25fa..0000000 --- a/Sora.xcodeproj/project.pbxproj +++ /dev/null @@ -1,671 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - 1308CFBC2D19844A004CD38C /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFBB2D19844A004CD38C /* Double+Extension.swift */; }; - 1308CFBE2D19844D004CD38C /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */; }; - 1308CFC12D198466004CD38C /* CustomPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1308CFC02D198466004CD38C /* CustomPlayer.swift */; }; - 132417842D13198000B4F2D2 /* SoraApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417832D13198000B4F2D2 /* SoraApp.swift */; }; - 132417862D13198000B4F2D2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417852D13198000B4F2D2 /* ContentView.swift */; }; - 132417882D13198200B4F2D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 132417872D13198200B4F2D2 /* Assets.xcassets */; }; - 1324178B2D13198200B4F2D2 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */; }; - 1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417932D1319E800B4F2D2 /* MiruDataStruct.swift */; }; - 1324179F2D1319E800B4F2D2 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417952D1319E800B4F2D2 /* Notification.swift */; }; - 132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417972D1319E800B4F2D2 /* HistoryManager.swift */; }; - 132417A12D1319E800B4F2D2 /* ModuleStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417992D1319E800B4F2D2 /* ModuleStruct.swift */; }; - 132417A22D1319E800B4F2D2 /* ModulesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179A2D1319E800B4F2D2 /* ModulesManager.swift */; }; - 132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */; }; - 132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A72D131A0600B4F2D2 /* SearchView.swift */; }; - 132417B92D131A0600B4F2D2 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417A82D131A0600B4F2D2 /* SearchResultsView.swift */; }; - 132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */; }; - 132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */; }; - 132417BC2D131A0600B4F2D2 /* SettingsLogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */; }; - 132417BD2D131A0600B4F2D2 /* SettingsModuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */; }; - 132417BE2D131A0600B4F2D2 /* SettingsPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */; }; - 132417BF2D131A0600B4F2D2 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B02D131A0600B4F2D2 /* SettingView.swift */; }; - 132417C02D131A0600B4F2D2 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B12D131A0600B4F2D2 /* HomeView.swift */; }; - 132417C12D131A0600B4F2D2 /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B32D131A0600B4F2D2 /* LibraryManager.swift */; }; - 132417C22D131A0600B4F2D2 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B42D131A0600B4F2D2 /* LibraryView.swift */; }; - 132417C32D131A0600B4F2D2 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B62D131A0600B4F2D2 /* MediaView.swift */; }; - 132417C42D131A0600B4F2D2 /* MediaExtraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417B72D131A0600B4F2D2 /* MediaExtraction.swift */; }; - 132417CF2D131B7400B4F2D2 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 132417CE2D131B7400B4F2D2 /* SwiftSoup */; }; - 132417D22D131C5300B4F2D2 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 132417D12D131C5300B4F2D2 /* Kingfisher */; }; - 132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D42D13240200B4F2D2 /* EpisodeCell.swift */; }; - 132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */; }; - 132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */; }; - 1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1352BA702D1ABC30000A9AF9 /* URLSession.swift */; }; - 13AEE7BA2D2451F200CA634A /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE7B92D2451F200CA634A /* GitHubAPI.swift */; }; - 13AEE7BC2D24521200CA634A /* SettingsReleasesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */; }; - 13B3A4B22D1477F100BCC0D5 /* SettingsStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */; }; - 13B544B02D26D8E900CC6C59 /* OpenCastSwift iOS in Frameworks */ = {isa = PBXBuildFile; productRef = 13B544AF2D26D8E900CC6C59 /* OpenCastSwift iOS */; }; - 13C9821F2D2152B1007A0132 /* GitHubRelease.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C9821E2D2152B1007A0132 /* GitHubRelease.swift */; }; - 13ED65752D284045008F4C23 /* SettingsEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13ED65742D284045008F4C23 /* SettingsEditorView.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 1308CFBB2D19844A004CD38C /* Double+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; - 1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; - 1308CFC02D198466004CD38C /* CustomPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPlayer.swift; sourceTree = ""; }; - 132417802D13198000B4F2D2 /* Sora.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sora.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 132417832D13198000B4F2D2 /* SoraApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoraApp.swift; sourceTree = ""; }; - 132417852D13198000B4F2D2 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 132417872D13198200B4F2D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 132417932D1319E800B4F2D2 /* MiruDataStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiruDataStruct.swift; sourceTree = ""; }; - 132417952D1319E800B4F2D2 /* Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; - 132417972D1319E800B4F2D2 /* HistoryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; - 132417992D1319E800B4F2D2 /* ModuleStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModuleStruct.swift; sourceTree = ""; }; - 1324179A2D1319E800B4F2D2 /* ModulesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModulesManager.swift; sourceTree = ""; }; - 1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; - 132417A72D131A0600B4F2D2 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; - 132417A82D131A0600B4F2D2 /* SearchResultsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = ""; }; - 132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAboutView.swift; sourceTree = ""; }; - 132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIUView.swift; sourceTree = ""; }; - 132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsLogsView.swift; sourceTree = ""; }; - 132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsModuleView.swift; sourceTree = ""; }; - 132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsPlayerView.swift; sourceTree = ""; }; - 132417B02D131A0600B4F2D2 /* SettingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; - 132417B12D131A0600B4F2D2 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; - 132417B32D131A0600B4F2D2 /* LibraryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; - 132417B42D131A0600B4F2D2 /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; - 132417B62D131A0600B4F2D2 /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = ""; }; - 132417B72D131A0600B4F2D2 /* MediaExtraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaExtraction.swift; sourceTree = ""; }; - 132417C52D131AA500B4F2D2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 132417D42D13240200B4F2D2 /* EpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; - 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; - 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; - 1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Sora.entitlements; sourceTree = ""; }; - 1352BA702D1ABC30000A9AF9 /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; }; - 13AEE7B92D2451F200CA634A /* GitHubAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubAPI.swift; sourceTree = ""; }; - 13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsReleasesView.swift; sourceTree = ""; }; - 13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStorageView.swift; sourceTree = ""; }; - 13C9821E2D2152B1007A0132 /* GitHubRelease.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubRelease.swift; sourceTree = ""; }; - 13ED65742D284045008F4C23 /* SettingsEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsEditorView.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 1324177D2D13198000B4F2D2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 132417D22D131C5300B4F2D2 /* Kingfisher in Frameworks */, - 132417CF2D131B7400B4F2D2 /* SwiftSoup in Frameworks */, - 13B544B02D26D8E900CC6C59 /* OpenCastSwift iOS in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 1308CFBA2D19843E004CD38C /* CustomPlayer */ = { - isa = PBXGroup; - children = ( - 1308CFC02D198466004CD38C /* CustomPlayer.swift */, - 1308CFBF2D198450004CD38C /* Components */, - ); - path = CustomPlayer; - sourceTree = ""; - }; - 1308CFBF2D198450004CD38C /* Components */ = { - isa = PBXGroup; - children = ( - 1308CFBD2D19844D004CD38C /* MusicProgressSlider.swift */, - 1308CFBB2D19844A004CD38C /* Double+Extension.swift */, - ); - path = Components; - sourceTree = ""; - }; - 132417772D13198000B4F2D2 = { - isa = PBXGroup; - children = ( - 132417822D13198000B4F2D2 /* Sora */, - 132417812D13198000B4F2D2 /* Products */, - ); - sourceTree = ""; - }; - 132417812D13198000B4F2D2 /* Products */ = { - isa = PBXGroup; - children = ( - 132417802D13198000B4F2D2 /* Sora.app */, - ); - name = Products; - sourceTree = ""; - }; - 132417822D13198000B4F2D2 /* Sora */ = { - isa = PBXGroup; - children = ( - 1352BA6F2D1AB113000A9AF9 /* Sora.entitlements */, - 132417C52D131AA500B4F2D2 /* Info.plist */, - 132417912D1319E800B4F2D2 /* Utils */, - 132417A52D131A0600B4F2D2 /* Views */, - 132417832D13198000B4F2D2 /* SoraApp.swift */, - 132417852D13198000B4F2D2 /* ContentView.swift */, - 132417872D13198200B4F2D2 /* Assets.xcassets */, - 132417892D13198200B4F2D2 /* Preview Content */, - ); - path = Sora; - sourceTree = ""; - }; - 132417892D13198200B4F2D2 /* Preview Content */ = { - isa = PBXGroup; - children = ( - 1324178A2D13198200B4F2D2 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - 132417912D1319E800B4F2D2 /* Utils */ = { - isa = PBXGroup; - children = ( - 13C9821D2D2152A0007A0132 /* GitHub */, - 1308CFBA2D19843E004CD38C /* CustomPlayer */, - 132417922D1319E800B4F2D2 /* Miru */, - 132417942D1319E800B4F2D2 /* Extensions */, - 132417962D1319E800B4F2D2 /* History */, - 132417982D1319E800B4F2D2 /* Modules */, - 1324179B2D1319E800B4F2D2 /* Player */, - ); - path = Utils; - sourceTree = ""; - }; - 132417922D1319E800B4F2D2 /* Miru */ = { - isa = PBXGroup; - children = ( - 132417932D1319E800B4F2D2 /* MiruDataStruct.swift */, - ); - path = Miru; - sourceTree = ""; - }; - 132417942D1319E800B4F2D2 /* Extensions */ = { - isa = PBXGroup; - children = ( - 132417952D1319E800B4F2D2 /* Notification.swift */, - 1352BA702D1ABC30000A9AF9 /* URLSession.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - 132417962D1319E800B4F2D2 /* History */ = { - isa = PBXGroup; - children = ( - 132417972D1319E800B4F2D2 /* HistoryManager.swift */, - ); - path = History; - sourceTree = ""; - }; - 132417982D1319E800B4F2D2 /* Modules */ = { - isa = PBXGroup; - children = ( - 132417992D1319E800B4F2D2 /* ModuleStruct.swift */, - 1324179A2D1319E800B4F2D2 /* ModulesManager.swift */, - ); - path = Modules; - sourceTree = ""; - }; - 1324179B2D1319E800B4F2D2 /* Player */ = { - isa = PBXGroup; - children = ( - 132417D82D1328B900B4F2D2 /* VideoPlayerView.swift */, - 1324179C2D1319E800B4F2D2 /* NormalPlayer.swift */, - ); - path = Player; - sourceTree = ""; - }; - 132417A52D131A0600B4F2D2 /* Views */ = { - isa = PBXGroup; - children = ( - 132417B12D131A0600B4F2D2 /* HomeView.swift */, - 132417B22D131A0600B4F2D2 /* LibraryViews */, - 132417A62D131A0600B4F2D2 /* SearchViews */, - 132417A92D131A0600B4F2D2 /* SettingsViews */, - 132417B52D131A0600B4F2D2 /* MediaViews */, - ); - path = Views; - sourceTree = ""; - }; - 132417A62D131A0600B4F2D2 /* SearchViews */ = { - isa = PBXGroup; - children = ( - 132417A72D131A0600B4F2D2 /* SearchView.swift */, - 132417A82D131A0600B4F2D2 /* SearchResultsView.swift */, - ); - path = SearchViews; - sourceTree = ""; - }; - 132417A92D131A0600B4F2D2 /* SettingsViews */ = { - isa = PBXGroup; - children = ( - 132417AA2D131A0600B4F2D2 /* SubPages */, - 132417B02D131A0600B4F2D2 /* SettingView.swift */, - ); - path = SettingsViews; - sourceTree = ""; - }; - 132417AA2D131A0600B4F2D2 /* SubPages */ = { - isa = PBXGroup; - children = ( - 132417AB2D131A0600B4F2D2 /* SettingsAboutView.swift */, - 132417AC2D131A0600B4F2D2 /* SettingsIUView.swift */, - 132417AD2D131A0600B4F2D2 /* SettingsLogsView.swift */, - 132417AE2D131A0600B4F2D2 /* SettingsModuleView.swift */, - 132417AF2D131A0600B4F2D2 /* SettingsPlayerView.swift */, - 13B3A4B12D1477F100BCC0D5 /* SettingsStorageView.swift */, - 13AEE7BB2D24521200CA634A /* SettingsReleasesView.swift */, - 13ED65742D284045008F4C23 /* SettingsEditorView.swift */, - ); - path = SubPages; - sourceTree = ""; - }; - 132417B22D131A0600B4F2D2 /* LibraryViews */ = { - isa = PBXGroup; - children = ( - 132417B42D131A0600B4F2D2 /* LibraryView.swift */, - 132417B32D131A0600B4F2D2 /* LibraryManager.swift */, - ); - path = LibraryViews; - sourceTree = ""; - }; - 132417B52D131A0600B4F2D2 /* MediaViews */ = { - isa = PBXGroup; - children = ( - 132417D32D1323F500B4F2D2 /* EpisodesCell */, - 132417B62D131A0600B4F2D2 /* MediaView.swift */, - 132417B72D131A0600B4F2D2 /* MediaExtraction.swift */, - ); - path = MediaViews; - sourceTree = ""; - }; - 132417D32D1323F500B4F2D2 /* EpisodesCell */ = { - isa = PBXGroup; - children = ( - 132417D42D13240200B4F2D2 /* EpisodeCell.swift */, - 132417D62D13242400B4F2D2 /* CircularProgressBar.swift */, - ); - path = EpisodesCell; - sourceTree = ""; - }; - 13C9821D2D2152A0007A0132 /* GitHub */ = { - isa = PBXGroup; - children = ( - 13C9821E2D2152B1007A0132 /* GitHubRelease.swift */, - 13AEE7B92D2451F200CA634A /* GitHubAPI.swift */, - ); - path = GitHub; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 1324177F2D13198000B4F2D2 /* Sora */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1324178E2D13198200B4F2D2 /* Build configuration list for PBXNativeTarget "Sora" */; - buildPhases = ( - 1324177C2D13198000B4F2D2 /* Sources */, - 1324177D2D13198000B4F2D2 /* Frameworks */, - 1324177E2D13198000B4F2D2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Sora; - packageProductDependencies = ( - 132417CE2D131B7400B4F2D2 /* SwiftSoup */, - 132417D12D131C5300B4F2D2 /* Kingfisher */, - 13B544AF2D26D8E900CC6C59 /* OpenCastSwift iOS */, - ); - productName = Sora; - productReference = 132417802D13198000B4F2D2 /* Sora.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 132417782D13198000B4F2D2 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; - TargetAttributes = { - 1324177F2D13198000B4F2D2 = { - CreatedOnToolsVersion = 13.2.1; - }; - }; - }; - buildConfigurationList = 1324177B2D13198000B4F2D2 /* Build configuration list for PBXProject "Sora" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 132417772D13198000B4F2D2; - packageReferences = ( - 132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */, - 132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */, - 13B544AE2D26D8E900CC6C59 /* XCRemoteSwiftPackageReference "OpenCastSwift" */, - ); - productRefGroup = 132417812D13198000B4F2D2 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 1324177F2D13198000B4F2D2 /* Sora */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 1324177E2D13198000B4F2D2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1324178B2D13198200B4F2D2 /* Preview Assets.xcassets in Resources */, - 132417882D13198200B4F2D2 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 1324177C2D13198000B4F2D2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B3A4B22D1477F100BCC0D5 /* SettingsStorageView.swift in Sources */, - 132417BB2D131A0600B4F2D2 /* SettingsIUView.swift in Sources */, - 132417C42D131A0600B4F2D2 /* MediaExtraction.swift in Sources */, - 132417B82D131A0600B4F2D2 /* SearchView.swift in Sources */, - 1308CFBC2D19844A004CD38C /* Double+Extension.swift in Sources */, - 132417D92D1328B900B4F2D2 /* VideoPlayerView.swift in Sources */, - 1324179F2D1319E800B4F2D2 /* Notification.swift in Sources */, - 132417BD2D131A0600B4F2D2 /* SettingsModuleView.swift in Sources */, - 132417BC2D131A0600B4F2D2 /* SettingsLogsView.swift in Sources */, - 1308CFC12D198466004CD38C /* CustomPlayer.swift in Sources */, - 132417A22D1319E800B4F2D2 /* ModulesManager.swift in Sources */, - 132417862D13198000B4F2D2 /* ContentView.swift in Sources */, - 13AEE7BA2D2451F200CA634A /* GitHubAPI.swift in Sources */, - 132417C22D131A0600B4F2D2 /* LibraryView.swift in Sources */, - 132417A32D1319E800B4F2D2 /* NormalPlayer.swift in Sources */, - 132417D72D13242400B4F2D2 /* CircularProgressBar.swift in Sources */, - 132417C02D131A0600B4F2D2 /* HomeView.swift in Sources */, - 132417BF2D131A0600B4F2D2 /* SettingView.swift in Sources */, - 132417C32D131A0600B4F2D2 /* MediaView.swift in Sources */, - 132417A12D1319E800B4F2D2 /* ModuleStruct.swift in Sources */, - 132417B92D131A0600B4F2D2 /* SearchResultsView.swift in Sources */, - 13AEE7BC2D24521200CA634A /* SettingsReleasesView.swift in Sources */, - 132417842D13198000B4F2D2 /* SoraApp.swift in Sources */, - 132417BE2D131A0600B4F2D2 /* SettingsPlayerView.swift in Sources */, - 132417C12D131A0600B4F2D2 /* LibraryManager.swift in Sources */, - 132417BA2D131A0600B4F2D2 /* SettingsAboutView.swift in Sources */, - 1324179E2D1319E800B4F2D2 /* MiruDataStruct.swift in Sources */, - 13ED65752D284045008F4C23 /* SettingsEditorView.swift in Sources */, - 1308CFBE2D19844D004CD38C /* MusicProgressSlider.swift in Sources */, - 132417D52D13240200B4F2D2 /* EpisodeCell.swift in Sources */, - 13C9821F2D2152B1007A0132 /* GitHubRelease.swift in Sources */, - 1352BA712D1ABC30000A9AF9 /* URLSession.swift in Sources */, - 132417A02D1319E800B4F2D2 /* HistoryManager.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 1324178C2D13198200B4F2D2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 1324178D2D13198200B4F2D2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 1324178F2D13198200B4F2D2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; - DEVELOPMENT_TEAM = 399LMK6Q2Y; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Sora/Info.plist; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 0.1.1; - PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTS_MACCATALYST = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 132417902D13198200B4F2D2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = Sora/Sora.entitlements; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Sora/Preview Content\""; - DEVELOPMENT_TEAM = 399LMK6Q2Y; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Sora/Info.plist; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 0.1.1; - PRODUCT_BUNDLE_IDENTIFIER = me.cranci.Sora; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTS_MACCATALYST = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1324177B2D13198000B4F2D2 /* Build configuration list for PBXProject "Sora" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1324178C2D13198200B4F2D2 /* Debug */, - 1324178D2D13198200B4F2D2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1324178E2D13198200B4F2D2 /* Build configuration list for PBXNativeTarget "Sora" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1324178F2D13198200B4F2D2 /* Debug */, - 132417902D13198200B4F2D2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - 132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/scinfu/SwiftSoup.git"; - requirement = { - kind = exactVersion; - version = 2.4.0; - }; - }; - 132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; - requirement = { - kind = exactVersion; - version = 7.9.1; - }; - }; - 13B544AE2D26D8E900CC6C59 /* XCRemoteSwiftPackageReference "OpenCastSwift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/danwilliams64/OpenCastSwift.git"; - requirement = { - branch = master; - kind = branch; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 132417CE2D131B7400B4F2D2 /* SwiftSoup */ = { - isa = XCSwiftPackageProductDependency; - package = 132417CD2D131B7400B4F2D2 /* XCRemoteSwiftPackageReference "SwiftSoup" */; - productName = SwiftSoup; - }; - 132417D12D131C5300B4F2D2 /* Kingfisher */ = { - isa = XCSwiftPackageProductDependency; - package = 132417D02D131C5300B4F2D2 /* XCRemoteSwiftPackageReference "Kingfisher" */; - productName = Kingfisher; - }; - 13B544AF2D26D8E900CC6C59 /* OpenCastSwift iOS */ = { - isa = XCSwiftPackageProductDependency; - package = 13B544AE2D26D8E900CC6C59 /* XCRemoteSwiftPackageReference "OpenCastSwift" */; - productName = "OpenCastSwift iOS"; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 132417782D13198000B4F2D2 /* Project object */; -} diff --git a/Sora.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sora.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index e6975d3..0000000 --- a/Sora.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,52 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher.git", - "state": { - "branch": null, - "revision": "b6f62758f21a8c03cd64f4009c037cfa580a256e", - "version": "7.9.1" - } - }, - { - "package": "OpenCastSwift", - "repositoryURL": "https://github.com/danwilliams64/OpenCastSwift.git", - "state": { - "branch": "master", - "revision": "f96bf1ed9c1dcad34a1fcccb50d79a6cf612def4", - "version": null - } - }, - { - "package": "SwiftProtobuf", - "repositoryURL": "https://github.com/apple/swift-protobuf", - "state": { - "branch": null, - "revision": "9f0c76544701845ad98716f3f6a774a892152bcb", - "version": "1.26.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", - "state": { - "branch": null, - "revision": "5386dab25134eec11fc35fc5e43caf422fad0270", - "version": "2.4.0" - } - }, - { - "package": "SwiftyJSON", - "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON", - "state": { - "branch": null, - "revision": "af76cf3ef710b6ca5f8c05f3a31307d44a3c5828", - "version": "5.0.2" - } - } - ] - }, - "version": 1 -} diff --git a/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate b/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 32d15d5..0000000 Binary files a/Sora.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Sora.xcodeproj/xcuserdata/Francesco.xcuserdatad/xcschemes/xcschememanagement.plist b/Sora.xcodeproj/xcuserdata/Francesco.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 6268ffc..0000000 --- a/Sora.xcodeproj/xcuserdata/Francesco.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,35 +0,0 @@ - - - - - SchemeUserState - - Playground (Playground) 1.xcscheme - - isShown - - orderHint - 2 - - Playground (Playground) 2.xcscheme - - isShown - - orderHint - 3 - - Playground (Playground).xcscheme - - isShown - - orderHint - 0 - - Sora.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/Sora/Assets.xcassets/AccentColor.colorset/.DS_Store b/Sora/Assets.xcassets/AccentColor.colorset/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Sora/Assets.xcassets/AccentColor.colorset/.DS_Store and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/120-1.png b/Sora/Assets.xcassets/AppIcon.appiconset/120-1.png deleted file mode 100644 index 1cbc052..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/120-1.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/120.png b/Sora/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index 1cbc052..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/120.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/152.png b/Sora/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index 953db5a..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/152.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/167.png b/Sora/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index 6e41ff7..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/167.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/180.png b/Sora/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index 394bb7b..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/180.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/20.png b/Sora/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 3dbb016..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/20.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/29.png b/Sora/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index 55dd67d..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/29.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/40-1.png b/Sora/Assets.xcassets/AppIcon.appiconset/40-1.png deleted file mode 100644 index 1e9c473..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/40-1.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/40-2.png b/Sora/Assets.xcassets/AppIcon.appiconset/40-2.png deleted file mode 100644 index 1e9c473..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/40-2.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/40.png b/Sora/Assets.xcassets/AppIcon.appiconset/40.png deleted file mode 100644 index 1e9c473..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/40.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/58-1.png b/Sora/Assets.xcassets/AppIcon.appiconset/58-1.png deleted file mode 100644 index 25a3dec..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/58-1.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/58.png b/Sora/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index 25a3dec..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/58.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/60.png b/Sora/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index d007118..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/60.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/76.png b/Sora/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index 6648094..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/76.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/80-1.png b/Sora/Assets.xcassets/AppIcon.appiconset/80-1.png deleted file mode 100644 index b048379..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/80-1.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/80.png b/Sora/Assets.xcassets/AppIcon.appiconset/80.png deleted file mode 100644 index b048379..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/80.png and /dev/null differ diff --git a/Sora/Assets.xcassets/AppIcon.appiconset/87.png b/Sora/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index 0659285..0000000 Binary files a/Sora/Assets.xcassets/AppIcon.appiconset/87.png and /dev/null differ diff --git a/Sora/ContentView.swift b/Sora/ContentView.swift deleted file mode 100644 index 58a8a13..0000000 --- a/Sora/ContentView.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// ContentView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct ContentView: View { - @EnvironmentObject var modulesManager: ModulesManager - - var body: some View { - TabView { - HomeView() - .tabItem { - Label("Home", systemImage: "house") - } - LibraryView() - .tabItem { - Label("Library", systemImage: "books.vertical") - } - SearchView() - .tabItem { - Label("Search", systemImage: "magnifyingglass") - } - SettingsView() - .tabItem { - Label("Settings", systemImage: "gearshape") - } - } - .onAppear { - checkForUpdate() - Logger.shared.log("Started Sora") - } - } - - func checkForUpdate() { - fetchLatestRelease { release in - guard let release = release else { return } - - let latestVersion = release.tagName - let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.1.1" - - if latestVersion.compare(currentVersion, options: .numeric) == .orderedDescending { - DispatchQueue.main.async { - showUpdateAlert(release: release) - } - } - } - } - - func fetchLatestRelease(completion: @escaping (GitHubRelease?) -> Void) { - let url = URL(string: "https://api.github.com/repos/cranci1/Sora/releases/latest")! - - URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { - completion(nil) - return - } - - let release = try? JSONDecoder().decode(GitHubRelease.self, from: data) - completion(release) - }.resume() - } - - func showUpdateAlert(release: GitHubRelease) { - let alert = UIAlertController(title: "Update Available", message: "A new version (\(release.tagName)) is available. Would you like to update Sora?", preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: "Update", style: .default, handler: { _ in - self.showInstallOptionsAlert(release: release) - })) - - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootViewController = windowScene.windows.first?.rootViewController { - rootViewController.present(alert, animated: true, completion: nil) - } - } - - func showInstallOptionsAlert(release: GitHubRelease) { - let installAlert = UIAlertController(title: "Install Update", message: "Choose an installation method:", preferredStyle: .alert) - - let downloadUrl = release.assets.first?.browserDownloadUrl ?? "" - - installAlert.addAction(UIAlertAction(title: "Install in AltStore", style: .default, handler: { _ in - if let url = URL(string: "altstore://install?url=\(downloadUrl)") { - UIApplication.shared.open(url) - } - })) - - installAlert.addAction(UIAlertAction(title: "Install in Sidestore", style: .default, handler: { _ in - if let url = URL(string: "sidestore://install?url=\(downloadUrl)") { - UIApplication.shared.open(url) - } - })) - - installAlert.addAction(UIAlertAction(title: "Open in Safari", style: .default, handler: { _ in - if let url = URL(string: downloadUrl) { - UIApplication.shared.open(url) - } - })) - - installAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootViewController = windowScene.windows.first?.rootViewController { - rootViewController.present(installAlert, animated: true, completion: nil) - } - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Sora/Info.plist b/Sora/Info.plist deleted file mode 100644 index 305d2ec..0000000 --- a/Sora/Info.plist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - me.cranci.scheme - CFBundleURLSchemes - - ryu - - - - UIBackgroundModes - - audio - - - diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift deleted file mode 100644 index bc0e9e6..0000000 --- a/Sora/SoraApp.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// SoraApp.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -@main -struct SoraApp: App { - @StateObject private var settings = Settings() - @StateObject private var modulesManager = ModulesManager() - - var body: some Scene { - WindowGroup { - ContentView() - .environmentObject(settings) - .environmentObject(modulesManager) - .accentColor(settings.accentColor) - .onAppear { - settings.updateAppearance() - } - .onOpenURL { url in - handleURL(url) - } - } - } - - private func handleURL(_ url: URL) { - guard url.scheme == "sora", - url.host == "module", - let components = URLComponents(url: url, resolvingAgainstBaseURL: true), - let moduleURL = components.queryItems?.first(where: { $0.name == "url" })?.value else { - return - } - - modulesManager.addModule(from: moduleURL) { result in - switch result { - case .success: - NotificationCenter.default.post(name: .moduleAdded, object: nil) - Logger.shared.log("Successfully added module from URL scheme: \(moduleURL)") - case .failure(let error): - Logger.shared.log("Failed to add module from URL scheme: \(error.localizedDescription)") - } - } - } -} diff --git a/Sora/Utils/CustomPlayer/Components/Double+Extension.swift b/Sora/Utils/CustomPlayer/Components/Double+Extension.swift deleted file mode 100644 index 801cc7b..0000000 --- a/Sora/Utils/CustomPlayer/Components/Double+Extension.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Double+Extension.swift -// AppleMusicSlider -// -// Created by Pratik on 14/01/23. -// -// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com" -// - -import Foundation - -extension Double { - func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.minute, .second] - formatter.unitsStyle = style - formatter.zeroFormattingBehavior = .pad - return formatter.string(from: self) ?? "" - } -} - -extension BinaryFloatingPoint { - func asTimeString(style: DateComponentsFormatter.UnitsStyle) -> String { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.minute, .second] - formatter.unitsStyle = style - formatter.zeroFormattingBehavior = .pad - return formatter.string(from: TimeInterval(self)) ?? "" - } -} diff --git a/Sora/Utils/CustomPlayer/Components/MusicProgressSlider.swift b/Sora/Utils/CustomPlayer/Components/MusicProgressSlider.swift deleted file mode 100644 index 8bbb950..0000000 --- a/Sora/Utils/CustomPlayer/Components/MusicProgressSlider.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// MusicProgressSlider.swift -// Custom Seekbar -// -// Created by Pratik on 08/01/23. -// -// Thanks to pratikg29 for this code inside his open source project "https://github.com/pratikg29/Custom-Slider-Control?ref=iosexample.com" -// I did edit just a little bit the code for my liking -// - -import SwiftUI - -struct MusicProgressSlider: View { - @Binding var value: T - let inRange: ClosedRange - let activeFillColor: Color - let fillColor: Color - let emptyColor: Color - let height: CGFloat - let onEditingChanged: (Bool) -> Void - - @State private var localRealProgress: T = 0 - @State private var localTempProgress: T = 0 - @GestureState private var isActive: Bool = false - - var body: some View { - GeometryReader { bounds in - ZStack { - VStack { - ZStack(alignment: .center) { - Capsule() - .fill(emptyColor) - Capsule() - .fill(isActive ? activeFillColor : fillColor) - .mask({ - HStack { - Rectangle() - .frame(width: max(bounds.size.width * CGFloat((localRealProgress + localTempProgress)), 0), alignment: .leading) - Spacer(minLength: 0) - } - }) - } - - HStack { - Text(value.asTimeString(style: .positional)) - Spacer(minLength: 0) - Text("-" + (inRange.upperBound - value).asTimeString(style: .positional)) - } - .font(.system(size: 12)) - .foregroundColor(isActive ? fillColor : emptyColor) - } - .frame(width: isActive ? bounds.size.width * 1.04 : bounds.size.width, alignment: .center) - .animation(animation, value: isActive) - } - .frame(width: bounds.size.width, height: bounds.size.height, alignment: .center) - .contentShape(Rectangle()) - .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) - .updating($isActive) { _, state, _ in - state = true - } - .onChanged { gesture in - localTempProgress = T(gesture.translation.width / bounds.size.width) - value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound) - }.onEnded { _ in - localRealProgress = max(min(localRealProgress + localTempProgress, 1), 0) - localTempProgress = 0 - }) - .onChange(of: isActive) { newValue in - value = max(min(getPrgValue(), inRange.upperBound), inRange.lowerBound) - onEditingChanged(newValue) - } - .onAppear { - localRealProgress = getPrgPercentage(value) - } - .onChange(of: value) { newValue in - if !isActive { - localRealProgress = getPrgPercentage(newValue) - } - } - } - .frame(height: isActive ? height * 1.25 : height, alignment: .center) - } - - private var animation: Animation { - if isActive { - return .spring() - } else { - return .spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.6) - } - } - - private func getPrgPercentage(_ value: T) -> T { - let range = inRange.upperBound - inRange.lowerBound - let correctedStartValue = value - inRange.lowerBound - let percentage = correctedStartValue / range - return percentage - } - - private func getPrgValue() -> T { - return ((localRealProgress + localTempProgress) * (inRange.upperBound - inRange.lowerBound)) + inRange.lowerBound - } -} diff --git a/Sora/Utils/CustomPlayer/CustomPlayer.swift b/Sora/Utils/CustomPlayer/CustomPlayer.swift deleted file mode 100644 index 8748230..0000000 --- a/Sora/Utils/CustomPlayer/CustomPlayer.swift +++ /dev/null @@ -1,271 +0,0 @@ -// -// ContentView.swift -// test2 -// -// Created by Francesco on 20/12/24. -// - -import SwiftUI -import AVKit - -struct CustomVideoPlayer: UIViewControllerRepresentable { - let player: AVPlayer - - func makeUIViewController(context: Context) -> AVPlayerViewController { - let controller = NormalPlayer() - controller.player = player - controller.showsPlaybackControls = false - player.play() - return controller - } - - func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { - // yes? Like the plural of the famous american rapper ye? -IBHRAD - } -} - -struct CustomMediaPlayer: View { - @State private var player: AVPlayer - @State private var isPlaying = true - @State private var currentTime: Double = 0.0 - @State private var duration: Double = 0.0 - @State private var showControls = false - @State private var inactivityTimer: Timer? - @State private var timeObserverToken: Any? - @Environment(\.presentationMode) var presentationMode - - let module: ModuleStruct - let fullUrl: String - let title: String - let episodeNumber: Int - let onWatchNext: () -> Void - - init(module: ModuleStruct, urlString: String, fullUrl: String, title: String, episodeNumber: Int, onWatchNext: @escaping () -> Void) { - guard let url = URL(string: urlString) else { - fatalError("Invalid URL string") - } - - var request = URLRequest(url: url) - if urlString.contains("ascdn") { - request.addValue("\(module.module[0].details.baseURL)", forHTTPHeaderField: "Referer") - } - - let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]]) - _player = State(initialValue: AVPlayer(playerItem: AVPlayerItem(asset: asset))) - - self.module = module - self.fullUrl = fullUrl - self.title = title - self.episodeNumber = episodeNumber - self.onWatchNext = onWatchNext - - let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)") - if lastPlayedTime > 0 { - let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1) - self._player.wrappedValue.seek(to: seekTime) - } - } - - var body: some View { - ZStack { - VStack { - ZStack { - CustomVideoPlayer(player: player) - .onAppear { - player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 600), queue: .main) { time in - currentTime = time.seconds - if let itemDuration = player.currentItem?.duration.seconds, itemDuration.isFinite && !itemDuration.isNaN { - duration = itemDuration - } - } - startUpdatingCurrentTime() - addPeriodicTimeObserver(fullURL: fullUrl) - } - .edgesIgnoringSafeArea(.all) - .overlay( - Group { - if showControls { - Color.black.opacity(0.5) - .edgesIgnoringSafeArea(.all) - HStack(spacing: 20) { - Button(action: { - currentTime = max(currentTime - 10, 0) - player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600)) - }) { - Image(systemName: "gobackward.10") - } - .foregroundColor(.white) - .font(.system(size: 25)) - .contentShape(Rectangle()) - .frame(width: 60, height: 60) - - Button(action: { - if isPlaying { - player.pause() - } else { - player.play() - } - isPlaying.toggle() - }) { - Image(systemName: isPlaying ? "pause.fill" : "play.fill") - } - .foregroundColor(.white) - .font(.system(size: 45)) - .contentShape(Rectangle()) - .frame(width: 80, height: 80) - - Button(action: { - currentTime = min(currentTime + 10, duration) - player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600)) - }) { - Image(systemName: "goforward.10") - } - .foregroundColor(.white) - .font(.system(size: 25)) - .contentShape(Rectangle()) - .frame(width: 60, height: 60) - } - } - } - .animation(.easeInOut(duration: 0.2), value: showControls), - alignment: .center - ) - .onTapGesture { - withAnimation { - showControls.toggle() - } - } - - VStack { - Spacer() - VStack { - HStack(alignment: .bottom) { - if showControls { - VStack(alignment: .leading) { - Text("Episode \(episodeNumber)") - .font(.subheadline) - .foregroundColor(.gray) - Text(title) - .font(.headline) - .foregroundColor(.white) - } - .padding(.horizontal, 32) - } - Spacer() - if duration - currentTime <= duration * 0.10 && currentTime != duration { - Button(action: { - player.pause() - presentationMode.wrappedValue.dismiss() - onWatchNext() - }) { - HStack { - Image(systemName: "forward.fill") - .foregroundColor(Color.black) - Text("Watch Next") - .font(.headline) - .foregroundColor(Color.black) - } - .padding() - .background(Color.white) - .cornerRadius(32) - } - .padding(.trailing, 10) - } - if showControls { - Menu { - Menu("Playback Speed") { - ForEach([0.5, 1.0, 1.25, 1.5, 1.75, 2.0], id: \.self) { speed in - Button(action: { - player.rate = Float(speed) - if player.timeControlStatus != .playing { - player.pause() - } - }) { - Text("\(speed, specifier: "%.2f")") - } - } - } - } label: { - Image(systemName: "ellipsis.circle") - .foregroundColor(.white) - .font(.system(size: 15)) - } - } - } - .padding(.trailing, 32) - - if showControls { - MusicProgressSlider( - value: $currentTime, - inRange: 0...duration, - activeFillColor: .white, - fillColor: .white.opacity(0.5), - emptyColor: .white.opacity(0.3), - height: 28, - onEditingChanged: { editing in - if !editing { - player.seek(to: CMTime(seconds: currentTime, preferredTimescale: 600)) - } - } - ) - .padding(.horizontal, 32) - .padding(.bottom, 10) - } - } - } - .onAppear { - startUpdatingCurrentTime() - } - .onDisappear { - player.pause() - inactivityTimer?.invalidate() - if let timeObserverToken = timeObserverToken { - player.removeTimeObserver(timeObserverToken) - self.timeObserverToken = nil - } - } - } - } - VStack { - if showControls { - HStack { - Button(action: { - presentationMode.wrappedValue.dismiss() - }) { - Image(systemName: "xmark") - .foregroundColor(.white) - .font(.system(size: 20)) - } - .frame(width: 60, height: 60) - .contentShape(Rectangle()) - .padding() - Spacer() - } - Spacer() - } - } - } - } - - private func startUpdatingCurrentTime() { - Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - currentTime = player.currentTime().seconds - } - } - - private func addPeriodicTimeObserver(fullURL: String) { - let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) - timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in - guard let currentItem = player.currentItem, - currentItem.duration.seconds.isFinite else { - return - } - - let currentTime = time.seconds - let duration = currentItem.duration.seconds - - UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)") - UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)") - } - } -} diff --git a/Sora/Utils/Extensions/Notification.swift b/Sora/Utils/Extensions/Notification.swift deleted file mode 100644 index 44863ae..0000000 --- a/Sora/Utils/Extensions/Notification.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Notification.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation - -extension Notification.Name { - static let moduleAdded = Notification.Name("moduleAdded") - static let moduleRemoved = Notification.Name("moduleRemoved") -} diff --git a/Sora/Utils/Extensions/URLSession.swift b/Sora/Utils/Extensions/URLSession.swift deleted file mode 100644 index 0ec1021..0000000 --- a/Sora/Utils/Extensions/URLSession.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// URLSession.swift -// Sora -// -// Created by Francesco on 24/12/24. -// - -import Foundation - -extension URLSession { - static let custom: URLSession = { - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = [ - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" - ] - return URLSession(configuration: configuration) - }() -} diff --git a/Sora/Utils/GitHub/GitHubAPI.swift b/Sora/Utils/GitHub/GitHubAPI.swift deleted file mode 100644 index 5dac7b5..0000000 --- a/Sora/Utils/GitHub/GitHubAPI.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// GitHubAPI.swift -// Sora -// -// Created by Francesco on 31/12/24. -// - -import Foundation - -struct GitHubReleases: Codable { - let tagName: String - let body: String - let htmlUrl: String - - enum CodingKeys: String, CodingKey { - case tagName = "tag_name" - case body - case htmlUrl = "html_url" - } -} - -class GitHubAPI { - static let shared = GitHubAPI() - - func fetchReleases(completion: @escaping ([GitHubReleases]?) -> Void) { - let url = URL(string: "https://api.github.com/repos/cranci1/Sora/releases")! - - URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { - completion(nil) - return - } - - let releases = try? JSONDecoder().decode([GitHubReleases].self, from: data) - completion(releases) - }.resume() - } -} diff --git a/Sora/Utils/GitHub/GitHubRelease.swift b/Sora/Utils/GitHub/GitHubRelease.swift deleted file mode 100644 index 4803aa3..0000000 --- a/Sora/Utils/GitHub/GitHubRelease.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// GitHubRelease.swift -// Sora -// -// Created by Francesco on 29/12/24. -// - -import Foundation - -struct GitHubRelease: Codable { - let url: String - let assetsUrl: String - let uploadUrl: String - let htmlUrl: String - let id: Int - let author: Author - let nodeId: String - let tagName: String - let targetCommitish: String - let name: String - let draft: Bool - let prerelease: Bool - let createdAt: String - let publishedAt: String - let assets: [Asset] - let tarballUrl: String - let zipballUrl: String - let body: String - - enum CodingKeys: String, CodingKey { - case url - case assetsUrl = "assets_url" - case uploadUrl = "upload_url" - case htmlUrl = "html_url" - case id - case author - case nodeId = "node_id" - case tagName = "tag_name" - case targetCommitish = "target_commitish" - case name - case draft - case prerelease - case createdAt = "created_at" - case publishedAt = "published_at" - case assets - case tarballUrl = "tarball_url" - case zipballUrl = "zipball_url" - case body - } - - struct Author: Codable { - let login: String - let id: Int - let nodeId: String - let avatarUrl: String - let gravatarId: String - let url: String - let htmlUrl: String - let followersUrl: String - let followingUrl: String - let gistsUrl: String - let starredUrl: String - let subscriptionsUrl: String - let organizationsUrl: String - let reposUrl: String - let eventsUrl: String - let receivedEventsUrl: String - let type: String - let siteAdmin: Bool - - enum CodingKeys: String, CodingKey { - case login - case id - case nodeId = "node_id" - case avatarUrl = "avatar_url" - case gravatarId = "gravatar_id" - case url - case htmlUrl = "html_url" - case followersUrl = "followers_url" - case followingUrl = "following_url" - case gistsUrl = "gists_url" - case starredUrl = "starred_url" - case subscriptionsUrl = "subscriptions_url" - case organizationsUrl = "organizations_url" - case reposUrl = "repos_url" - case eventsUrl = "events_url" - case receivedEventsUrl = "received_events_url" - case type - case siteAdmin = "site_admin" - } - } - - struct Asset: Codable { - let url: String - let id: Int - let nodeId: String - let name: String - let label: String? - let uploader: Author - let contentType: String - let state: String - let size: Int - let downloadCount: Int - let createdAt: String - let updatedAt: String - let browserDownloadUrl: String - - enum CodingKeys: String, CodingKey { - case url - case id - case nodeId = "node_id" - case name - case label - case uploader - case contentType = "content_type" - case state - case size - case downloadCount = "download_count" - case createdAt = "created_at" - case updatedAt = "updated_at" - case browserDownloadUrl = "browser_download_url" - } - } -} diff --git a/Sora/Utils/History/HistoryManager.swift b/Sora/Utils/History/HistoryManager.swift deleted file mode 100644 index cbc7fe2..0000000 --- a/Sora/Utils/History/HistoryManager.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// HistoryManager.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation -import Combine - -class HistoryManager: ObservableObject { - @Published var searchHistory: [String] = UserDefaults.standard.stringArray(forKey: "SearchHistory") ?? [] - - private var cancellables = Set() - - init() { - NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification) - .sink { [weak self] _ in - DispatchQueue.main.async { - self?.searchHistory = UserDefaults.standard.stringArray(forKey: "SearchHistory") ?? [] - } - } - .store(in: &cancellables) - } - - func addSearchHistory(_ item: String) { - if !searchHistory.contains(item) { - searchHistory.insert(item, at: 0) - UserDefaults.standard.set(searchHistory, forKey: "SearchHistory") - } - } - - func deleteHistoryItem(at offsets: IndexSet) { - searchHistory.remove(atOffsets: offsets) - UserDefaults.standard.set(searchHistory, forKey: "SearchHistory") - } -} diff --git a/Sora/Utils/Miru/MiruDataStruct.swift b/Sora/Utils/Miru/MiruDataStruct.swift deleted file mode 100644 index c50e75d..0000000 --- a/Sora/Utils/Miru/MiruDataStruct.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// MiruDataStruct.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation - -struct MiruDataStruct: Codable { - var likes: [Like] - - struct Like: Codable { - let anilistID: Int - var gogoSlug: String - let title: String - let cover: String - - enum CodingKeys: String, CodingKey { - case anilistID = "anilist_id" - case gogoSlug = "gogo_slug" - case title - case cover - } - } -} diff --git a/Sora/Utils/Modules/ModuleStruct.swift b/Sora/Utils/Modules/ModuleStruct.swift deleted file mode 100644 index c8725cc..0000000 --- a/Sora/Utils/Modules/ModuleStruct.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// ModuleStruct.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation - -struct ModuleStruct: Codable { - let name: String - let version: String - let author: Author - let iconURL: String - let stream: String - let language: String - let extractor: String - let module: [Module] - - struct Author: Codable { - let name: String - let website: String - } - - struct Module: Codable, Hashable { - let search: Search - let featured: Featured - let details: Details - let episodes: Episodes - - struct Search: Codable, Hashable { - let url: String - let parameter: String - let documentSelector: String - let title: String - let image: Image - let href: String - let searchable: Bool? - - struct Image: Codable, Hashable { - let url: String - let attribute: String - } - } - - struct Featured: Codable, Hashable { - let url: String - let documentSelector: String - let title: String - let image: Image - let href: String - - struct Image: Codable, Hashable { - let url: String - let attribute: String - } - } - - struct Details: Codable, Hashable { - let baseURL: String - let pageRedirects: Bool? - let aliases: Aliases - let synopsis: String - let airdate: String - let stars: String - - struct Aliases: Codable, Hashable { - let selector: String - let attribute: String - } - } - - struct Episodes: Codable, Hashable { - let selector: String - let order: String - let pattern: String - let pattern2: String? - } - } -} diff --git a/Sora/Utils/Modules/ModulesManager.swift b/Sora/Utils/Modules/ModulesManager.swift deleted file mode 100644 index 99f9b3e..0000000 --- a/Sora/Utils/Modules/ModulesManager.swift +++ /dev/null @@ -1,155 +0,0 @@ -// -// ModulesManager.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation - -class ModulesManager: ObservableObject { - @Published var modules: [ModuleStruct] = [] - @Published var isLoading = true - var moduleURLs: [String: String] = [:] - private let modulesFileName = "modules.json" - private let moduleURLsFileName = "moduleURLs.json" - - init() { - loadModules() - } - - func loadModules() { - isLoading = true - loadModuleURLs() - loadModuleData() - isLoading = false - } - - func addModule(from urlString: String, completion: @escaping (Result) -> Void) { - guard let url = URL(string: urlString) else { - completion(.failure(ModuleError.invalidURL)) - return - } - let task = URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { - completion(.failure(error ?? ModuleError.unknown)) - return - } - do { - let module = try JSONDecoder().decode(ModuleStruct.self, from: data) - DispatchQueue.main.async { - if !self.modules.contains(where: { $0.name == module.name }) { - self.modules.append(module) - self.moduleURLs[module.name] = urlString - self.saveModuleData() - self.saveModuleURLs() - NotificationCenter.default.post(name: .moduleAdded, object: nil) - completion(.success(())) - } else { - completion(.failure(ModuleError.duplicateModule)) - } - } - } catch { - completion(.failure(error)) - } - } - task.resume() - } - - func deleteModule(named name: String) { - if let index = modules.firstIndex(where: { $0.name == name }) { - modules.remove(at: index) - moduleURLs.removeValue(forKey: name) - saveModuleData() - saveModuleURLs() - NotificationCenter.default.post(name: .moduleRemoved, object: nil) - } - } - - func refreshModules() { - for (name, urlString) in moduleURLs { - guard let url = URL(string: urlString) else { continue } - let task = URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { return } - do { - let updatedModule = try JSONDecoder().decode(ModuleStruct.self, from: data) - DispatchQueue.main.async { - if let index = self.modules.firstIndex(where: { $0.name == name }) { - self.modules[index] = updatedModule - self.saveModuleData() - } - } - } catch { - print("Failed to decode module during refresh: \(error.localizedDescription)") - Logger.shared.log("Failed to decode module during refresh: \(error.localizedDescription)") - } - } - task.resume() - } - } - - private func loadModuleURLs() { - let fileURL = getDocumentsDirectory().appendingPathComponent(moduleURLsFileName) - do { - let data = try Data(contentsOf: fileURL) - moduleURLs = try JSONDecoder().decode([String: String].self, from: data) - } catch { - print("Failed to load module URLs: \(error.localizedDescription)") - Logger.shared.log("Failed to load module URLs: \(error.localizedDescription)") - } - } - - private func loadModuleData() { - let fileURL = getDocumentsDirectory().appendingPathComponent(modulesFileName) - do { - let data = try Data(contentsOf: fileURL) - modules = try JSONDecoder().decode([ModuleStruct].self, from: data) - } catch { - print("Failed to load modules: \(error.localizedDescription)") - Logger.shared.log("Failed to load modules: \(error.localizedDescription)") - } - } - - func saveModuleData() { - let fileURL = getDocumentsDirectory().appendingPathComponent(modulesFileName) - do { - let data = try JSONEncoder().encode(modules) - try data.write(to: fileURL) - } catch { - print("Failed to save modules: \(error.localizedDescription)") - Logger.shared.log("Failed to save modules: \(error.localizedDescription)") - } - } - - private func saveModuleURLs() { - let fileURL = getDocumentsDirectory().appendingPathComponent(moduleURLsFileName) - do { - let data = try JSONEncoder().encode(moduleURLs) - try data.write(to: fileURL) - } catch { - print("Failed to save module URLs: \(error.localizedDescription)") - Logger.shared.log("Failed to save module URLs: \(error.localizedDescription)") - } - } - - private func getDocumentsDirectory() -> URL { - FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - } - - enum ModuleError: LocalizedError { - case invalidURL - case duplicateModule - case unknown - - var errorDescription: String? { - switch self { - case .invalidURL: - return "The provided URL is invalid." - case .duplicateModule: - return "This module already exists." - case .unknown: - return "An unknown error occurred." - } - } - } -} diff --git a/Sora/Utils/Player/NormalPlayer.swift b/Sora/Utils/Player/NormalPlayer.swift deleted file mode 100644 index 2c92496..0000000 --- a/Sora/Utils/Player/NormalPlayer.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// NormalPlayer.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import AVKit - -class NormalPlayer: AVPlayerViewController { - private var originalRate: Float = 1.0 - private var holdGesture: UILongPressGestureRecognizer? - - override func viewDidLoad() { - super.viewDidLoad() - setupHoldGesture() - setupAudioSession() - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - if UserDefaults.standard.bool(forKey: "AlwaysLandscape") { - return .landscape - } else { - return .all - } - } - - override var prefersHomeIndicatorAutoHidden: Bool { - return true - } - - override var prefersStatusBarHidden: Bool { - return true - } - - private func setupHoldGesture() { - holdGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleHoldGesture(_:))) - holdGesture?.minimumPressDuration = 0.5 - if let holdGesture = holdGesture { - view.addGestureRecognizer(holdGesture) - } - } - - @objc private func handleHoldGesture(_ gesture: UILongPressGestureRecognizer) { - switch gesture.state { - case .began: - beginHoldSpeed() - case .ended, .cancelled: - endHoldSpeed() - default: - break - } - } - - private func beginHoldSpeed() { - guard let player = player else { return } - originalRate = player.rate - let holdSpeed = UserDefaults.standard.float(forKey: "holdSpeedPlayer") - player.rate = holdSpeed - } - - private func endHoldSpeed() { - player?.rate = originalRate - } - - func setupAudioSession() { - do { - let audioSession = AVAudioSession.sharedInstance() - try audioSession.setCategory(.playback, mode: .moviePlayback, options: .mixWithOthers) - try audioSession.setActive(true) - - try audioSession.overrideOutputAudioPort(.speaker) - } catch { - print("Failed to set up AVAudioSession: \(error)") - } - } -} diff --git a/Sora/Utils/Player/VideoPlayerView.swift b/Sora/Utils/Player/VideoPlayerView.swift deleted file mode 100644 index 98b388a..0000000 --- a/Sora/Utils/Player/VideoPlayerView.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// VideoPlayerView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import UIKit -import AVKit - -class VideoPlayerViewController: UIViewController { - let module: ModuleStruct - - var player: AVPlayer? - var playerViewController: AVPlayerViewController? - var timeObserverToken: Any? - var streamUrl: String? - var fullUrl: String = "" - - init(module: ModuleStruct) { - self.module = module - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - guard let streamUrl = streamUrl, let url = URL(string: streamUrl) else { - return - } - - var request = URLRequest(url: url) - if streamUrl.contains("ascdn") { - request.addValue("\(module.module[0].details.baseURL)", forHTTPHeaderField: "Referer") - } - - let asset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": request.allHTTPHeaderFields ?? [:]]) - let playerItem = AVPlayerItem(asset: asset) - - player = AVPlayer(playerItem: playerItem) - playerViewController = NormalPlayer() - playerViewController?.player = player - addPeriodicTimeObserver(fullURL: fullUrl) - - if let playerViewController = playerViewController { - playerViewController.view.frame = self.view.frame - self.view.addSubview(playerViewController.view) - self.addChild(playerViewController) - playerViewController.didMove(toParent: self) - } - - let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(fullUrl)") - if lastPlayedTime > 0 { - let seekTime = CMTime(seconds: lastPlayedTime, preferredTimescale: 1) - self.player?.seek(to: seekTime) { _ in - self.player?.play() - } - } else { - self.player?.play() - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - player?.play() - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - player?.pause() - if let timeObserverToken = timeObserverToken { - player?.removeTimeObserver(timeObserverToken) - self.timeObserverToken = nil - } - } - - func addPeriodicTimeObserver(fullURL: String) { - guard let player = self.player else { return } - - let interval = CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) - timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in - guard let currentItem = player.currentItem, - currentItem.duration.seconds.isFinite else { - return - } - - let currentTime = time.seconds - let duration = currentItem.duration.seconds - - UserDefaults.standard.set(currentTime, forKey: "lastPlayedTime_\(fullURL)") - UserDefaults.standard.set(duration, forKey: "totalTime_\(fullURL)") - } - } -} diff --git a/Sora/Views/HomeView.swift b/Sora/Views/HomeView.swift deleted file mode 100644 index 3af0df8..0000000 --- a/Sora/Views/HomeView.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// HomeView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher -import SwiftSoup - -struct HomeView: View { - @StateObject private var modulesManager = ModulesManager() - @State private var featuredItems: [String: [ItemResult]] = [:] - @State private var isLoading = true - - var body: some View { - NavigationView { - ScrollView { - VStack(alignment: .leading) { - if isLoading { - ProgressView("Loading Featured Items...") - .padding() - } else { - ForEach(modulesManager.modules, id: \.name) { module in - if let items = featuredItems[module.name], !items.isEmpty { - VStack(alignment: .leading) { - HStack(alignment: .bottom) { - Text("Featured") - .font(.title2) - .bold() - .padding(.leading) - - Text("on \(module.name)") - .font(.system(size: 15)) - .foregroundColor(.secondary) - .bold() - } - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 20) { - ForEach(items) { item in - NavigationLink(destination: MediaView(module: module, item: item)) { - VStack { - KFImage(URL(string: item.imageUrl)) - .resizable() - .scaledToFill() - .frame(width: 120, height: 180) - .clipped() - .cornerRadius(8) - - Text(item.name) - .font(.caption) - .lineLimit(1) - .foregroundColor(.primary) - } - .frame(width: 120) - .padding(.leading, 5) - } - } - } - .padding(.horizontal) - } - } - .padding(.bottom) - } - } - } - } - .navigationTitle("Home") - } - .onAppear { - if featuredItems.isEmpty { - fetchFeaturedItems() - } - } - } - .navigationViewStyle(StackNavigationViewStyle()) - } - - private func fetchFeaturedItems() { - isLoading = true - let group = DispatchGroup() - - for module in modulesManager.modules { - group.enter() - fetchFeaturedItems(for: module) { items in - DispatchQueue.main.async { - featuredItems[module.name] = items - group.leave() - } - } - } - - group.notify(queue: .main) { - isLoading = false - } - } - - private func fetchFeaturedItems(for module: ModuleStruct, completion: @escaping ([ItemResult]) -> Void) { - let urlString = module.module[0].featured.url - guard let url = URL(string: urlString) else { - completion([]) - return - } - - URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { - completion([]) - return - } - - do { - let html = String(data: data, encoding: .utf8) ?? "" - let document = try SwiftSoup.parse(html) - let elements = try document.select(module.module[0].featured.documentSelector) - - var results: [ItemResult] = [] - for element in elements { - let title = try element.select(module.module[0].featured.title).text() - let href = try element.select(module.module[0].featured.href).attr("href") - var imageURL = try element.select(module.module[0].featured.image.url).attr(module.module[0].featured.image.attribute) - - if imageURL.contains(",") { - imageURL = imageURL.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.first ?? imageURL - } - - if !imageURL.starts(with: "http") { - imageURL = "\(module.module[0].details.baseURL.hasSuffix("/") ? module.module[0].details.baseURL : "\(module.module[0].details.baseURL)/")\(imageURL.hasPrefix("/") ? String(imageURL.dropFirst()) : imageURL)" - } - - imageURL = imageURL.replacingOccurrences(of: " ", with: "%20") - - let result = ItemResult(name: title, imageUrl: imageURL, href: href) - results.append(result) - } - - completion(results) - } catch { - print("Error parsing HTML: \(error)") - Logger.shared.log("Error parsing HTML: \(error)") - completion([]) - } - }.resume() - } -} diff --git a/Sora/Views/LibraryViews/LibraryManager.swift b/Sora/Views/LibraryViews/LibraryManager.swift deleted file mode 100644 index 4833d69..0000000 --- a/Sora/Views/LibraryViews/LibraryManager.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// LibraryManager.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import Foundation - -class LibraryManager: ObservableObject { - static let shared = LibraryManager() - - @Published var libraryItems: [LibraryItem] = [] - private let userDefaults = UserDefaults.standard - private let libraryKey = "LibraryItems" - - private init() { - loadLibrary() - } - - func loadLibrary() { - if let data = userDefaults.data(forKey: libraryKey), - let decoded = try? JSONDecoder().decode([LibraryItem].self, from: data) { - libraryItems = decoded - } - } - - func saveLibrary() { - if let encoded = try? JSONEncoder().encode(libraryItems) { - userDefaults.set(encoded, forKey: libraryKey) - } - } - - func addToLibrary(_ item: LibraryItem) { - if !libraryItems.contains(where: { $0.id == item.id }) { - libraryItems.append(item) - saveLibrary() - Logger.shared.log("Added to library: \(item.title)") - } - } - - func removeFromLibrary(_ item: LibraryItem) { - libraryItems.removeAll(where: { $0.id == item.id }) - saveLibrary() - Logger.shared.log("Removed from library: \(item.title)") - } - - func importFromMiruData(_ miruData: MiruDataStruct, module: ModuleStruct) { - var newLibraryItems: [LibraryItem] = [] - - for like in miruData.likes { - let libraryItem = LibraryItem( - anilistID: like.anilistID, - title: like.title, - image: like.cover, - url: like.gogoSlug, - module: module, - dateAdded: Date() - ) - newLibraryItems.append(libraryItem) - Logger.shared.log("Importing item: \(libraryItem.title)") - } - - DispatchQueue.main.async { - self.libraryItems.append(contentsOf: newLibraryItems) - self.saveLibrary() - Logger.shared.log("Completed importing \(newLibraryItems.count) items") - } - } -} diff --git a/Sora/Views/LibraryViews/LibraryView.swift b/Sora/Views/LibraryViews/LibraryView.swift deleted file mode 100644 index 168eaff..0000000 --- a/Sora/Views/LibraryViews/LibraryView.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// LibraryView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher -import Foundation - -struct LibraryItem: Identifiable, Codable { - var id = UUID() - let anilistID: Int - let title: String - let image: String - let url: String - let module: ModuleStruct - var dateAdded: Date -} - -struct LibraryView: View { - @StateObject private var libraryManager = LibraryManager.shared - - var body: some View { - NavigationView { - ScrollView { - if libraryManager.libraryItems.isEmpty { - emptyLibraryView - } else { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 20) { - ForEach(libraryManager.libraryItems.sorted(by: { $0.dateAdded > $1.dateAdded })) { item in - NavigationLink(destination: MediaView(module: item.module, item: ItemResult(name: item.title, imageUrl: item.image, href: item.url))) { - itemView(item) - } - } - } - .padding() - } - } - .navigationTitle("Library") - } - .navigationViewStyle(StackNavigationViewStyle()) - } - - var emptyLibraryView: some View { - VStack(spacing: 8) { - Image(systemName: "books.vertical") - .font(.system(size: 75)) - .foregroundColor(.secondary) - Text("Your library is empty") - .font(.headline) - .multilineTextAlignment(.center) - Text("Start by adding items you find in the search results or by importing Miru bookmarks from settings!") - .font(.subheadline) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .position(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 3) - } - - func itemView(_ item: LibraryItem) -> some View { - VStack() { - ZStack(alignment: .bottomTrailing) { - KFImage(URL(string: item.image)) - .resizable() - .aspectRatio(2/3, contentMode: .fill) - .cornerRadius(10) - .frame(width: 150, height: 225) - - KFImage(URL(string: item.module.iconURL)) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 30, height: 30) - .background(Color.white) - .clipShape(Circle()) - .padding(5) - } - - Text(item.title) - .font(.subheadline) - .foregroundColor(Color.primary) - .padding([.leading, .bottom], 8) - .lineLimit(1) - } - .contextMenu { - Button(role: .destructive) { - libraryManager.removeFromLibrary(item) - } label: { - Label("Remove from Library", systemImage: "trash") - } - } - } -} diff --git a/Sora/Views/MediaViews/EpisodesCell/CircularProgressBar.swift b/Sora/Views/MediaViews/EpisodesCell/CircularProgressBar.swift deleted file mode 100644 index 57cb55b..0000000 --- a/Sora/Views/MediaViews/EpisodesCell/CircularProgressBar.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// CircularProgressBar.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct CircularProgressBar: View { - var progress: Double - - var body: some View { - ZStack { - Circle() - .stroke(lineWidth: 5.0) - .opacity(0.3) - .foregroundColor(Color.accentColor) - - Circle() - .trim(from: 0.0, to: CGFloat(min(progress, 1.0))) - .stroke(style: StrokeStyle(lineWidth: 5.0, lineCap: .round, lineJoin: .round)) - .foregroundColor(Color.accentColor) - .rotationEffect(Angle(degrees: 270.0)) - .animation(.linear, value: progress) - - if progress >= 0.90 { - Image(systemName: "checkmark") - .font(.system(size: 12)) - } else { - Text(String(format: "%.0f%%", min(progress, 1.0) * 100.0)) - .font(.system(size: 12)) - } - } - } -} diff --git a/Sora/Views/MediaViews/EpisodesCell/EpisodeCell.swift b/Sora/Views/MediaViews/EpisodesCell/EpisodeCell.swift deleted file mode 100644 index c384f49..0000000 --- a/Sora/Views/MediaViews/EpisodesCell/EpisodeCell.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// EpisodeCell.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher -import SwiftyJSON - -struct EpisodeCell: View { - let episode: String - let episodeID: Int - let imageUrl: String - let progress: Double - let itemID: Int - - @State private var episodeTitle: String = "" - @State private var episodeImageUrl: String = "" - @State private var isLoading: Bool = true - - var body: some View { - HStack { - ZStack { - KFImage(URL(string: episodeImageUrl.isEmpty ? "https://raw.githubusercontent.com/cranci1/Sora/refs/heads/main/assets/banner2.png" : episodeImageUrl)) - .resizable() - .aspectRatio(16/9, contentMode: .fill) - .frame(width: 100, height: 56) - .cornerRadius(8) - - if isLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - } - } - - VStack(alignment: .leading) { - Text("Episode \(episodeID + 1)") - .font(.system(size: 15)) - if !episodeTitle.isEmpty { - Text(episodeTitle) - .font(.system(size: 13)) - .foregroundColor(.secondary) - } - } - - Spacer() - - CircularProgressBar(progress: progress) - .frame(width: 40, height: 40) - } - .onAppear { - fetchEpisodeDetails() - } - } - - func fetchEpisodeDetails() { - let cacheKey = "episodeDetails_\(itemID)_\(episodeID)" - - if let cachedData = UserDefaults.standard.data(forKey: cacheKey) { - parseEpisodeDetails(data: cachedData) - return - } - - guard let url = URL(string: "https://api.ani.zip/mappings?anilist_id=\(itemID)") else { - isLoading = false - return - } - - URLSession.custom.dataTask(with: url) { data, _, error in - if let error = error { - print("Failed to fetch episode details: \(error)") - DispatchQueue.main.async { - self.isLoading = false - } - return - } - - guard let data = data else { - print("No data received") - DispatchQueue.main.async { - self.isLoading = false - } - return - } - - UserDefaults.standard.set(data, forKey: cacheKey) - self.parseEpisodeDetails(data: data) - }.resume() - } - - func parseEpisodeDetails(data: Data) { - do { - let json = try JSON(data: data) - guard let episodeDetails = json["episodes"]["\(episodeID + 1)"].dictionary, - let title = episodeDetails["title"]?.dictionary, - let image = episodeDetails["image"]?.string else { - print("Invalid response format") - DispatchQueue.main.async { - self.isLoading = false - } - return - } - - DispatchQueue.main.async { - self.episodeTitle = title["en"]?.string ?? "" - self.episodeImageUrl = image - self.isLoading = false - } - } catch { - print("Failed to parse JSON: \(error)") - DispatchQueue.main.async { - self.isLoading = false - } - } - } -} diff --git a/Sora/Views/MediaViews/MediaExtraction.swift b/Sora/Views/MediaViews/MediaExtraction.swift deleted file mode 100644 index accb81f..0000000 --- a/Sora/Views/MediaViews/MediaExtraction.swift +++ /dev/null @@ -1,403 +0,0 @@ -// -// MediaExtraction.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import SwiftSoup - -extension MediaView { - func fetchItemDetails() { - guard let url = URL(string: item.href.hasPrefix("https") ? item.href : "\(module.module[0].details.baseURL.hasSuffix("/") ? module.module[0].details.baseURL : "\(module.module[0].details.baseURL)/")\(item.href.hasPrefix("/") ? String(item.href.dropFirst()) : item.href)") else { return } - - URLSession.custom.dataTask(with: url) { data, _, error in - defer { isLoading = false } - guard let data = data, error == nil else { return } - - do { - let html = String(data: data, encoding: .utf8) ?? "" - let document = try SwiftSoup.parse(html) - - let details = module.module[0].details - let episodes = module.module[0].episodes - - let aliases = (try? document.select(details.aliases.selector).attr(details.aliases.attribute)) ?? "" - let synopsis = (try? document.select(details.synopsis).text()) ?? "" - let airdate = (try? document.select(details.airdate).text()) ?? "" - let stars = (try? document.select(details.stars).text()) ?? "" - - let episodeElements = try document.select(episodes.selector) - var episodeList = (try? episodeElements.map { try $0.attr("href") }) ?? [] - - if module.module[0].episodes.order == "reversed" { - episodeList.reverse() - } - - DispatchQueue.main.async { - self.aliases = aliases - self.synopsis = synopsis - self.airdate = airdate - self.stars = stars - self.episodes = episodeList - } - } catch { - print("Error parsing HTML: \(error)") - Logger.shared.log("Error parsing HTML: \(error)") - } - }.resume() - } - - func fetchEpisodeStream(urlString: String) { - guard var url = URL(string: urlString.hasPrefix("https") ? urlString : "\(module.module[0].details.baseURL)\(urlString)") else { return } - - Logger.shared.log("Pressed episode button") - - let dispatchGroup = DispatchGroup() - - let pageRedirects = module.module[0].details.pageRedirects ?? false - - - if pageRedirects { - dispatchGroup.enter() // Start tracking the redirect task - URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { - dispatchGroup.leave() // End tracking if there's an error - return - } - - let html = String(data: data, encoding: .utf8) ?? "" - let redirectedUrl = extractFromRedirectURL(from: html) - if let redirect = redirectedUrl, let newURL = URL(string: redirect) { - url = newURL - } - dispatchGroup.leave() // End tracking after successful execution - }.resume() - } - - dispatchGroup.notify(queue: .main) { // This block executes after all tasks - URLSession.custom.dataTask(with: url) { data, _, error in - guard let data = data, error == nil else { return } - - let html = String(data: data, encoding: .utf8) ?? "" - - - let streamType = module.stream - let streamURLs = extractStreamURLs(from: html, streamType: streamType) - - if module.extractor == "dub-sub" { - Logger.shared.log("extracting for dub-sub") - let dubSubURLs = extractDubSubURLs(from: html) - let subURLs = dubSubURLs.filter { $0.type == "SUB" }.map { $0.url } - let dubURLs = dubSubURLs.filter { $0.type == "DUB" }.map { $0.url } - - if !subURLs.isEmpty || !dubURLs.isEmpty { - DispatchQueue.main.async { - self.presentStreamSelection(subURLs: subURLs, dubURLs: dubURLs, fullURL: urlString) - } - } else { - DispatchQueue.main.async { - self.playStream(urlString: streamURLs.first, fullURL: urlString) - } - } - } else if module.extractor == "pattern-mp4" || module.extractor == "pattern-HLS" { - Logger.shared.log("extracting for pattern-mp4/hls") - let patternURL = extractPatternURL(from: html) - guard let patternURL = patternURL else { return } - - URLSession.custom.dataTask(with: patternURL) { data, _, error in - guard let data = data, error == nil else { return } - - let patternHTML = String(data: data, encoding: .utf8) ?? "" - let mp4URLs = extractStreamURLs(from: patternHTML, streamType: streamType).map { $0.replacingOccurrences(of: "amp;", with: "") } - - DispatchQueue.main.async { - self.playStream(urlString: mp4URLs.first, fullURL: urlString) - } - }.resume() - } else if module.extractor == "pattern" { - Logger.shared.log("extracting for pattern") - let patternURL = extractPatternURL(from: html) - - DispatchQueue.main.async { - self.playStream(urlString: patternURL?.absoluteString, fullURL: urlString) - } - } else if module.extractor == "voe" { - Logger.shared.log("extracting for voe") - - let voeUrl = extractVoeStream(from: html) - - DispatchQueue.main.async { - self.playStream(urlString: voeUrl?.absoluteString, fullURL: urlString) - } - - } else { - DispatchQueue.main.async { - self.playStream(urlString: streamURLs.first, fullURL: urlString) - } - } - }.resume() - } - } - - func extractStreamURLs(from html: String, streamType: String) -> [String] { - let pattern: String - switch streamType { - case "HLS": - pattern = #"https:\/\/[^"\s<>]+\.m3u8(?:\?[^\s"'<>]+)?"# - case "MP4": - pattern = #"https:\/\/[^"\s<>]+\.mp4(?:\?[^\s"'<>]+)?"# - default: - return [] - } - - do { - Logger.shared.log(streamType) - let regex = try NSRegularExpression(pattern: pattern, options: []) - let matches = regex.matches(in: html, options: [], range: NSRange(html.startIndex..., in: html)) - return matches.compactMap { - Range($0.range, in: html).map { String(html[$0]) } - } - } catch { - print("Invalid regex: \(error)") - Logger.shared.log("Invalid regex: \(error)") - return [] - } - } - - func extractPatternURL(from html: String) -> URL? { - var pattern = module.module[0].episodes.pattern - - if module.extractor == "pattern" { - if let data = Data(base64Encoded: pattern), let decodedPattern = String(data: data, encoding: .utf8) { - pattern = decodedPattern - } else { - print("Failed to decode base64 pattern") - Logger.shared.log("Failed to decode base64 pattern") - return nil - } - } - - do { - let regex = try NSRegularExpression(pattern: pattern, options: []) - let range = NSRange(html.startIndex.. [(type: String, url: String)] { - let pattern = #""type":"(SUB|DUB)","url":"(.*?\.m3u8)""# - - guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { - return [] - } - - let range = NSRange(htmlContent.startIndex..., in: htmlContent) - let matches = regex.matches(in: htmlContent, range: range) - - return matches.compactMap { match in - if match.numberOfRanges == 3, - let typeRange = Range(match.range(at: 1), in: htmlContent), - let urlRange = Range(match.range(at: 2), in: htmlContent) { - let type = String(htmlContent[typeRange]) - let urlString = String(htmlContent[urlRange]).replacingOccurrences(of: "\\/", with: "/") - Logger.shared.log(urlString) - return (type, urlString) - } - return nil - } - } - - /// Grabs hls stream from voe sites - func extractVoeStream(from html: String) -> URL? { - - let hlsPattern = "'hls': '(.*?)'" - guard let regex = try? NSRegularExpression(pattern: hlsPattern, options: []) else { return nil } - let range = NSRange(html.startIndex..., in: html) - if let match = regex.firstMatch(in: html, options: [], range: range), - let matchRange = Range(match.range(at: 1), in: html) { - let base64Hls = String(html[matchRange]) - guard let data = Data(base64Encoded: base64Hls), - let decodedURLString = String(data: data, encoding: .utf8) - else { return nil } - return URL(string: decodedURLString) - } - return nil - } - - - func presentStreamSelection(subURLs: [String], dubURLs: [String], fullURL: String) { - let uniqueSubURLs = Array(Set(subURLs)) - let uniqueDubURLs = Array(Set(dubURLs)) - - if uniqueSubURLs.count == 1 && uniqueDubURLs.isEmpty { - self.playStream(urlString: uniqueSubURLs.first, fullURL: fullURL) - return - } - - if uniqueDubURLs.count == 1 && uniqueSubURLs.isEmpty { - self.playStream(urlString: uniqueDubURLs.first, fullURL: fullURL) - return - } - - let alert = UIAlertController(title: "Select Stream", message: "Choose the audio type", preferredStyle: .actionSheet) - - if !uniqueDubURLs.isEmpty { - for dubURL in uniqueDubURLs { - alert.addAction(UIAlertAction(title: "DUB", style: .default) { _ in - self.playStream(urlString: dubURL, fullURL: fullURL) - }) - } - } - - if !uniqueSubURLs.isEmpty { - for subURL in uniqueSubURLs { - alert.addAction(UIAlertAction(title: "SUB", style: .default) { _ in - self.playStream(urlString: subURL, fullURL: fullURL) - }) - } - } - - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - - DispatchQueue.main.async { - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootVC = windowScene.windows.first?.rootViewController { - if let popoverController = alert.popoverPresentationController { - popoverController.sourceView = rootVC.view - popoverController.sourceRect = CGRect(x: rootVC.view.bounds.midX, y: rootVC.view.bounds.midY, width: 0, height: 0) - popoverController.permittedArrowDirections = [] - } - rootVC.present(alert, animated: true, completion: nil) - } - } - } - - - /// Extracts the URL from a redirect page - /// Example: href="/redirect/1234567" -> https://baseUrl.com/redirect/1234567 - func extractFromRedirectURL(from html: String) -> String? { - - let pattern = #"href="\/redirect\/\d+""# - - do { - let regex = try NSRegularExpression(pattern: pattern, options: []) - let range = NSRange(html.startIndex.. String? { - let semaphore = DispatchSemaphore(value: 0) // To block the thread until the task completes - var redirectedURLString: String? - - var request = URLRequest(url: url) - request.httpMethod = "HEAD" // Use HEAD to get only headers - - let delegate = RedirectHandler() - let sessionConfig = URLSessionConfiguration.default - let session = URLSession(configuration: sessionConfig, delegate: delegate, delegateQueue: nil) - - session.dataTask(with: request) { _, response, error in - // Extract httpResponse as a standalone variable - guard let httpResponse = response as? HTTPURLResponse else { - Logger.shared.log("Invalid response for URL: \(url)") - semaphore.signal() - return - } - - // Process the httpResponse for redirection logic - if (httpResponse.statusCode == 301 || httpResponse.statusCode == 302), - let location = httpResponse.value(forHTTPHeaderField: "Location"), - let redirectedURL = URL(string: location) { - redirectedURLString = redirectedURL.absoluteString - Logger.shared.log("Redirected URL: \(redirectedURLString ?? "nil")") - } else { - if let error = error { - Logger.shared.log("Error fetching redirected URL: \(error.localizedDescription)") - } else { - Logger.shared.log("No redirection for URL: \(url)") - } - } - semaphore.signal() // Signal the semaphore to resume execution - }.resume() - - semaphore.wait() // Wait for the network task to complete - - if redirectedURLString?.contains("voe.sx") == true { - return voeUrlHandler(url: URL(string: redirectedURLString!)!) - } - else { - return redirectedURLString - } - - } - - /// Voe uses a custom handler to extract the video URL from the page - /// The site uses windows.location.href to redirect to the video page, usally another domain but with the same path - /// The replacement URL is hardcoded right now TODO: Make it dynamic - func voeUrlHandler(url: URL) -> String? { - - let urlString = url.absoluteString - - // Check if the URL is a voe.sx URL - guard urlString.contains("voe.sx") else { - Logger.shared.log("Not a voe.sx URL") - return nil - } - - // Extract the path from the URL and append it to the hardcoded replacement URL - // Example: https://voe.sx/e/1234567 -> /e/1234567 - let hardcodedURL = "https://sandratableother.com" - let finishedUrl = urlString.replacingOccurrences(of: "https://voe.sx", with: hardcodedURL) - - return finishedUrl - } - -} - -/// Custom handler to handle HTTP redirections and prevent them -class RedirectHandler: NSObject, URLSessionDelegate, URLSessionTaskDelegate { - func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping @Sendable (URLRequest?) -> Void - ) { - completionHandler(nil) - } -} diff --git a/Sora/Views/MediaViews/MediaView.swift b/Sora/Views/MediaViews/MediaView.swift deleted file mode 100644 index b53d50f..0000000 --- a/Sora/Views/MediaViews/MediaView.swift +++ /dev/null @@ -1,376 +0,0 @@ -// -// MediaView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import AVKit -import SwiftUI -import Kingfisher -import SwiftyJSON -import SafariServices - -struct MediaView: View { - let module: ModuleStruct - let item: ItemResult - - @State var aliases: String = "" - @State var synopsis: String = "" - @State var airdate: String = "" - @State var stars: String = "" - @State var episodes: [String] = [] - @State var isLoading: Bool = true - @State var showFullSynopsis: Bool = false - @State var itemID: Int? - @State private var selectedEpisode: String = "" - @State private var selectedEpisodeNumber: Int = 0 - @State private var episodeRange: ClosedRange = 0...99 - @State private var selectedRange: String = "1-100" - - @AppStorage("externalPlayer") private var externalPlayer: String = "Default" - @StateObject private var libraryManager = LibraryManager.shared - - var body: some View { - Group { - if isLoading { - ProgressView() - .padding() - } else { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - HStack(alignment: .top, spacing: 10) { - KFImage(URL(string: item.imageUrl)) - .resizable() - .aspectRatio(2/3, contentMode: .fill) - .cornerRadius(10) - .frame(width: 150, height: 225) - - VStack(alignment: .leading, spacing: 4) { - Text(item.name) - .font(.system(size: 17)) - .fontWeight(.bold) - - if !aliases.isEmpty && aliases != item.name { - Text(aliases) - .font(.system(size: 13)) - .foregroundColor(.secondary) - } - - Spacer() - - HStack(alignment: .center, spacing: 12) { - Text(module.name) - .font(.system(size: 13)) - .padding(4) - .background(Capsule().fill(Color.accentColor.opacity(0.4))) - - Button(action: { - }) { - Image(systemName: "ellipsis.circle") - .resizable() - .frame(width: 20, height: 20) - } - - Button(action: { - openSafariViewController(with: "\(module.module[0].details.baseURL)") - }) { - Image(systemName: "safari") - .resizable() - .frame(width: 20, height: 20) - } - } - } - } - - if !synopsis.isEmpty { - VStack(alignment: .leading, spacing: 2) { - HStack(alignment: .center) { - Text("Synopsis") - .font(.system(size: 18)) - .fontWeight(.bold) - - Spacer() - - Button(action: { - showFullSynopsis.toggle() - }) { - Text(showFullSynopsis ? "Less" : "More") - .font(.system(size: 14)) - } - } - - Text(synopsis) - .lineLimit(showFullSynopsis ? nil : 4) - .font(.system(size: 14)) - } - } - - HStack { - Button(action: { - startWatchingFirstUnfinishedEpisode() - }) { - HStack { - Image(systemName: "play.fill") - .foregroundColor(.primary) - Text("Start Watching") - .font(.headline) - .foregroundColor(.primary) - } - .padding() - .frame(maxWidth: .infinity) - .background(Color.accentColor) - .cornerRadius(10) - } - - Button(action: { - if isItemInLibrary() { - removeFromLibrary() - } else { - addToLibrary() - } - }) { - Image(systemName: isItemInLibrary() ? "bookmark.fill" : "bookmark") - .resizable() - .frame(width: 20, height: 27) - } - } - - if !episodes.isEmpty { - VStack(alignment: .leading, spacing: 10) { - HStack { - Text("Episodes") - .font(.system(size: 18)) - .fontWeight(.bold) - - Spacer() - - if episodes.count > 100 { - Menu { - ForEach(0..<(episodes.count / 100) + 1, id: \.self) { index in - let start = index * 100 + 1 - let end = min((index + 1) * 100, episodes.count) - Button(action: { - episodeRange = (start - 1)...(end - 1) - selectedRange = "\(start)-\(end)" - }) { - Text("\(start)-\(end)") - } - } - } label: { - Text(selectedRange) - .font(.system(size: 14)) - } - } - } - - ForEach(episodeRange, id: \.self) { index in - if index < episodes.count { - let episodeURL = episodes[index].hasPrefix("https") ? episodes[index] : "\(module.module[0].details.baseURL)\(episodes[index])" - let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(episodeURL)") - let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(episodeURL)") - let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0 - - EpisodeCell(episode: episodes[index], episodeID: index, imageUrl: item.imageUrl, progress: progress, itemID: itemID ?? 0) - .onTapGesture { - selectedEpisode = episodes[index] - selectedEpisodeNumber = index + 1 - fetchEpisodeStream(urlString: episodeURL) - } - } - } - } - } - } - .padding() - .navigationBarTitleDisplayMode(.inline) - .navigationBarTitle(item.name) - .navigationViewStyle(StackNavigationViewStyle()) - } - } - } - .onAppear { - fetchItemDetails() - fetchItemID(byTitle: item.name) { result in - switch result { - case .success(let id): - itemID = id - Logger.shared.log("Fetched Item ID: \(id)") - case .failure(let error): - print("Failed to fetch Item ID: \(error)") - Logger.shared.log("Failed to fetch Item ID: \(error)") - } - } - } - } - - func isItemInLibrary() -> Bool { - return libraryManager.libraryItems.contains(where: { $0.url == item.href }) - } - - func addToLibrary() { - let libraryItem = LibraryItem( - anilistID: itemID ?? 0, - title: item.name, - image: item.imageUrl, - url: item.href, - module: module, - dateAdded: Date() - ) - libraryManager.addToLibrary(libraryItem) - } - - func removeFromLibrary() { - if let libraryItem = libraryManager.libraryItems.first(where: { $0.url == item.href }) { - libraryManager.removeFromLibrary(libraryItem) - } - } - - private func startWatchingFirstUnfinishedEpisode() { - for (index, episode) in episodes.enumerated() { - let episodeURL = episode.hasPrefix("https") ? episode : "\(module.module[0].details.baseURL)\(episode)" - let totalTime = UserDefaults.standard.double(forKey: "totalTime_\(episodeURL)") - let lastPlayedTime = UserDefaults.standard.double(forKey: "lastPlayedTime_\(episodeURL)") - let progress = totalTime > 0 ? lastPlayedTime / totalTime : 0 - - if progress < 0.90 { - selectedEpisode = episode - selectedEpisodeNumber = index + 1 - fetchEpisodeStream(urlString: episodeURL) - break - } - } - } - - func playStream(urlString: String?, fullURL: String) { - guard let streamUrl = urlString else { return } - - if externalPlayer == "Infuse" || externalPlayer == "VLC" || externalPlayer == "OutPlayer" || externalPlayer == "nPlayer" { - var scheme: String - switch externalPlayer { - case "Infuse": - scheme = "infuse://x-callback-url/play?url=" - case "VLC": - scheme = "vlc://" - case "OutPlayer": - scheme = "outplayer://" - case "nPlayer": - scheme = "nplayer-" - default: - scheme = "" - } - openInExternalPlayer(scheme: scheme, url: streamUrl) - Logger.shared.log("Opening external app with scheme: \(scheme)") - return - } else if externalPlayer == "Sora" { - DispatchQueue.main.async { - let customMediaPlayer = CustomMediaPlayer( - module: module, - urlString: streamUrl, - fullUrl: fullURL, - title: item.name, - episodeNumber: selectedEpisodeNumber, - onWatchNext: { - selectNextEpisode() - } - ) - let hostingController = UIHostingController(rootView: customMediaPlayer) - hostingController.modalPresentationStyle = .fullScreen - Logger.shared.log("Opening custom media player with url: \(streamUrl)") - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(hostingController, animated: true, completion: nil) - } - } - return - } - - DispatchQueue.main.async { - let videoPlayerViewController = VideoPlayerViewController(module: module) - videoPlayerViewController.streamUrl = streamUrl - videoPlayerViewController.fullUrl = fullURL - videoPlayerViewController.modalPresentationStyle = .fullScreen - Logger.shared.log("Opening video player with url: \(streamUrl)") - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(videoPlayerViewController, animated: true, completion: nil) - } - } - } - - private func selectNextEpisode() { - guard let currentEpisodeIndex = episodes.firstIndex(of: selectedEpisode) else { return } - let nextEpisodeIndex = currentEpisodeIndex + 1 - if nextEpisodeIndex < episodes.count { - selectedEpisode = episodes[nextEpisodeIndex] - selectedEpisodeNumber = nextEpisodeIndex + 1 - let nextEpisodeURL = "\(module.module[0].details.baseURL)\(episodes[nextEpisodeIndex])" - fetchEpisodeStream(urlString: nextEpisodeURL) - } - } - - private func openSafariViewController(with urlString: String) { - guard let url = URL(string: item.href.hasPrefix("https") ? item.href : "\(module.module[0].details.baseURL.hasSuffix("/") ? module.module[0].details.baseURL : "\(module.module[0].details.baseURL)/")\(item.href.hasPrefix("/") ? String(item.href.dropFirst()) : item.href)") else { - Logger.shared.log("Unable to open the webpage") - return - } - let safariViewController = SFSafariViewController(url: url) - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootVC = windowScene.windows.first?.rootViewController { - rootVC.present(safariViewController, animated: true, completion: nil) - } - } - - private func openInExternalPlayer(scheme: String, url: String) { - guard let streamUrl = URL(string: "\(scheme)\(url)") else { - Logger.shared.log("Unable to open the stream: '\(scheme)\(url)'") - return - } - UIApplication.shared.open(streamUrl, options: [:], completionHandler: nil) - Logger.shared.log("Unable to open the stream: 'streamUrl'") - } - - func fetchItemID(byTitle title: String, completion: @escaping (Result) -> Void) { - let query = """ - query { - Media(search: "\(title)", type: ANIME) { - id - } - } - """ - - guard let url = URL(string: "https://graphql.anilist.co") else { - completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))) - return - } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let parameters: [String: Any] = ["query": query] - request.httpBody = try? JSONSerialization.data(withJSONObject: parameters) - - URLSession.custom.dataTask(with: request) { data, _, error in - if let error = error { - completion(.failure(error)) - return - } - - guard let data = data else { - completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"]))) - return - } - - let json = JSON(data) - if let id = json["data"]["Media"]["id"].int { - completion(.success(id)) - } else { - let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid response"]) - completion(.failure(error)) - } - }.resume() - } -} diff --git a/Sora/Views/SearchViews/SearchResultsView.swift b/Sora/Views/SearchViews/SearchResultsView.swift deleted file mode 100644 index 0ce4eef..0000000 --- a/Sora/Views/SearchViews/SearchResultsView.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// SearchResultsView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import SwiftSoup -import Kingfisher - -struct SearchResultsView: View { - let module: ModuleStruct? - let searchText: String - @State private var searchResults: [ItemResult] = [] - @State private var isLoading: Bool = true - @State private var filter: FilterType = .all - @AppStorage("listSearch") private var isListSearchEnabled: Bool = false - - enum FilterType: String, CaseIterable { - case all = "All" - case dub = "Dub" - case sub = "Sub" - case ova = "OVA" - case ona = "ONA" - case movie = "Movie" - } - - var body: some View { - if isListSearchEnabled { - oldUI - } else { - modernUI - } - } - - var modernUI: some View { - VStack { - if isLoading { - ProgressView() - .padding() - } else if searchResults.isEmpty { - Text("No results found") - .foregroundColor(.secondary) - .padding() - } else { - ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 20) { - ForEach(filteredResults) { result in - NavigationLink(destination: MediaView(module: module!, item: result)) { - VStack { - KFImage(URL(string: result.imageUrl)) - .resizable() - .aspectRatio(2/3, contentMode: .fill) - .cornerRadius(10) - .frame(width: 150, height: 225) - - Text(result.name) - .font(.subheadline) - .foregroundColor(Color.primary) - .padding([.leading, .bottom], 8) - .lineLimit(1) - } - } - } - } - .padding() - } - .navigationViewStyle(StackNavigationViewStyle()) - .navigationTitle("Results") - .toolbar { - filterMenu - } - } - } - .onAppear { - performSearch() - } - } - - var oldUI: some View { - VStack { - if isLoading { - ProgressView() - .padding() - } else if searchResults.isEmpty { - Text("No results found") - .foregroundColor(.secondary) - .padding() - } else { - List { - ForEach(filteredResults) { result in - NavigationLink(destination: MediaView(module: module!, item: result)) { - HStack { - KFImage(URL(string: result.imageUrl)) - .resizable() - .scaledToFill() - .frame(width: 100, height: 150) - .clipped() - - VStack(alignment: .leading) { - Text(result.name) - .font(.system(size: 16)) - .padding(.leading, 10) - } - } - .padding(.vertical, 5) - } - } - } - .navigationTitle("Results") - .toolbar { - filterMenu - } - } - } - .navigationViewStyle(StackNavigationViewStyle()) - .onAppear { - performSearch() - } - } - - var filterMenu: some View { - Menu { - ForEach([FilterType.all], id: \.self) { filter in - Button(action: { - self.filter = filter - performSearch() - }) { - Label(filter.rawValue, systemImage: self.filter == filter ? "checkmark" : "") - } - } - Menu("Audio") { - ForEach([FilterType.dub, FilterType.sub], id: \.self) { filter in - Button(action: { - self.filter = filter - performSearch() - }) { - Label(filter.rawValue, systemImage: self.filter == filter ? "checkmark" : "") - } - } - } - Menu("Format") { - ForEach([FilterType.ova, FilterType.ona, FilterType.movie], id: \.self) { filter in - Button(action: { - self.filter = filter - performSearch() - }) { - Label(filter.rawValue, systemImage: self.filter == filter ? "checkmark" : "") - } - } - } - } label: { - Label("Filter", systemImage: filter == .all ? "line.horizontal.3.decrease.circle" : "line.horizontal.3.decrease.circle.fill") - } - } - - var filteredResults: [ItemResult] { - switch filter { - case .all: - return searchResults - case .dub: - return searchResults.filter { $0.name.contains("Dub") || $0.name.contains("ITA") } - case .sub: - return searchResults.filter { !$0.name.contains("Dub") && !$0.name.contains("ITA") } - case .ova, .ona: - return searchResults.filter { $0.name.contains(filter.rawValue) } - case .movie: - return searchResults.filter { $0.name.contains("Movie") || $0.name.contains("Film") } - } - } - - func performSearch() { - guard let module = module, !searchText.isEmpty else { return } - - let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? searchText - let parameter = module.module[0].search.parameter - let urlString: String - - if parameter == "blank" { - urlString = "\(module.module[0].search.url)\(encodedSearchText)" - } else { - urlString = "\(module.module[0].search.url)?\(parameter)=\(encodedSearchText)" - } - - guard let url = URL(string: urlString) else { return } - - URLSession.custom.dataTask(with: url) { data, _, error in - defer { isLoading = false } - guard let data = data, error == nil else { return } - - do { - let html = String(data: data, encoding: .utf8) ?? "" - let document = try SwiftSoup.parse(html) - let elements = try document.select(module.module[0].search.documentSelector) - - var results: [ItemResult] = [] - for element in elements { - let title = try element.select(module.module[0].search.title).text() - let href = try element.select(module.module[0].search.href).attr("href") - var imageURL = try element.select(module.module[0].search.image.url).attr(module.module[0].search.image.attribute) - - if imageURL.contains(",") { - imageURL = imageURL.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.first ?? imageURL - } - - if !imageURL.starts(with: "http") { - imageURL = "\(module.module[0].details.baseURL.hasSuffix("/") ? module.module[0].details.baseURL : "\(module.module[0].details.baseURL)/")\(imageURL.hasPrefix("/") ? String(imageURL.dropFirst()) : imageURL)" - } - - imageURL = imageURL.replacingOccurrences(of: " ", with: "%20") - - // If imageURL is not available or is the same as the baseURL, use a default image - if imageURL.isEmpty || imageURL == module.module[0].details.baseURL + "/" { - imageURL = "https://s4.anilist.co/file/anilistcdn/character/large/default.jpg" - } - - let result = ItemResult(name: title, imageUrl: imageURL, href: href) - results.append(result) - } - - // Filter out non-searchable modules - if module.module[0].search.searchable == false { - results = results.filter { $0.name.lowercased().contains(searchText.lowercased()) } - } - - DispatchQueue.main.async { - self.searchResults = results - } - } catch { - print("Error parsing HTML: \(error)") - Logger.shared.log("Error parsing HTML: \(error)") - } - }.resume() - } -} diff --git a/Sora/Views/SearchViews/SearchView.swift b/Sora/Views/SearchViews/SearchView.swift deleted file mode 100644 index 441e655..0000000 --- a/Sora/Views/SearchViews/SearchView.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// SearchView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct ItemResult: Identifiable { - let id = UUID() - let name: String - let imageUrl: String - let href: String -} - -struct SearchView: View { - @State private var searchText: String = "" - @State private var searchResults: [ItemResult] = [] - @State private var navigateToResults: Bool = false - @State private var selectedModule: ModuleStruct? - @State private var showAlert = false - @StateObject private var modulesManager = ModulesManager() - @ObservedObject private var searchHistoryManager = HistoryManager() - - var body: some View { - NavigationView { - VStack { - HStack { - Menu { - ForEach(modulesManager.modules, id: \.name) { module in - Button(action: { - selectedModule = module - }) { - Text(module.name) - } - } - } label: { - Label(selectedModule?.name ?? "Select Module", systemImage: "chevron.down") - } - - Spacer() - - SearchBar(text: $searchText, onSearchButtonClicked: { - if let _ = selectedModule, !searchText.isEmpty { - searchHistoryManager.addSearchHistory(searchText) - navigateToResults = true - } else { - showAlert = true - Logger.shared.log("No Module is selected for the search") - } - }) - } - .padding(.horizontal) - - List { - if !searchHistoryManager.searchHistory.isEmpty { - Section(header: Text("Search History")) { - ForEach(searchHistoryManager.searchHistory, id: \.self) { historyItem in - Button(action: { - searchText = historyItem - if let _ = selectedModule, !searchText.isEmpty { - navigateToResults = true - } else { - showAlert = true - Logger.shared.log("No Module is selected for the search") - } - }) { - Text(historyItem) - .foregroundColor(.primary) - } - } - .onDelete(perform: searchHistoryManager.deleteHistoryItem) - } - } - } - .navigationTitle("Search") - .onSubmit(of: .search) { - if let _ = selectedModule, !searchText.isEmpty { - navigateToResults = true - } else { - showAlert = true - Logger.shared.log("No Module is selected for the search") - } - } - - NavigationLink( - destination: SearchResultsView(module: selectedModule, searchText: searchText), - isActive: $navigateToResults, - label: { - EmptyView() - } - ) - .hidden() - } - .onAppear { - modulesManager.loadModules() - NotificationCenter.default.addObserver(forName: .moduleAdded, object: nil, queue: .main) { _ in - modulesManager.loadModules() - } - NotificationCenter.default.addObserver(forName: .moduleRemoved, object: nil, queue: .main) { _ in - modulesManager.loadModules() - } - } - .alert(isPresented: $showAlert) { - Alert( - title: Text("No module selected"), - message: Text("Please select a module before searching."), - dismissButton: .default(Text("OK")) - ) - } - } - .navigationViewStyle(StackNavigationViewStyle()) - } -} - -struct SearchBar: View { - @Binding var text: String - var onSearchButtonClicked: () -> Void - - var body: some View { - HStack { - TextField("Search...", text: $text, onCommit: onSearchButtonClicked) - .padding(7) - .padding(.horizontal, 25) - .background(Color(.systemGray6)) - .cornerRadius(8) - .overlay( - HStack { - Image(systemName: "magnifyingglass") - .foregroundColor(.gray) - .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - .padding(.leading, 8) - - if !text.isEmpty { - Button(action: { - self.text = "" - }) { - Image(systemName: "multiply.circle.fill") - .foregroundColor(.gray) - .padding(.trailing, 8) - } - } - } - ) - .padding(.horizontal, 10) - } - } -} diff --git a/Sora/Views/SettingsViews/SettingView.swift b/Sora/Views/SettingsViews/SettingView.swift deleted file mode 100644 index de3c813..0000000 --- a/Sora/Views/SettingsViews/SettingView.swift +++ /dev/null @@ -1,303 +0,0 @@ -// -// SettingView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher -import UniformTypeIdentifiers - -struct SettingsView: View { - @EnvironmentObject var settings: Settings - @State private var isDocumentPickerPresented = false - @State private var showImportSuccessAlert = false - @State private var showImportFailAlert = false - @State private var importErrorMessage = "" - @State private var miruDataToImport: MiruDataStruct? - @State private var selectedModule: ModuleStruct? - @StateObject private var libraryManager = LibraryManager.shared - @StateObject private var modulesManager = ModulesManager() - - var body: some View { - NavigationView { - Form { - Section(header: Text("Interface")) { - ColorPicker("Accent Color", selection: $settings.accentColor) - HStack() { - Text("Appearance") - Picker("Appearance", selection: $settings.selectedAppearance) { - Text("System").tag(Appearance.system) - Text("Light").tag(Appearance.light) - Text("Dark").tag(Appearance.dark) - } - .pickerStyle(SegmentedPickerStyle()) - } - NavigationLink(destination: SettingsIUView()) { - Text("Interface Settings") - } - NavigationLink(destination: SettingsPlayerView()) { - Text("Media Player") - } - } - - Section(header: Text("External Features")) { - NavigationLink(destination: SettingsModuleView()) { - HStack { - Image(systemName: "puzzlepiece.fill") - Text("Modules") - } - } - NavigationLink(destination: SettingsStorageView()) { - HStack { - Image(systemName: "externaldrive.fill") - Text("Storage") - } - } - ForEach(modulesManager.modules.filter { $0.extractor == "dub-sub" }, id: \.name) { module in - Button(action: { - isDocumentPickerPresented = true - selectedModule = module - }) { - HStack { - Image(systemName: "tray.and.arrow.down.fill") - Text("Import Miru Bookmarks into \(module.name)") - } - } - } - } - - Section(header: Text("Debug")) { - NavigationLink(destination: SettingsLogsView()) { - HStack { - Image(systemName: "doc.text.fill") - Text("Logs") - } - } - NavigationLink(destination: SettingsEditorView(modulesManager: ModulesManager())) { - HStack { - Image(systemName: "pencil.and.outline") - Text("Modules Editor") - } - } - } - - Section(header: Text("Info")) { - NavigationLink(destination: AboutView()) { - Text("About") - } - NavigationLink(destination: SettingsReleasesView()) { - Text("Releases") - } - Button(action: { - if let url = URL(string: "https://github.com/cranci1/Sora") { - UIApplication.shared.open(url) - } - }) { - HStack { - Text("Sora github repo") - .foregroundColor(.primary) - Spacer() - Image(systemName: "safari") - .foregroundColor(.secondary) - } - } - Button(action: { - if let url = URL(string: "https://github.com/cranci1/Sora/issues") { - UIApplication.shared.open(url) - } - }) { - HStack { - Text("Report an issue") - .foregroundColor(.primary) - Spacer() - Image(systemName: "safari") - .foregroundColor(.secondary) - } - } - Button(action: { - if let url = URL(string: "https://discord.gg/x7hppDWFDZ") { - UIApplication.shared.open(url) - } - }) { - HStack { - Text("Join the Discord") - .foregroundColor(.primary) - Spacer() - Image(systemName: "safari") - .foregroundColor(.secondary) - } - } - } - } - .navigationTitle("Settings") - .sheet(isPresented: $isDocumentPickerPresented) { - DocumentPicker( - libraryManager: libraryManager, - onSuccess: { miruData in - miruDataToImport = miruData - if let selectedModule = selectedModule { - libraryManager.importFromMiruData(miruData, module: selectedModule) - showImportSuccessAlert = true - } - }, - onFailure: { errorMessage in - importErrorMessage = errorMessage - showImportFailAlert = true - } - ) - } - .alert("Data Imported!", isPresented: $showImportSuccessAlert) { - Button("OK", role: .cancel) { } - } message: { - Text("Miru bookmarks are now imported in Sora, enjoy!") - } - .alert("Import Failed", isPresented: $showImportFailAlert) { - Button("OK", role: .cancel) { } - } message: { - Text(importErrorMessage) - } - } - .navigationViewStyle(StackNavigationViewStyle()) - } -} - -struct DocumentPicker: UIViewControllerRepresentable { - var libraryManager: LibraryManager - var onSuccess: (MiruDataStruct) -> Void - var onFailure: (String) -> Void - - func makeCoordinator() -> Coordinator { - Coordinator(self, libraryManager: libraryManager, onSuccess: onSuccess, onFailure: onFailure) - } - - func makeUIViewController(context: Context) -> UIDocumentPickerViewController { - let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.json]) - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} - - class Coordinator: NSObject, UIDocumentPickerDelegate { - var parent: DocumentPicker - var libraryManager: LibraryManager - var onSuccess: (MiruDataStruct) -> Void - var onFailure: (String) -> Void - - init(_ parent: DocumentPicker, libraryManager: LibraryManager, onSuccess: @escaping (MiruDataStruct) -> Void, onFailure: @escaping (String) -> Void) { - self.parent = parent - self.libraryManager = libraryManager - self.onSuccess = onSuccess - self.onFailure = onFailure - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - guard let selectedFileURL = urls.first else { - let errorMessage = "No file URL selected" - print(errorMessage) - Logger.shared.log(errorMessage) - onFailure(errorMessage) - return - } - - guard selectedFileURL.startAccessingSecurityScopedResource() else { - let errorMessage = "Could not access the file" - print(errorMessage) - Logger.shared.log("Could not access the Miru Backup File") - onFailure(errorMessage) - return - } - - defer { - selectedFileURL.stopAccessingSecurityScopedResource() - } - - do { - let data = try Data(contentsOf: selectedFileURL) - var miruData = try JSONDecoder().decode(MiruDataStruct.self, from: data) - - miruData.likes = miruData.likes.map { like in - var updatedLike = like - updatedLike.gogoSlug = "/series/" + like.gogoSlug - return updatedLike - } - - Logger.shared.log("Imported Miru data from \(selectedFileURL)") - onSuccess(miruData) - } catch { - let errorMessage = "Failed to import Miru data: \(error.localizedDescription)" - print(errorMessage) - Logger.shared.log(errorMessage) - onFailure(errorMessage) - } - } - - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - let errorMessage = "Document picker was closed" - print(errorMessage) - Logger.shared.log(errorMessage) - onFailure(errorMessage) - } - } -} - -enum Appearance: String, CaseIterable, Identifiable { - case system, light, dark - - var id: String { self.rawValue } -} - -class Settings: ObservableObject { - @Published var accentColor: Color { - didSet { - saveAccentColor(accentColor) - } - } - @Published var selectedAppearance: Appearance { - didSet { - UserDefaults.standard.set(selectedAppearance.rawValue, forKey: "selectedAppearance") - updateAppearance() - } - } - - init() { - if let colorData = UserDefaults.standard.data(forKey: "accentColor"), - let uiColor = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(colorData) as? UIColor { - self.accentColor = Color(uiColor) - } else { - self.accentColor = .accentColor - } - if let appearanceRawValue = UserDefaults.standard.string(forKey: "selectedAppearance"), - let appearance = Appearance(rawValue: appearanceRawValue) { - self.selectedAppearance = appearance - } else { - self.selectedAppearance = .system - } - updateAppearance() - } - - private func saveAccentColor(_ color: Color) { - let uiColor = UIColor(color) - do { - let colorData = try NSKeyedArchiver.archivedData(withRootObject: uiColor, requiringSecureCoding: false) - UserDefaults.standard.set(colorData, forKey: "accentColor") - } catch { - print("Failed to save accent color: \(error.localizedDescription)") - Logger.shared.log("Failed to save accent color: \(error.localizedDescription)") - } - } - - func updateAppearance() { - guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return } - switch selectedAppearance { - case .system: - windowScene.windows.first?.overrideUserInterfaceStyle = .unspecified - case .light: - windowScene.windows.first?.overrideUserInterfaceStyle = .light - case .dark: - windowScene.windows.first?.overrideUserInterfaceStyle = .dark - } - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsAboutView.swift b/Sora/Views/SettingsViews/SubPages/SettingsAboutView.swift deleted file mode 100644 index c9f1b02..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsAboutView.swift +++ /dev/null @@ -1,293 +0,0 @@ -// -// SettingsAboutView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher - -struct AboutView: View { - var body: some View { - Form { - Section(footer: Text("Sora is a free open source app, under the GPLv3.0 License. You can find the entire Sora code in the github repo.")) { - HStack(alignment: .center, spacing: 10) { - KFImage(URL(string: "https://raw.githubusercontent.com/cranci1/Sora/main/Sora/Assets.xcassets/AppIcon.appiconset/180.png")) - .resizable() - .frame(width: 80, height: 80) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("Sora") - .font(.largeTitle) - .fontWeight(.bold) - - Text("Public beta 0.1.1") - .font(.subheadline) - .foregroundColor(.secondary) - } - } - .padding(.vertical) - } - - Section(header: Text("Developer")) { - Button(action: { - if let url = URL(string: "https://github.com/cranci1") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/100066266?v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("cranci1") - .font(.headline) - .foregroundColor(.pink) - Text("YAY it's me") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.pink) - } - } - } - - Section(header: Text("Huge thanks"), footer: Text("A huge thanks to the Miru Development team for their support and contributions to Sora. I wont ever be able to thank them enough. Thanks a lot to them and all my discord helper.")) { - HStack { - KFImage(URL(string: "https://storage.ko-fi.com/cdn/useruploads/e68c31f0-7e66-4d63-934a-0508ce443bc0_e71506-30ce-4a01-9ac3-892ffcd18b77.png")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - Text("Miru Development Team") - .font(.headline) - .foregroundColor(.green) - } - Button(action: { - if let url = URL(string: "https://github.com/bshar1865") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/98615778?v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("MA.") - .font(.headline) - .foregroundColor(.orange) - Text("Discord Helper") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.orange) - } - } - Button(action: { - if let url = URL(string: "https://github.com/50n50") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/80717571?v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("50/50") - .font(.headline) - .foregroundColor(.mint) - Text("Discord Helper & Designer") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.mint) - } - } - Button(action: { - if let url = URL(string: "https://github.com/IBH-RAD") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/116025932?u=393be7ee3f476362b9e09d4f195ac035c5060236&v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("IBH") - .font(.headline) - .foregroundColor(.purple) - Text("Discord Helper & Bug Hunter") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.purple) - } - } - Button(action: { - if let url = URL(string: "https://github.com/Seeike") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/122684677?v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("Seiike") - .font(.headline) - .foregroundColor(.yellow) - Text("Discord Helper") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.yellow) - } - } - } - - Section(header: Text("Acknowledgements"), footer: Text("Thanks to the creators of this frameworks, that made Sora creation much simplier.")) { - Button(action: { - if let url = URL(string: "https://github.com/scinfu/SwiftSoup") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://raw.githubusercontent.com/scinfu/SwiftSoup/master/swiftsoup.png")) - .resizable() - .frame(width: 40, height: 40) - - VStack(alignment: .leading) { - Text("SwiftSoup") - .font(.headline) - .foregroundColor(.red) - Text("Web scraping") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.red) - } - } - Button(action: { - if let url = URL(string: "https://github.com/onevcat/Kingfisher") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://products.fileformat.com/image/swift/kingfisher/header-image.png")) - .resizable() - .frame(width: 40, height: 40) - - VStack(alignment: .leading) { - Text("Kingfisher") - .font(.headline) - .foregroundColor(.blue) - Text("Images caching and loading") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.blue) - } - } - Button(action: { - if let url = URL(string: "https://github.com/ipavlidakis/OpenCastSwift") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/575802?v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("OpenCastSwift") - .font(.headline) - .foregroundColor(.green) - Text("Casting support") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.green) - } - } - Button(action: { - if let url = URL(string: "https://github.com/SwiftyJSON/SwiftyJSON") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/8858017?s=200&v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("SwiftyJSON") - .font(.headline) - .foregroundColor(.orange) - Text("Opencast dependency") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.orange) - } - } - Button(action: { - if let url = URL(string: "https://github.com/apple/swift-protobuf") { - UIApplication.shared.open(url) - } - }) { - HStack { - KFImage(URL(string: "https://avatars.githubusercontent.com/u/10639145?s=200&v=4")) - .resizable() - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text("Swift Protobuf") - .font(.headline) - .foregroundColor(.purple) - Text("Opencast dependency") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Image(systemName: "safari") - .foregroundColor(.purple) - } - } - } - } - .navigationTitle("About") - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsEditorView.swift b/Sora/Views/SettingsViews/SubPages/SettingsEditorView.swift deleted file mode 100644 index c41a9a2..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsEditorView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// SettingsEditorView.swift -// Sora -// -// Created by Francesco on 03/01/25. -// - -import SwiftUI - -struct SettingsEditorView: View { - @ObservedObject var modulesManager: ModulesManager - @State private var jsonText: String = "" - - var body: some View { - VStack { - TextEditor(text: $jsonText) - .padding() - .onAppear { - if let data = try? JSONEncoder().encode(modulesManager.modules), - let jsonString = String(data: data, encoding: .utf8) { - jsonText = jsonString - } - } - } - .navigationTitle("Editor") - .navigationBarItems(trailing: Button("Save") { - if let data = jsonText.data(using: .utf8), - let modules = try? JSONDecoder().decode([ModuleStruct].self, from: data) { - modulesManager.modules = modules - modulesManager.saveModuleData() - } - }) - } -} \ No newline at end of file diff --git a/Sora/Views/SettingsViews/SubPages/SettingsIUView.swift b/Sora/Views/SettingsViews/SubPages/SettingsIUView.swift deleted file mode 100644 index 24953b8..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsIUView.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// SettingsIUView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct SettingsIUView: View { - @AppStorage("listSearch") private var isListSearchEnabled: Bool = false - - var body: some View { - Form { - Section(header: Text("Search")) { - Toggle("List Search Style", isOn: $isListSearchEnabled) - .tint(.accentColor) - } - } - .navigationTitle("Interface Preference") - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsLogsView.swift b/Sora/Views/SettingsViews/SubPages/SettingsLogsView.swift deleted file mode 100644 index 07bbeae..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsLogsView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// SettingsLogsView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct SettingsLogsView: View { - @State private var logs: String = "" - - var body: some View { - VStack { - ScrollView { - Text(logs) - .font(.footnote) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - .textSelection(.enabled) - } - .navigationTitle("Logs") - .onAppear { - logs = Logger.shared.getLogs() - } - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Menu { - Button(action: { - UIPasteboard.general.string = logs - }) { - Label("Copy to Clipboard", systemImage: "doc.on.doc") - } - Button(role: .destructive, action: { - Logger.shared.clearLogs() - logs = Logger.shared.getLogs() - }) { - Label("Clear Logs", systemImage: "trash") - } - } label: { - Image(systemName: "ellipsis.circle") - .resizable() - .frame(width: 20, height: 20) - } - } - } - } -} - -class Logger { - static let shared = Logger() - private var logs: [(message: String, timestamp: Date)] = [] - - private init() {} - - func log(_ message: String) { - logs.append((message: message, timestamp: Date())) - } - - func getLogs() -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] \($0.message)" } - .joined(separator: "\n---\n") - } - - func clearLogs() { - logs.removeAll() - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsModuleView.swift b/Sora/Views/SettingsViews/SubPages/SettingsModuleView.swift deleted file mode 100644 index 77faa47..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsModuleView.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// SettingsModuleView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI -import Kingfisher - -struct ErrorMessage: Identifiable { - var id: String { message } - let message: String -} - -struct SettingsModuleView: View { - @StateObject private var modulesManager = ModulesManager() - @State private var showingAddModuleAlert = false - @State private var moduleURL = "" - @State private var errorMessage: ErrorMessage? - @State private var previusImageURLs: [String: String] = [:] - - var body: some View { - VStack { - if modulesManager.isLoading { - ProgressView("Loading Modules...") - } else { - List { - ForEach(modulesManager.modules, id: \.name) { module in - HStack { - if let url = URL(string: module.iconURL) { - if previusImageURLs[module.name] != module.iconURL { - KFImage(url) - .resizable() - .frame(width: 50, height: 50) - .clipShape(Circle()) - .padding(.trailing, 10) - .onAppear { - previusImageURLs[module.name] = module.iconURL - } - } else { - KFImage(url) - .resizable() - .frame(width: 50, height: 50) - .clipShape(Circle()) - .padding(.trailing, 10) - } - } - VStack(alignment: .leading) { - Text(module.name) - .font(.headline) - .foregroundColor(.primary) - Text("Version: \(module.version)") - .font(.subheadline) - .foregroundColor(.secondary) - Text("Author: \(module.author.name)") - .font(.subheadline) - .foregroundColor(.secondary) - Text("Language: \(module.language)") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() - Text(module.stream) - .font(.caption) - .padding(5) - .background(Color.accentColor) - .foregroundColor(Color.primary) - .clipShape(Capsule()) - } - .contextMenu { - Button(action: { - UIPasteboard.general.string = modulesManager.moduleURLs[module.name] - }) { - Label("Copy URL", systemImage: "doc.on.doc") - } - Button(role: .destructive, action: { - modulesManager.deleteModule(named: module.name) - }) { - Label("Delete", systemImage: "trash") - } - } - } - .onDelete(perform: deleteModule) - } - .navigationBarTitle("Modules") - .navigationBarItems(trailing: Button(action: { - showAddModuleAlert() - }) { - Image(systemName: "plus") - .resizable() - .frame(width: 20, height: 20) - }) - .refreshable { - modulesManager.refreshModules() - } - } - } - .onAppear { - modulesManager.loadModules() - } - .alert(item: $errorMessage) { error in - Alert(title: Text("Error"), message: Text(error.message), dismissButton: .default(Text("OK"))) - } - } - - func showAddModuleAlert() { - let alert = UIAlertController(title: "Add Module", message: "Enter the URL of the module file", preferredStyle: .alert) - alert.addTextField { textField in - textField.placeholder = "https://real.url/module.json" - } - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { _ in - if let url = alert.textFields?.first?.text { - modulesManager.addModule(from: url) { result in - switch result { - case .success: - break - case .failure(let error): - errorMessage = ErrorMessage(message: error.localizedDescription) - Logger.shared.log(error.localizedDescription.capitalized) - } - } - } - })) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let rootViewController = windowScene.windows.first?.rootViewController { - rootViewController.present(alert, animated: true, completion: nil) - } - } - - func deleteModule(at offsets: IndexSet) { - offsets.forEach { index in - let module = modulesManager.modules[index] - modulesManager.deleteModule(named: module.name) - } - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsPlayerView.swift b/Sora/Views/SettingsViews/SubPages/SettingsPlayerView.swift deleted file mode 100644 index 429c001..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsPlayerView.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// SettingsPlayerView.swift -// Sora -// -// Created by Francesco on 18/12/24. -// - -import SwiftUI - -struct SettingsPlayerView: View { - @AppStorage("externalPlayer") private var externalPlayer: String = "Default" - @AppStorage("AlwaysLandscape") private var isAlwaysLandscape = false - @AppStorage("holdSpeedPlayer") private var holdSpeedPlayer: Double = 2.0 - - var body: some View { - Form { - Section(header: Text("Media Player"), footer: Text("The Force Landscape and HoldSpeed only work inside the default iOS player and Sora player.")) { - HStack { - Text("Media Player") - Spacer() - Menu(externalPlayer) { - Button(action: { - externalPlayer = "Default" - }) { - Label("Default", systemImage: externalPlayer == "Default" ? "checkmark" : "") - } - Button(action: { - externalPlayer = "VLC" - }) { - Label("VLC", systemImage: externalPlayer == "VLC" ? "checkmark" : "") - } - Button(action: { - externalPlayer = "OutPlayer" - }) { - Label("OutPlayer", systemImage: externalPlayer == "OutPlayer" ? "checkmark" : "") - } - Button(action: { - externalPlayer = "Infuse" - }) { - Label("Infuse", systemImage: externalPlayer == "Infuse" ? "checkmark" : "") - } - Button(action: { - externalPlayer = "nPlayer" - }) { - Label("nPlayer", systemImage: externalPlayer == "nPlayer" ? "checkmark" : "") - } - Button(action: { - externalPlayer = "Sora" - }) { - Label("Sora", systemImage: externalPlayer == "Sora" ? "checkmark" : "") - } - } - } - - HStack { - Text("Hold Speed:") - Spacer() - Stepper( - value: $holdSpeedPlayer, - in: 0.25...2.0, - step: 0.25 - ) { - Text(String(format: "%.2f", holdSpeedPlayer)) - } - } - } - } - .navigationTitle("Player") - } -} - diff --git a/Sora/Views/SettingsViews/SubPages/SettingsReleasesView.swift b/Sora/Views/SettingsViews/SubPages/SettingsReleasesView.swift deleted file mode 100644 index bcc6c79..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsReleasesView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// SettingsReleasesView.swift -// Sora -// -// Created by Francesco on 31/12/24. -// - -import SwiftUI - -struct SettingsReleasesView: View { - @State private var releases: [GitHubReleases] = [] - - var body: some View { - List(releases, id: \.tagName) { release in - VStack(alignment: .leading) { - Text(release.tagName) - .font(.system(size: 17)) - .bold() - Text(release.body) - .font(.system(size: 14)) - } - .contextMenu { - Button(action: { - if let url = URL(string: release.htmlUrl) { - UIApplication.shared.open(url) - } - }) { - Text("View on GitHub") - Image(systemName: "safari") - } - } - } - .navigationTitle("Releases") - .onAppear { - GitHubAPI.shared.fetchReleases { fetchedReleases in - if let fetchedReleases = fetchedReleases { - self.releases = fetchedReleases - } - } - } - } -} diff --git a/Sora/Views/SettingsViews/SubPages/SettingsStorageView.swift b/Sora/Views/SettingsViews/SubPages/SettingsStorageView.swift deleted file mode 100644 index 991218d..0000000 --- a/Sora/Views/SettingsViews/SubPages/SettingsStorageView.swift +++ /dev/null @@ -1,269 +0,0 @@ -// -// SettingsStorageView.swift -// Sora -// -// Created by Francesco on 19/12/24. -// - -import SwiftUI - -struct SettingsStorageView: View { - @State private var appSize: String = "Calculating..." - @State private var storageDetails: [(String, Double, Color)] = [] - @State private var deviceStorage: (total: Int64, used: Int64) = (0, 0) - @State private var showingClearCacheAlert = false - @State private var showingClearDocumentsAlert = false - - var body: some View { - List { - Section { - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .center) { - Text(appSize) - .font(.system(size: 28, weight: .bold)) - Text("of \(ByteCountFormatter.string(fromByteCount: deviceStorage.total, countStyle: .file))") - .foregroundColor(.secondary) - } - - VStack(alignment: .leading, spacing: 4) { - GeometryReader { geometry in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 8) - .fill(Color.gray.opacity(0.2)) - .frame(height: 24) - - RoundedRectangle(cornerRadius: 8) - .fill(Color.gray.opacity(0.3)) - .frame(width: geometry.size.width * CGFloat(deviceStorage.used) / CGFloat(deviceStorage.total)) - .frame(height: 24) - - HStack(spacing: 0) { - ForEach(storageDetails, id: \.0) { detail in - RoundedRectangle(cornerRadius: 0) - .fill(detail.2) - .frame(width: geometry.size.width * CGFloat(detail.1 * 1024 * 1024) / CGFloat(deviceStorage.total)) - .frame(height: 24) - } - } - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } - .frame(height: 24) - - HStack(spacing: 16) { - ForEach(storageDetails, id: \.0) { detail in - HStack(spacing: 4) { - Circle() - .fill(detail.2) - .frame(width: 8, height: 8) - Text(detail.0) - .font(.caption) - .foregroundColor(.secondary) - } - } - HStack(spacing: 4) { - Circle() - .fill(Color.gray.opacity(0.3)) - .frame(width: 8, height: 8) - Text("System") - .font(.caption) - .foregroundColor(.secondary) - } - } - } - } - .padding(.vertical, 8) - } - - Section { - ForEach(storageDetails, id: \.0) { detail in - HStack { - Image(systemName: categoryIcon(for: detail.0)) - .foregroundColor(.white) - .frame(width: 28, height: 28) - .background(detail.2) - .clipShape(RoundedRectangle(cornerRadius: 6)) - - VStack(alignment: .leading, spacing: 2) { - Text(detail.0) - Text("\(detail.1, specifier: "%.2f") MB") - .font(.caption) - .foregroundColor(.secondary) - } - } - } - } - - Section(header: Text("Actions")) { - Button(action: { showingClearCacheAlert = true }) { - actionRow( - icon: "clock.fill", - title: "Clear Cache", - subtitle: "Free up space used by cached items", - iconColor: .accentColor - ) - } - - Button(action: { showingClearDocumentsAlert = true }) { - actionRow( - icon: "doc.fill", - title: "Clear Documents", - subtitle: "Check and remove unnecessary files", - iconColor: .accentColor - ) - } - } - } - .navigationTitle("Storage") - .onAppear { - calculateAppSize() - getDeviceStorage() - } - .alert("Clear Cache", isPresented: $showingClearCacheAlert) { - Button("Cancel", role: .cancel) { } - Button("Clear", role: .destructive) { - clearCache() - } - } message: { - Text("Are you sure you want to clear the cache? This action cannot be undone.") - } - .alert("Clear Documents", isPresented: $showingClearDocumentsAlert) { - Button("Cancel", role: .cancel) { } - Button("Clear", role: .destructive) { - clearDocuments() - } - } message: { - Text("Are you sure you want to clear all documents? This action cannot be undone.") - } - } - - private func clearCache() { - if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first { - do { - let fileURLs = try FileManager.default.contentsOfDirectory( - at: cacheURL, - includingPropertiesForKeys: nil, - options: .skipsHiddenFiles - ) - - for fileURL in fileURLs { - try FileManager.default.removeItem(at: fileURL) - } - - calculateAppSize() - getDeviceStorage() - Logger.shared.log("Cleared Cache") - } catch { - print("Error clearing cache: \(error)") - Logger.shared.log("Error clearing cache: \(error)") - } - } - } - - private func clearDocuments() { - if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { - do { - let fileURLs = try FileManager.default.contentsOfDirectory( - at: documentsURL, - includingPropertiesForKeys: nil, - options: .skipsHiddenFiles - ) - - for fileURL in fileURLs { - try FileManager.default.removeItem(at: fileURL) - } - - calculateAppSize() - getDeviceStorage() - Logger.shared.log("Cleared Documents") - } catch { - print("Error clearing documents: \(error)") - Logger.shared.log("Error clearing documents: \(error)") - } - } - } - - private func getDeviceStorage() { - do { - let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String) - let values = try fileURL.resourceValues(forKeys: [.volumeTotalCapacityKey, .volumeAvailableCapacityKey]) - - if let total = values.volumeTotalCapacity, - let available = values.volumeAvailableCapacity { - deviceStorage.total = Int64(total) - deviceStorage.used = Int64(total - available) - } - } catch { - print("Error getting device storage: \(error)") - Logger.shared.log("Error getting device storage: \(error)") - } - } - - private func getTotalAppBytes() -> Int64 { - return Int64(totalSize() * 1024 * 1024) - } - - private func actionRow(icon: String, title: String, subtitle: String, iconColor: Color) -> some View { - HStack { - Image(systemName: icon) - .foregroundColor(.white) - .frame(width: 28, height: 28) - .background(iconColor) - .clipShape(RoundedRectangle(cornerRadius: 6)) - - VStack(alignment: .leading, spacing: 2) { - Text(title) - Text(subtitle) - .font(.caption) - .foregroundColor(.secondary) - } - } - } - - private func categoryIcon(for category: String) -> String { - switch category { - case "Documents": - return "doc.fill" - case "Cache": - return "clock.fill" - case "Temporary": - return "trash.fill" - default: - return "questionmark" - } - } - - private func calculateAppSize() { - let cacheSize = getDirectorySize(url: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!) - let documentsSize = getDirectorySize(url: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!) - let tmpSize = getDirectorySize(url: FileManager.default.temporaryDirectory) - - let totalSize = cacheSize + documentsSize + tmpSize - self.appSize = ByteCountFormatter.string(fromByteCount: Int64(totalSize), countStyle: .file) - self.storageDetails = [ - ("Documents", documentsSize / 1024 / 1024, .green), - ("Cache", cacheSize / 1024 / 1024, .orange), - ("Temporary", tmpSize / 1024 / 1024, .red) - ] - } - - private func getDirectorySize(url: URL) -> Double { - var size: Double = 0 - if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.fileSizeKey], options: [], errorHandler: nil) { - for case let fileURL as URL in enumerator { - do { - let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey]) - size += Double(resourceValues.fileSize ?? 0) - } catch { - print("Error calculating size for file \(fileURL): \(error)") - Logger.shared.log("Error calculating size for file \(fileURL): \(error)") - } - } - } - return size - } - - private func totalSize() -> Double { - return storageDetails.reduce(0) { $0 + $1.1 } - } -} diff --git a/assets/Sora_banner.png b/assets/Sora_banner.png deleted file mode 100644 index d99b8e7..0000000 Binary files a/assets/Sora_banner.png and /dev/null differ diff --git a/assets/banner2.png b/assets/banner2.png deleted file mode 100644 index a46bc4f..0000000 Binary files a/assets/banner2.png and /dev/null differ diff --git a/ipabuild.sh b/ipabuild.sh deleted file mode 100755 index e9ea9bd..0000000 --- a/ipabuild.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -set -e - -cd "$(dirname "$0")" - -WORKING_LOCATION="$(pwd)" -APPLICATION_NAME=Sora - -if [ ! -d "build" ]; then - mkdir build -fi - -cd build - -xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \ - -scheme "$APPLICATION_NAME" \ - -configuration Release \ - -derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp" \ - -destination 'generic/platform=iOS' \ - clean build \ - CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" - -DD_APP_PATH="$WORKING_LOCATION/build/DerivedDataApp/Build/Products/Release-iphoneos/$APPLICATION_NAME.app" -TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app" -cp -r "$DD_APP_PATH" "$TARGET_APP" - -codesign --remove "$TARGET_APP" -if [ -e "$TARGET_APP/_CodeSignature" ]; then - rm -rf "$TARGET_APP/_CodeSignature" -fi -if [ -e "$TARGET_APP/embedded.mobileprovision" ]; then - rm -rf "$TARGET_APP/embedded.mobileprovision" -fi - - -mkdir Payload -cp -r Sora.app Payload/Sora.app -strip Payload/Sora.app/Sora -zip -vr Sora.ipa Payload -rm -rf Sora.app -rm -rf Payload diff --git a/macbuild.sh b/macbuild.sh deleted file mode 100755 index 1472471..0000000 --- a/macbuild.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -set -e - -cd "$(dirname "$0")" - -WORKING_LOCATION="$(pwd)" -APPLICATION_NAME=Sora - -if [ ! -d "build" ]; then - mkdir build -fi - -cd build - -xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \ - -scheme "$APPLICATION_NAME" \ - -configuration Release \ - -derivedDataPath "$WORKING_LOCATION/build/DerivedDataApp" \ - -destination 'platform=macOS,variant=Mac Catalyst' \ - clean build \ - CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" - -DD_APP_PATH="$WORKING_LOCATION/build/DerivedDataApp/Build/Products/Release-maccatalyst/$APPLICATION_NAME.app" -TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app" - -if [ -e "$TARGET_APP" ]; then - rm -rf "$TARGET_APP" -fi - -cp -r "$DD_APP_PATH" "$TARGET_APP" - -codesign --remove "$TARGET_APP" -if [ -e "$TARGET_APP/_CodeSignature" ]; then - rm -rf "$TARGET_APP/_CodeSignature" -fi - -zip -vr "$APPLICATION_NAME-catalyst.zip" "$APPLICATION_NAME.app" -rm -rf "$APPLICATION_NAME.app"