Add Next Up Widgets (#43)
* WIP * add EntryView UI apply AppGroups apply KeychainGroups * update widget layout * update widget layout * Refactoring add WidgetEnvironment add snapshot logic add placeholder * fix * fix team id * pass ci? * update keychain group * Update PersistenceController.swift Co-authored-by: Aiden Vigue <acvigue@me.com>
This commit is contained in:
parent
d6aa8c4a22
commit
7c5a4441c1
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 628B95212670CABD0091AF3B /* WidgetKit.framework */; };
|
||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
||||
5321753F2671DEA6005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
||||
|
@ -72,9 +73,38 @@
|
|||
621C638026672A30004216EA /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 621C637F26672A30004216EA /* NukeUI */; };
|
||||
621C638226676728004216EA /* NukeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621C638126676728004216EA /* NukeExtensions.swift */; };
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */; };
|
||||
6228B1C22670EB010067FD35 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBFD263B596B003A4E83 /* PersistenceController.swift */; };
|
||||
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
|
||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
||||
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
|
||||
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D92671138200A7371D /* ImageExtensions.swift */; };
|
||||
6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D92671138200A7371D /* ImageExtensions.swift */; };
|
||||
6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D92671138200A7371D /* ImageExtensions.swift */; };
|
||||
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 628B95232670CABD0091AF3B /* SwiftUI.framework */; };
|
||||
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 628B95262670CABD0091AF3B /* NextUpWidget.swift */; };
|
||||
628B95292670CABE0091AF3B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 628B95282670CABE0091AF3B /* Assets.xcassets */; };
|
||||
628B952D2670CABE0091AF3B /* WidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 628B95202670CABD0091AF3B /* WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
628B95332670CAEA0091AF3B /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95322670CAEA0091AF3B /* NukeUI */; };
|
||||
628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95342670CAEA0091AF3B /* JellyfinAPI */; };
|
||||
628B95372670CB800091AF3B /* JellyfinWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 628B95362670CB800091AF3B /* JellyfinWidget.swift */; };
|
||||
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBFF263B596B003A4E83 /* Model.xcdatamodeld */; };
|
||||
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; };
|
||||
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
||||
62FA8A522671DE3C004BA2AB /* WidgetEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FA8A512671DE3C004BA2AB /* WidgetEnvironment.swift */; };
|
||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
628B952B2670CABE0091AF3B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5377CBE9263B596A003A4E83 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 628B951F2670CABD0091AF3B;
|
||||
remoteInfo = WidgetExtensionExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5302F8322658B74800647A2E /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
|
@ -107,6 +137,17 @@
|
|||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
628B95312670CABE0091AF3B /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
628B952D2670CABE0091AF3B /* WidgetExtension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -160,6 +201,17 @@
|
|||
621338B22660A07800A81A2A /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
||||
621C638126676728004216EA /* NukeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeExtensions.swift; sourceTree = "<group>"; };
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeader.swift; sourceTree = "<group>"; };
|
||||
6267B3D526710B8900A7371D /* CollectionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtensions.swift; sourceTree = "<group>"; };
|
||||
6267B3D92671138200A7371D /* ImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageExtensions.swift; sourceTree = "<group>"; };
|
||||
628B95202670CABD0091AF3B /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
628B95212670CABD0091AF3B /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
628B95232670CABD0091AF3B /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
628B95262670CABD0091AF3B /* NextUpWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpWidget.swift; sourceTree = "<group>"; };
|
||||
628B95282670CABE0091AF3B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
628B952A2670CABE0091AF3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
628B95362670CB800091AF3B /* JellyfinWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinWidget.swift; sourceTree = "<group>"; };
|
||||
628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
62FA8A512671DE3C004BA2AB /* WidgetEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetEnvironment.swift; sourceTree = "<group>"; };
|
||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -188,6 +240,18 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
628B951D2670CABD0091AF3B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
628B95332670CAEA0091AF3B /* NukeUI in Frameworks */,
|
||||
628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */,
|
||||
531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */,
|
||||
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */,
|
||||
628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
|
@ -244,6 +308,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
53D5E3DA264B460200BADDC8 /* Cartfile */,
|
||||
628B95252670CABD0091AF3B /* WidgetExtension */,
|
||||
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
|
||||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */,
|
||||
535870612669D21600D05A09 /* JellyfinPlayer tvOS */,
|
||||
|
@ -257,6 +322,7 @@
|
|||
children = (
|
||||
5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */,
|
||||
535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */,
|
||||
628B95202670CABD0091AF3B /* WidgetExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -308,6 +374,8 @@
|
|||
children = (
|
||||
5358709C2669D82900D05A09 /* TVVLCKit.framework */,
|
||||
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */,
|
||||
628B95212670CABD0091AF3B /* WidgetKit.framework */,
|
||||
628B95232670CABD0091AF3B /* SwiftUI.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
@ -323,10 +391,25 @@
|
|||
621C638126676728004216EA /* NukeExtensions.swift */,
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
|
||||
621338922660107500A81A2A /* StringExtensions.swift */,
|
||||
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
|
||||
6267B3D92671138200A7371D /* ImageExtensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
628B95252670CABD0091AF3B /* WidgetExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */,
|
||||
628B95362670CB800091AF3B /* JellyfinWidget.swift */,
|
||||
628B95262670CABD0091AF3B /* NextUpWidget.swift */,
|
||||
628B95282670CABE0091AF3B /* Assets.xcassets */,
|
||||
628B952A2670CABE0091AF3B /* Info.plist */,
|
||||
62FA8A512671DE3C004BA2AB /* WidgetEnvironment.swift */,
|
||||
);
|
||||
path = WidgetExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3157265D6F5E008AA076 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -372,10 +455,12 @@
|
|||
5377CBEF263B596A003A4E83 /* Resources */,
|
||||
53D5E3DF264B47EE00BADDC8 /* Embed Frameworks */,
|
||||
5302F8322658B74800647A2E /* CopyFiles */,
|
||||
628B95312670CABE0091AF3B /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
628B952C2670CABE0091AF3B /* PBXTargetDependency */,
|
||||
);
|
||||
name = "JellyfinPlayer iOS";
|
||||
packageProductDependencies = (
|
||||
|
@ -388,6 +473,28 @@
|
|||
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
628B951F2670CABD0091AF3B /* WidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 628B952E2670CABE0091AF3B /* Build configuration list for PBXNativeTarget "WidgetExtension" */;
|
||||
buildPhases = (
|
||||
628B951C2670CABD0091AF3B /* Sources */,
|
||||
628B951D2670CABD0091AF3B /* Frameworks */,
|
||||
628B951E2670CABD0091AF3B /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = WidgetExtension;
|
||||
packageProductDependencies = (
|
||||
628B95322670CAEA0091AF3B /* NukeUI */,
|
||||
628B95342670CAEA0091AF3B /* JellyfinAPI */,
|
||||
628B95392670CE250091AF3B /* KeychainSwift */,
|
||||
);
|
||||
productName = WidgetExtensionExtension;
|
||||
productReference = 628B95202670CABD0091AF3B /* WidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
|
@ -406,6 +513,9 @@
|
|||
5377CBF0263B596A003A4E83 = {
|
||||
CreatedOnToolsVersion = 12.5;
|
||||
};
|
||||
628B951F2670CABD0091AF3B = {
|
||||
CreatedOnToolsVersion = 12.5;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 5377CBEC263B596A003A4E83 /* Build configuration list for PBXProject "JellyfinPlayer" */;
|
||||
|
@ -429,6 +539,7 @@
|
|||
targets = (
|
||||
5377CBF0263B596A003A4E83 /* JellyfinPlayer iOS */,
|
||||
5358705F2669D21600D05A09 /* JellyfinPlayer tvOS */,
|
||||
628B951F2670CABD0091AF3B /* WidgetExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -455,6 +566,14 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
628B951E2670CABD0091AF3B /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
628B95292670CABE0091AF3B /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
@ -462,7 +581,9 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */,
|
||||
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
||||
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
|
||||
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
|
||||
535870A92669D8AE00D05A09 /* NukeExtensions.swift in Sources */,
|
||||
|
@ -502,12 +623,14 @@
|
|||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
||||
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
|
||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
||||
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
|
||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
||||
53C4404E266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
|
||||
5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */,
|
||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
|
@ -519,8 +642,32 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
628B951C2670CABD0091AF3B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */,
|
||||
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */,
|
||||
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */,
|
||||
6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */,
|
||||
628B95372670CB800091AF3B /* JellyfinWidget.swift in Sources */,
|
||||
6228B1C22670EB010067FD35 /* PersistenceController.swift in Sources */,
|
||||
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,
|
||||
62FA8A522671DE3C004BA2AB /* WidgetEnvironment.swift in Sources */,
|
||||
628B95382670CDAB0091AF3B /* Model.xcdatamodeld in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
628B952C2670CABE0091AF3B /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 628B951F2670CABD0091AF3B /* WidgetExtension */;
|
||||
targetProxy = 628B952B2670CABE0091AF3B /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
535870722669D21700D05A09 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
|
@ -702,6 +849,7 @@
|
|||
5377CC1C263B596B003A4E83 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
|
||||
|
@ -733,6 +881,7 @@
|
|||
5377CC1D263B596B003A4E83 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
|
||||
|
@ -762,6 +911,56 @@
|
|||
};
|
||||
name = Release;
|
||||
};
|
||||
628B952F2670CABE0091AF3B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 36;
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
628B95302670CABE0091AF3B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 36;
|
||||
DEVELOPMENT_TEAM = 9R8RREG67J;
|
||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
|
@ -792,6 +991,15 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
628B952E2670CABE0091AF3B /* Build configuration list for PBXNativeTarget "WidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
628B952F2670CABE0091AF3B /* Debug */,
|
||||
628B95302670CABE0091AF3B /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
|
@ -870,6 +1078,21 @@
|
|||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
628B95322670CAEA0091AF3B /* NukeUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
628B95342670CAEA0091AF3B /* JellyfinAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||
productName = JellyfinAPI;
|
||||
};
|
||||
628B95392670CE250091AF3B /* KeychainSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "628B951F2670CABD0091AF3B"
|
||||
BuildableName = "WidgetExtension.appex"
|
||||
BlueprintName = "WidgetExtension"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.springboard">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "628B951F2670CABD0091AF3B"
|
||||
BuildableName = "WidgetExtension.appex"
|
||||
BlueprintName = "WidgetExtension"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetKind"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetDefaultView"
|
||||
value = "snapshot"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "_XCWidgetFamily"
|
||||
value = "medium"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -11,6 +11,7 @@ import KeychainSwift
|
|||
import Nuke
|
||||
import Combine
|
||||
import JellyfinAPI
|
||||
import WidgetKit
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
@ -63,6 +64,7 @@ struct ContentView: View {
|
|||
let savedUser = savedUsers[0]
|
||||
|
||||
let keychain = KeychainSwift()
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
if keychain.get("AccessToken_\(savedUser.user_id ?? "")") != nil {
|
||||
globalData.authToken = keychain.get("AccessToken_\(savedUser.user_id ?? "")") ?? ""
|
||||
globalData.server = servers[0]
|
||||
|
@ -126,6 +128,7 @@ struct ContentView: View {
|
|||
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
|
||||
}
|
||||
}
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
|
@ -6,7 +6,15 @@
|
|||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.me.vigue.jellyfin.mobileclient</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)me.vigue.jellyfin.sharedKeychain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -30,6 +30,9 @@ struct PersistenceController {
|
|||
|
||||
init(inMemory: Bool = false) {
|
||||
container = NSPersistentCloudKitContainer(name: "Model")
|
||||
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.me.vigue.jellyfin.mobileclient")!.appendingPathComponent("\(container.name).sqlite"))]
|
||||
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public extension Collection {
|
||||
|
||||
/// SwifterSwift: Safe protects the array from out of bounds by use of optional.
|
||||
///
|
||||
/// let arr = [1, 2, 3, 4, 5]
|
||||
/// arr[safe: 1] -> 2
|
||||
/// arr[safe: 10] -> nil
|
||||
///
|
||||
/// - Parameter index: index of element to access element.
|
||||
subscript(safe index: Index) -> Element? {
|
||||
return indices.contains(index) ? self[index] : nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Image {
|
||||
func centerCropped() -> some View {
|
||||
GeometryReader { geo in
|
||||
self
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
.clipped()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.me.vigue.jellyfin.mobileclient</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)me.vigue.jellyfin.sharedKeychain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.765",
|
||||
"green" : "0.361",
|
||||
"red" : "0.667"
|
||||
}
|
||||
},
|
||||
"idiom" : "iphone"
|
||||
},
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.765",
|
||||
"green" : "0.361",
|
||||
"red" : "0.667"
|
||||
}
|
||||
},
|
||||
"idiom" : "tv"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 255 KiB |
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,18 @@
|
|||
/* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
import KeychainSwift
|
||||
|
||||
@main
|
||||
struct JellyfinWidgetBundle: WidgetBundle {
|
||||
@WidgetBundleBuilder
|
||||
var body: some Widget {
|
||||
NextUpWidget()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
/* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Combine
|
||||
import JellyfinAPI
|
||||
import Nuke
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
enum WidgetError: String, Error {
|
||||
case unknown
|
||||
case emptyServer
|
||||
case emptyUser
|
||||
case emptyHeader
|
||||
}
|
||||
|
||||
struct NextUpWidgetProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> NextUpEntry {
|
||||
NextUpEntry(date: Date(), items: [], error: nil)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) {
|
||||
let currentDate = Date()
|
||||
WidgetEnvironment.shared.update()
|
||||
guard let server = WidgetEnvironment.shared.server else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyServer))
|
||||
}
|
||||
}
|
||||
guard let savedUser = WidgetEnvironment.shared.user else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyUser))
|
||||
}
|
||||
}
|
||||
guard let header = WidgetEnvironment.shared.header else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyHeader))
|
||||
}
|
||||
}
|
||||
var tempCancellables = Set<AnyCancellable>()
|
||||
JellyfinAPI.basePath = server.baseURI ?? ""
|
||||
JellyfinAPI.customHeaders = ["X-Emby-Authorization": header]
|
||||
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||
.sink(receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
break
|
||||
case let .failure(error):
|
||||
completion(NextUpEntry(date: currentDate, items: [], error: error))
|
||||
}
|
||||
}, receiveValue: { response in
|
||||
let dispatchGroup = DispatchGroup()
|
||||
let items = response.items ?? []
|
||||
var downloadedItems = [(BaseItemDto, UIImage?)]()
|
||||
items.enumerated().forEach { _, item in
|
||||
dispatchGroup.enter()
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(baseURL: server.baseURI ?? "", maxWidth: 320)) { result in
|
||||
guard case let .success(image) = result else {
|
||||
dispatchGroup.leave()
|
||||
return
|
||||
}
|
||||
downloadedItems.append((item, image.image))
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil))
|
||||
}
|
||||
})
|
||||
.store(in: &tempCancellables)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
|
||||
let currentDate = Date()
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)!
|
||||
WidgetEnvironment.shared.update()
|
||||
guard let server = WidgetEnvironment.shared.server else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyServer)],
|
||||
policy: .after(entryDate)))
|
||||
}
|
||||
}
|
||||
guard let savedUser = WidgetEnvironment.shared.user else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyUser)],
|
||||
policy: .after(entryDate)))
|
||||
}
|
||||
}
|
||||
guard let header = WidgetEnvironment.shared.header else { return
|
||||
DispatchQueue.main.async {
|
||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: WidgetError.emptyHeader)],
|
||||
policy: .after(entryDate)))
|
||||
}
|
||||
}
|
||||
var tempCancellables = Set<AnyCancellable>()
|
||||
JellyfinAPI.basePath = server.baseURI ?? ""
|
||||
JellyfinAPI.customHeaders = ["X-Emby-Authorization": header]
|
||||
TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb])
|
||||
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||
.sink(receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
break
|
||||
case let .failure(error):
|
||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate)))
|
||||
}
|
||||
}, receiveValue: { response in
|
||||
let dispatchGroup = DispatchGroup()
|
||||
let items = response.items ?? []
|
||||
var downloadedItems = [(BaseItemDto, UIImage?)]()
|
||||
items.enumerated().forEach { _, item in
|
||||
dispatchGroup.enter()
|
||||
ImagePipeline.shared.loadImage(with: item.getBackdropImage(baseURL: server.baseURI ?? "", maxWidth: 320)) { result in
|
||||
guard case let .success(image) = result else {
|
||||
dispatchGroup.leave()
|
||||
return
|
||||
}
|
||||
downloadedItems.append((item, image.image))
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)],
|
||||
policy: .after(entryDate)))
|
||||
}
|
||||
})
|
||||
.store(in: &tempCancellables)
|
||||
}
|
||||
}
|
||||
|
||||
struct NextUpEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let items: [(BaseItemDto, UIImage?)]
|
||||
let error: Error?
|
||||
}
|
||||
|
||||
struct NextUpEntryView: View {
|
||||
var entry: NextUpWidgetProvider.Entry
|
||||
|
||||
@Environment(\.widgetFamily)
|
||||
var family
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
Group {
|
||||
if let error = entry.error {
|
||||
HStack {
|
||||
Image(systemName: "exclamationmark.octagon")
|
||||
Text((error as? WidgetError)?.rawValue ?? "")
|
||||
}
|
||||
.background(Color.blue)
|
||||
} else if entry.items.isEmpty {
|
||||
Text("Empty Next Up")
|
||||
.font(.body)
|
||||
.bold()
|
||||
.foregroundColor(.primary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
} else {
|
||||
switch family {
|
||||
case .systemSmall:
|
||||
small(item: entry.items.first)
|
||||
case .systemMedium:
|
||||
medium(items: entry.items)
|
||||
case .systemLarge:
|
||||
large(items: entry.items)
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color(.secondarySystemBackground))
|
||||
}
|
||||
}
|
||||
|
||||
extension NextUpEntryView {
|
||||
var smallVideoPlaceholderView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Color(.systemGray)
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
Color(.systemGray2)
|
||||
.frame(width: 100, height: 10)
|
||||
Color(.systemGray3)
|
||||
.frame(width: 80, height: 10)
|
||||
}
|
||||
}
|
||||
|
||||
var largeVideoPlaceholderView: some View {
|
||||
HStack(spacing: 20) {
|
||||
Color(.systemGray)
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Color(.systemGray2)
|
||||
.frame(width: 100, height: 10)
|
||||
Color(.systemGray3)
|
||||
.frame(width: 80, height: 10)
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NextUpEntryView {
|
||||
var headerSymbol: some View {
|
||||
Image("WidgetHeaderSymbol")
|
||||
.resizable()
|
||||
.frame(width: 12, height: 12)
|
||||
.cornerRadius(4)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
|
||||
func smallVideoView(item: (BaseItemDto, UIImage?)) -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
Text(item.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func largeVideoView(item: (BaseItemDto, UIImage?)) -> some View {
|
||||
HStack(spacing: 20) {
|
||||
if let image = item.1 {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(.init(width: 1, height: 0.5625), contentMode: .fill)
|
||||
.clipped()
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(item.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NextUpEntryView {
|
||||
func small(item: (BaseItemDto, UIImage?)?) -> some View {
|
||||
VStack(alignment: .trailing) {
|
||||
headerSymbol
|
||||
if let item = item {
|
||||
smallVideoView(item: item)
|
||||
} else {
|
||||
smallVideoPlaceholderView
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
}
|
||||
|
||||
func medium(items: [(BaseItemDto, UIImage?)]) -> some View {
|
||||
VStack(alignment: .trailing) {
|
||||
headerSymbol
|
||||
HStack(spacing: 16) {
|
||||
if let firstItem = items[safe: 0] {
|
||||
smallVideoView(item: firstItem)
|
||||
} else {
|
||||
smallVideoPlaceholderView
|
||||
}
|
||||
if let secondItem = items[safe: 1] {
|
||||
smallVideoView(item: secondItem)
|
||||
} else {
|
||||
smallVideoPlaceholderView
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
}
|
||||
|
||||
func large(items: [(BaseItemDto, UIImage?)]) -> some View {
|
||||
VStack(spacing: 0) {
|
||||
if let firstItem = items[safe: 0] {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
if let image = firstItem.1 {
|
||||
Image(uiImage: image)
|
||||
.centerCropped()
|
||||
.innerShadow(color: Color.black.opacity(0.5), radius: 0.5)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(firstItem.0.seriesName ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.white)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
Text("\(firstItem.0.name ?? "") · S\(firstItem.0.parentIndexNumber ?? 0):E\(firstItem.0.indexNumber ?? 0)")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.shadow(radius: 8)
|
||||
.padding(12)
|
||||
}
|
||||
headerSymbol
|
||||
.padding(12)
|
||||
}
|
||||
.clipped()
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
VStack(spacing: 8) {
|
||||
if let secondItem = items[safe: 1] {
|
||||
largeVideoView(item: secondItem)
|
||||
} else {
|
||||
largeVideoPlaceholderView
|
||||
}
|
||||
Divider()
|
||||
if let thirdItem = items[safe: 2] {
|
||||
largeVideoView(item: thirdItem)
|
||||
} else {
|
||||
largeVideoPlaceholderView
|
||||
}
|
||||
}
|
||||
.padding(12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NextUpWidget: Widget {
|
||||
let kind: String = "NextUpWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind,
|
||||
provider: NextUpWidgetProvider()) { entry in
|
||||
NextUpEntryView(entry: entry)
|
||||
}
|
||||
.configurationDisplayName("Next Up")
|
||||
.description("Keep watching where you left off or see what's up next.")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
||||
}
|
||||
}
|
||||
|
||||
struct NextUpWidget_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol"))],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
.preferredColorScheme(.dark)
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
.preferredColorScheme(.dark)
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
.preferredColorScheme(.dark)
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||
.preferredColorScheme(.dark)
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||
.preferredColorScheme(.dark)
|
||||
NextUpEntryView(entry: .init(date: Date(),
|
||||
items: [
|
||||
(.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
(.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"),
|
||||
UIImage(named: "WidgetHeaderSymbol")),
|
||||
],
|
||||
error: nil))
|
||||
.previewContext(WidgetPreviewContext(family: .systemLarge))
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private extension View {
|
||||
func innerShadow(color: Color, radius: CGFloat = 0.1) -> some View {
|
||||
modifier(InnerShadow(color: color, radius: min(max(0, radius), 1)))
|
||||
}
|
||||
}
|
||||
|
||||
private struct InnerShadow: ViewModifier {
|
||||
var color: Color = .gray
|
||||
var radius: CGFloat = 0.1
|
||||
|
||||
private var colors: [Color] {
|
||||
[color.opacity(0.75), color.opacity(0.0), .clear]
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
GeometryReader { geo in
|
||||
content
|
||||
.overlay(LinearGradient(gradient: Gradient(colors: self.colors), startPoint: .top, endPoint: .bottom)
|
||||
.frame(height: self.radius * self.minSide(geo)),
|
||||
alignment: .top)
|
||||
.overlay(LinearGradient(gradient: Gradient(colors: self.colors), startPoint: .bottom, endPoint: .top)
|
||||
.frame(height: self.radius * self.minSide(geo)),
|
||||
alignment: .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
func minSide(_ geo: GeometryProxy) -> CGFloat {
|
||||
CGFloat(3) * min(geo.size.width, geo.size.height) / 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
/*
|
||||
* SwiftFin is subject to the terms of the Mozilla Public
|
||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import KeychainSwift
|
||||
import UIKit
|
||||
|
||||
final class WidgetEnvironment {
|
||||
static let shared = WidgetEnvironment()
|
||||
|
||||
var server: Server!
|
||||
var user: SignedInUser!
|
||||
var header: String?
|
||||
|
||||
init() {
|
||||
update()
|
||||
}
|
||||
|
||||
func update() {
|
||||
let serverRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Server")
|
||||
let servers = try? PersistenceController.shared.container.viewContext.fetch(serverRequest) as? [Server]
|
||||
let savedUserRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SignedInUser")
|
||||
let savedUsers = try? PersistenceController.shared.container.viewContext.fetch(savedUserRequest) as? [SignedInUser]
|
||||
|
||||
server = servers?.first
|
||||
user = savedUsers?.first
|
||||
|
||||
let keychain = KeychainSwift()
|
||||
// need prefix
|
||||
keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain"
|
||||
guard let authToken = keychain.get("AccessToken_\(user?.user_id ?? "")") else {
|
||||
return
|
||||
}
|
||||
|
||||
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
var deviceName = UIDevice.current.name
|
||||
deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current)
|
||||
deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]")
|
||||
|
||||
var header = "MediaBrowser "
|
||||
header.append("Client=\"SwiftFin\", ")
|
||||
header.append("Device=\"\(deviceName)\", ")
|
||||
header.append("DeviceId=\"\(user?.device_uuid ?? "")\", ")
|
||||
header.append("Version=\"\(appVersion ?? "0.0.1")\", ")
|
||||
header.append("Token=\"\(authToken)\"")
|
||||
|
||||
self.header = header
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.me.vigue.jellyfin.mobileclient</string>
|
||||
</array>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)me.vigue.jellyfin.sharedKeychain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
Loading…
Reference in New Issue