diff --git a/Kodi Remote.entitlements b/Kodi Remote.entitlements new file mode 100644 index 000000000..d3aa35aca --- /dev/null +++ b/Kodi Remote.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.it.joethefox.XBMC-Remote.share + + + diff --git a/Kodi Remote.xcodeproj/project.pbxproj b/Kodi Remote.xcodeproj/project.pbxproj index 6ac38ba73..154e96093 100644 --- a/Kodi Remote.xcodeproj/project.pbxproj +++ b/Kodi Remote.xcodeproj/project.pbxproj @@ -3,10 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ + 0636D0DE2E86BF3E00789FCC /* PlayOnKodi.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0636D0D42E86BF3E00789FCC /* PlayOnKodi.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0F00207816EE247A003822B5 /* OBSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F00207616EE247A003822B5 /* OBSlider.m */; }; 0F05FB69170D79D400BBA833 /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F05FB68170D79D400BBA833 /* NSString+MD5.m */; }; 0F089AA518EC4E3300F32430 /* SettingsValuesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F089AA418EC4E3300F32430 /* SettingsValuesViewController.m */; }; @@ -107,7 +108,33 @@ C79B0EA52DF0CF6900046334 /* BaseMasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C79B0EA42DF0CF6900046334 /* BaseMasterViewController.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 0636D0DC2E86BF3E00789FCC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0F5548ED151D1187007E633F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0636D0D32E86BF3E00789FCC; + remoteInfo = PlayOnKodi; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0636D0DF2E86BF3E00789FCC /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0636D0DE2E86BF3E00789FCC /* PlayOnKodi.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 0636D0D42E86BF3E00789FCC /* PlayOnKodi.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PlayOnKodi.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 0636D0E72E86C0A200789FCC /* Kodi Remote.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Kodi Remote.entitlements"; sourceTree = ""; }; 0F00207516EE247A003822B5 /* OBSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OBSlider.h; sourceTree = ""; }; 0F00207616EE247A003822B5 /* OBSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OBSlider.m; sourceTree = ""; }; 0F0320841C59531700C7E25D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -346,7 +373,28 @@ C7F28D4826961FE50004B112 /* ConvenienceMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConvenienceMacros.h; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 0636D0E22E86BF3E00789FCC /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 0636D0D32E86BF3E00789FCC /* PlayOnKodi */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 0636D0D52E86BF3E00789FCC /* PlayOnKodi */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (0636D0E22E86BF3E00789FCC /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = PlayOnKodi; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ + 0636D0D12E86BF3E00789FCC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 0F5548F3151D1187007E633F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -513,11 +561,13 @@ 0F5548EB151D1187007E633F = { isa = PBXGroup; children = ( + 0636D0E72E86C0A200789FCC /* Kodi Remote.entitlements */, C70F41D02BA4D98E00847C75 /* PrivacyInfo.xcprivacy */, 0F59E2661564796B00184AE8 /* CONTRIBUTING.md */, 0F59E26315646FB800184AE8 /* LICENSE */, 0F8717911564566300AE2D48 /* README.md */, 0F554900151D1187007E633F /* XBMC Remote */, + 0636D0D52E86BF3E00789FCC /* PlayOnKodi */, 0F5548F9151D1187007E633F /* Frameworks */, 0F5548F7151D1187007E633F /* Products */, ); @@ -527,6 +577,7 @@ isa = PBXGroup; children = ( 0F5548F6151D1187007E633F /* Kodi Remote.app */, + 0636D0D42E86BF3E00789FCC /* PlayOnKodi.appex */, ); name = Products; sourceTree = ""; @@ -751,6 +802,28 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0636D0D32E86BF3E00789FCC /* PlayOnKodi */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0636D0E32E86BF3E00789FCC /* Build configuration list for PBXNativeTarget "PlayOnKodi" */; + buildPhases = ( + 0636D0D02E86BF3E00789FCC /* Sources */, + 0636D0D12E86BF3E00789FCC /* Frameworks */, + 0636D0D22E86BF3E00789FCC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 0636D0D52E86BF3E00789FCC /* PlayOnKodi */, + ); + name = PlayOnKodi; + packageProductDependencies = ( + ); + productName = PlayOnKodi; + productReference = 0636D0D42E86BF3E00789FCC /* PlayOnKodi.appex */; + productType = "com.apple.product-type.app-extension"; + }; 0F5548F5151D1187007E633F /* Kodi Remote */ = { isa = PBXNativeTarget; buildConfigurationList = 0F55491A151D1187007E633F /* Build configuration list for PBXNativeTarget "Kodi Remote" */; @@ -758,10 +831,12 @@ 0F5548F2151D1187007E633F /* Sources */, 0F5548F3151D1187007E633F /* Frameworks */, 0F5548F4151D1187007E633F /* Resources */, + 0636D0DF2E86BF3E00789FCC /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 0636D0DD2E86BF3E00789FCC /* PBXTargetDependency */, ); name = "Kodi Remote"; productName = "XBMC Remote"; @@ -775,9 +850,13 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1520; ORGANIZATIONNAME = "Team Kodi"; TargetAttributes = { + 0636D0D32E86BF3E00789FCC = { + CreatedOnToolsVersion = 16.4; + }; 0F5548F5151D1187007E633F = { LastSwiftMigration = 1620; }; @@ -820,11 +899,19 @@ projectRoot = ""; targets = ( 0F5548F5151D1187007E633F /* Kodi Remote */, + 0636D0D32E86BF3E00789FCC /* PlayOnKodi */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 0636D0D22E86BF3E00789FCC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 0F5548F4151D1187007E633F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -860,6 +947,13 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0636D0D02E86BF3E00789FCC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 0F5548F2151D1187007E633F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -940,6 +1034,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 0636D0DD2E86BF3E00789FCC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0636D0D32E86BF3E00789FCC /* PlayOnKodi */; + targetProxy = 0636D0DC2E86BF3E00789FCC /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 0F554903151D1187007E633F /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -1006,6 +1108,95 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0636D0E02E86BF3E00789FCC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = PlayOnKodi/PlayOnKodi.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PlayOnKodi/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PlayOnKodi; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Team Kodi. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "it.joethefox.XBMC-Remote.PlayOnKodi"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0636D0E12E86BF3E00789FCC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = PlayOnKodi/PlayOnKodi.entitlements; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PlayOnKodi/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PlayOnKodi; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Team Kodi. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "it.joethefox.XBMC-Remote.PlayOnKodi"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 0F554918151D1187007E633F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1120,6 +1311,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Kodi Remote.entitlements"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "XBMC Remote/Kodi Remote-Prefix.pch"; INFOPLIST_FILE = "XBMC Remote/Kodi Remote-Info.plist"; @@ -1138,6 +1330,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Kodi Remote.entitlements"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "XBMC Remote/Kodi Remote-Prefix.pch"; INFOPLIST_FILE = "XBMC Remote/Kodi Remote-Info.plist"; @@ -1153,6 +1346,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0636D0E32E86BF3E00789FCC /* Build configuration list for PBXNativeTarget "PlayOnKodi" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0636D0E02E86BF3E00789FCC /* Debug */, + 0636D0E12E86BF3E00789FCC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 0F5548F0151D1187007E633F /* Build configuration list for PBXProject "Kodi Remote" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/PlayOnKodi/Base.lproj/MainInterface.storyboard b/PlayOnKodi/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..286a50894 --- /dev/null +++ b/PlayOnKodi/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlayOnKodi/Info.plist b/PlayOnKodi/Info.plist new file mode 100644 index 000000000..0b4b82166 --- /dev/null +++ b/PlayOnKodi/Info.plist @@ -0,0 +1,23 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPrincipalClass + PlayOnKodi.ShareViewController + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/PlayOnKodi/PlayOnKodi.entitlements b/PlayOnKodi/PlayOnKodi.entitlements new file mode 100644 index 000000000..d3aa35aca --- /dev/null +++ b/PlayOnKodi/PlayOnKodi.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.it.joethefox.XBMC-Remote.share + + + diff --git a/PlayOnKodi/ShareView.swift b/PlayOnKodi/ShareView.swift new file mode 100644 index 000000000..b43228f54 --- /dev/null +++ b/PlayOnKodi/ShareView.swift @@ -0,0 +1,48 @@ +import SwiftUI +import SwiftData + +struct ShareView: View { + @State var urlFromShareViewSheet: String + + init(urlFromShareViewSeet: String) { + self.urlFromShareViewSheet = urlFromShareViewSeet + } + + var body: some View { + NavigationStack { + VStack(spacing: 20) { + Text(urlFromShareViewSheet) + Button { + Task { + await saveLink(sharedLink: urlFromShareViewSheet) + } + self.close() + } label: { + Text("Save Link") + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .buttonBorderShape(.roundedRectangle(radius: 5)) + } + .padding() + .toolbar { + Button("Cancel") { + self.close() + } + } + .navigationTitle("Add new bookmark") + } + } + + func saveLink(sharedLink: String) async { + // do something + } + + func close() { + NotificationCenter.default.post(name: NSNotification.Name("Close"), object: nil) + } +} + +//#Preview { +// ShareView() +//} diff --git a/PlayOnKodi/ShareViewController.swift b/PlayOnKodi/ShareViewController.swift new file mode 100644 index 000000000..9963ffb5f --- /dev/null +++ b/PlayOnKodi/ShareViewController.swift @@ -0,0 +1,54 @@ +import UIKit +import SwiftUI + +class ShareViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + + guard + let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, + let itemProvider = extensionItem.attachments?.first else { + self.close() + return + } + + if itemProvider.hasItemConformingToTypeIdentifier("public.url") { + itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in + if error != nil { + self.close() + return + } + + // TODO: Add ability to choose between 'play' and 'queue' + + if let sharedUrl = url as? URL, let host = (url as? URL)?.host() { + if host.contains("youtube.com") || host.contains("youtu.be") { + debugPrint("Youtube URL Found:", sharedUrl) + self.close() + return + } else { + self.close() + return + } + } else { + self.close() + return + } + } + } else { + close() + return + } + + NotificationCenter.default.addObserver(forName: NSNotification.Name("Close"), object: nil, queue: nil) { _ in + DispatchQueue.main.async { + self.close() + } + } + } + + func close() { + self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + NotificationCenter.default.post(name: NSNotification.Name("Close"), object: nil) + } +}