├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yaml │ ├── config.yml │ └── feature_request.md ├── release.yml └── workflows │ ├── ci.yml │ ├── lint-pr.yaml │ └── testflight.yml ├── .gitignore ├── .swiftformat ├── Cartfile ├── ChromeCastFramework.json ├── Documentation ├── contributing.md ├── libraries.md └── players.md ├── Gemfile ├── LICENSE.md ├── PreferencesView ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata ├── Package.resolved ├── Package.swift ├── README.md └── Sources │ └── PreferencesView │ ├── Box.swift │ ├── KeyCommandAction.swift │ ├── KeyCommandsBuilder.swift │ ├── PreferenceKeys.swift │ ├── PreferencesView.swift │ ├── PressCommandAction.swift │ ├── PressCommandBuilder.swift │ ├── UIPreferencesHostingController.swift │ ├── UIViewController+Swizzling.swift │ └── ViewExtensions.swift ├── README.md ├── Resources ├── AppIcons │ ├── Dark │ │ ├── AppIcon-dark-blue.svg │ │ ├── AppIcon-dark-green.svg │ │ ├── AppIcon-dark-jellyfin.svg │ │ ├── AppIcon-dark-orange.svg │ │ ├── AppIcon-dark-red.svg │ │ └── AppIcon-dark-yellow.svg │ ├── Inverted-Dark │ │ ├── AppIcon-invertedDark-blue.svg │ │ ├── AppIcon-invertedDark-green.svg │ │ ├── AppIcon-invertedDark-jellyfin.svg │ │ ├── AppIcon-invertedDark-orange.svg │ │ ├── AppIcon-invertedDark-red.svg │ │ └── AppIcon-invertedDark-yellow.svg │ ├── Inverted-Light │ │ ├── AppIcon-invertedLight-blue.svg │ │ ├── AppIcon-invertedLight-green.svg │ │ ├── AppIcon-invertedLight-jellyfin.svg │ │ ├── AppIcon-invertedLight-orange.svg │ │ ├── AppIcon-invertedLight-red.svg │ │ └── AppIcon-invertedLight-yellow.svg │ ├── Light │ │ ├── AppIcon-light-blue.svg │ │ ├── AppIcon-light-green.svg │ │ ├── AppIcon-light-jellyfin.svg │ │ ├── AppIcon-light-orange.svg │ │ ├── AppIcon-light-red.svg │ │ └── AppIcon-light-yellow.svg │ └── Primary │ │ └── AppIcon-primary-primary.svg ├── Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg ├── primary-wide.svg └── testflight.svg ├── Scripts └── Translations │ ├── AlphabetizeStrings.swift │ └── PurgeUnusedStrings.swift ├── Shared ├── AppIcons │ ├── AppIcons.swift │ ├── DarkAppIcon.swift │ ├── InvertedDarkAppIcon.swift │ ├── InvertedLightAppIcon.swift │ ├── LightAppIcon.swift │ └── PrimaryAppIcon.swift ├── Components │ ├── AlternateLayoutView.swift │ ├── AssertionFailureView.swift │ ├── AttributeBadge.swift │ ├── BlurView.swift │ ├── BulletedList.swift │ ├── CenteredLazyVGrid.swift │ ├── ChevronButton.swift │ ├── ConditionalMenu.swift │ ├── FastSVGView.swift │ ├── ImageView.swift │ ├── LetterPickerOrientation.swift │ ├── ListRowCheckbox.swift │ ├── MaxHeightText.swift │ ├── PosterIndicators │ │ ├── FavoriteIndicator.swift │ │ ├── ProgressIndicator.swift │ │ ├── UnwatchedIndicator.swift │ │ └── WatchedIndicator.swift │ ├── ProgressBar.swift │ ├── RedrawOnNotificationView.swift │ ├── RotateContentView.swift │ ├── RowDivider.swift │ ├── SelectorView.swift │ ├── SeparatorHStack.swift │ ├── SeparatorVStack.swift │ ├── SystemImageContentView.swift │ ├── TextPairView.swift │ ├── TruncatedText.swift │ ├── UserProfileImage │ │ ├── UserProfileHeroImage.swift │ │ └── UserProfileImage.swift │ ├── UserProfileRow.swift │ └── WrappedView.swift ├── Coordinators │ ├── AdminDashboardCoordinator.swift │ ├── AppSettingsCoordinator.swift │ ├── BasicNavigationCoordinator.swift │ ├── CustomDeviceProfileCoordinator.swift │ ├── CustomizeSettingsCoordinator.swift │ ├── DownloadListCoordinator.swift │ ├── DownloadTaskCoordinator.swift │ ├── EditCustomDeviceProfileCoordinator.swift │ ├── FilterCoordinator.swift │ ├── HomeCoordinator.swift │ ├── ItemCoordinator.swift │ ├── ItemEditorCoordinator.swift │ ├── ItemImagePickerCoordinator.swift │ ├── ItemImagesCoordinator.swift │ ├── LibraryCoordinator.swift │ ├── LiveTVCoordinator │ │ ├── iOSLiveTVCoordinator.swift │ │ └── tvOSLiveTVCoordinator.swift │ ├── LiveVideoPlayerCoordinator.swift │ ├── MainCoordinator │ │ ├── iOSMainCoordinator.swift │ │ ├── iOSMainTabCoordinator.swift │ │ ├── tvOSMainCoordinator.swift │ │ └── tvOSMainTabCoordinator.swift │ ├── MediaCoordinator.swift │ ├── MediaSourceInfoCoordinator.swift │ ├── PlaybackQualitySettingsCoordinator.swift │ ├── PlaybackSettingsCoordinator.swift │ ├── SearchCoordinator.swift │ ├── SelectUserCoordinator.swift │ ├── SettingsCoordinator.swift │ ├── UserProfileImageCoordinator.swift │ ├── UserProfileSettingsCoordinator.swift │ ├── UserSignInCoordinator.swift │ ├── VideoPlayerCoordinator.swift │ ├── VideoPlayerSettingsCoordinator.swift │ └── VideoPlayerWrapperCoordinator.swift ├── Errors │ └── NetworkError.swift ├── Extensions │ ├── Array.swift │ ├── Binding.swift │ ├── Button.swift │ ├── CGPoint.swift │ ├── CGSize.swift │ ├── Collection.swift │ ├── Color.swift │ ├── CoreStore.swift │ ├── Dictionary.swift │ ├── Double.swift │ ├── Edge.swift │ ├── EdgeInsets.swift │ ├── EnvironmentValues.swift │ ├── Equatable.swift │ ├── Files.swift │ ├── Font.swift │ ├── FormatStyle.swift │ ├── HorizontalAlignment.swift │ ├── Int.swift │ ├── JellyfinAPI │ │ ├── ActiveSessionsPolicy.swift │ │ ├── ActivityLogEntry.swift │ │ ├── BaseItemDto │ │ │ ├── BaseItemDto+Images.swift │ │ │ ├── BaseItemDto+Poster.swift │ │ │ ├── BaseItemDto+VideoPlayerViewModel.swift │ │ │ └── BaseItemDto.swift │ │ ├── BaseItemKind.swift │ │ ├── BaseItemPerson │ │ │ ├── BaseItemPerson+Poster.swift │ │ │ └── BaseItemPerson.swift │ │ ├── ChapterInfo.swift │ │ ├── CodecProfile.swift │ │ ├── CollectionType.swift │ │ ├── DayOfWeek.swift │ │ ├── DeviceInfoDto.swift │ │ ├── DeviceProfile.swift │ │ ├── DeviceType.swift │ │ ├── DirectPlayProfile.swift │ │ ├── DynamicDayOfWeek.swift │ │ ├── ImageBlurHashes.swift │ │ ├── ImageInfo.swift │ │ ├── ImageType.swift │ │ ├── ItemFields.swift │ │ ├── ItemFilter+ItemTrait.swift │ │ ├── JellyfinAPIError.swift │ │ ├── JellyfinClient.swift │ │ ├── LogLevel.swift │ │ ├── LoginFailurePolicy.swift │ │ ├── MaxBitratePolicy.swift │ │ ├── MediaSourceInfo │ │ │ ├── MediaSourceInfo+ItemVideoPlayerViewModel.swift │ │ │ └── MediaSourceInfo.swift │ │ ├── MediaStream.swift │ │ ├── MetadataField.swift │ │ ├── NameGuidPair.swift │ │ ├── ParentalRating.swift │ │ ├── PersonKind.swift │ │ ├── PlayMethod.swift │ │ ├── PlayerStateInfo.swift │ │ ├── RemoteImageInfo.swift │ │ ├── RemoteSearchResult.swift │ │ ├── ServerTicks.swift │ │ ├── SessionInfoDto.swift │ │ ├── SortOrder+ItemSortOrder.swift │ │ ├── SpecialFeatureType.swift │ │ ├── SubtitleProfile.swift │ │ ├── SyncPlayUserAccessType.swift │ │ ├── TaskCompletionStatus.swift │ │ ├── TaskState.swift │ │ ├── TaskTriggerType.swift │ │ ├── TranscodeReason.swift │ │ ├── TranscodingProfile.swift │ │ ├── UserDto.swift │ │ └── Video3DFormat.swift │ ├── NavigationCoordinatable.swift │ ├── Nuke │ │ ├── DataCache.swift │ │ └── ImagePipeline.swift │ ├── Optional.swift │ ├── OrderedDictionary.swift │ ├── PersistentLogHandler.swift │ ├── RatingType.swift │ ├── Sequence.swift │ ├── Set.swift │ ├── String.swift │ ├── Task.swift │ ├── Text.swift │ ├── UIApplication.swift │ ├── UIColor.swift │ ├── UIDevice.swift │ ├── UIGestureRecognizer.swift │ ├── UIHostingController.swift │ ├── UIScreen.swift │ ├── URL.swift │ ├── URLComponents.swift │ ├── URLResponse.swift │ ├── URLSessionConfiguration.swift │ ├── VerticalAlignment.swift │ ├── VideoRangeType.swift │ └── ViewExtensions │ │ ├── Backport │ │ ├── BackPort+ScrollIndicatorVisibility.swift │ │ └── Backport.swift │ │ ├── Modifiers │ │ ├── BackgroundParallaxHeaderModifier.swift │ │ ├── BottomEdgeGradientModifier.swift │ │ ├── ErrorMessage.swift │ │ ├── OnFinalDisappearModifier.swift │ │ ├── OnFirstAppearModifier.swift │ │ ├── OnReceiveNotificationModifier.swift │ │ ├── OnScenePhaseChangedModifier.swift │ │ ├── OnSizeChangedModifier.swift │ │ ├── ScrollIfLargerThanContainerModifier.swift │ │ ├── ScrollViewOffsetModifier.swift │ │ └── SinceLastDisappearModifier.swift │ │ ├── PreferenceKeys.swift │ │ └── ViewExtensions.swift ├── Objects │ ├── AppAppearance.swift │ ├── ArrayBuilder.swift │ ├── BindingBox.swift │ ├── CaseIterablePicker.swift │ ├── ChannelProgram.swift │ ├── CommaStringBuilder.swift │ ├── CurrentDate.swift │ ├── CustomDeviceProfileAction.swift │ ├── DisplayOrder │ │ ├── BoxSetDisplayOrder.swift │ │ └── SeriesDisplayOrder.swift │ ├── Displayable.swift │ ├── Eventful.swift │ ├── GestureAction.swift │ ├── ImageSource.swift │ ├── ItemArrayElements.swift │ ├── ItemFilter │ │ ├── AnyItemFilter.swift │ │ ├── ItemFilter.swift │ │ ├── ItemFilterCollection.swift │ │ ├── ItemFilterType.swift │ │ ├── ItemGenre.swift │ │ ├── ItemLetter.swift │ │ ├── ItemSortBy.swift │ │ ├── ItemTag.swift │ │ └── ItemYear.swift │ ├── ItemViewAttributes.swift │ ├── ItemViewType.swift │ ├── LibraryDisplayType.swift │ ├── LibraryParent │ │ ├── LibraryParent.swift │ │ └── TitledLibraryParent.swift │ ├── MediaComponents │ │ ├── AudoCodec.swift │ │ ├── MediaContainer.swift │ │ ├── SubtitleFormat.swift │ │ └── VideoCodec.swift │ ├── NotificationSet.swift │ ├── OverlayType.swift │ ├── PanDirectionGestureRecognizer.swift │ ├── PlaybackBitrate │ │ ├── PlaybackBitrate.swift │ │ └── PlaybackBitrateTestSize.swift │ ├── PlaybackCompatibility │ │ ├── PlaybackCompatibility+Video.swift │ │ └── PlaybackCompatibility.swift │ ├── PlaybackDeviceProfile.swift │ ├── PlaybackSpeed.swift │ ├── Poster.swift │ ├── PosterDisplayType.swift │ ├── RepeatingTimer.swift │ ├── RoundedCorner.swift │ ├── ScalingButtonStyle.swift │ ├── SelectUserServerSelection.swift │ ├── SeriesStatus.swift │ ├── SliderType.swift │ ├── Stateful.swift │ ├── Storable.swift │ ├── SupportedCaseIterable.swift │ ├── SystemImageable.swift │ ├── TextPair.swift │ ├── TimeStampType.swift │ ├── TimerProxy.swift │ ├── TrailerSelection.swift │ ├── TrailingTimestampType.swift │ ├── Trie.swift │ ├── UserAccessPolicy.swift │ ├── UserPermissions.swift │ ├── UserSignInState.swift │ ├── Utilities.swift │ ├── VideoPlayerActionButton.swift │ ├── VideoPlayerJumpLength.swift │ └── VideoPlayerType │ │ ├── VideoPlayerType+Native.swift │ │ ├── VideoPlayerType+Shared.swift │ │ ├── VideoPlayerType+Swiftfin.swift │ │ └── VideoPlayerType.swift ├── ServerDiscovery │ ├── ServerDiscovery.swift │ └── ServerResponse.swift ├── Services │ ├── DownloadManager.swift │ ├── DownloadTask.swift │ ├── Keychain.swift │ ├── LogManager.swift │ ├── Notifications.swift │ ├── SwiftfinDefaults.swift │ └── UserSession.swift ├── Strings │ └── Strings.swift ├── SwiftfinStore │ ├── StoredValue │ │ ├── StoredValue.swift │ │ ├── StoredValues+Server.swift │ │ ├── StoredValues+Temp.swift │ │ └── StoredValues+User.swift │ ├── SwiftfinStore+Mappings.swift │ ├── SwiftfinStore+ServerState.swift │ ├── SwiftfinStore.swift │ ├── SwiftinStore+UserState.swift │ ├── V1Schema │ │ ├── SwiftfinStore+V1.swift │ │ ├── V1ServerModel.swift │ │ └── V1UserModel.swift │ └── V2Schema │ │ ├── SwiftfinStore+V2.swift │ │ ├── V2AnyData.swift │ │ ├── V2ServerModel.swift │ │ └── V2UserModel.swift └── ViewModels │ ├── AdminDashboard │ ├── APIKeysViewModel.swift │ ├── ActiveSessionsViewModel.swift │ ├── AddServerUserViewModel.swift │ ├── DeviceDetailViewModel.swift │ ├── DevicesViewModel.swift │ ├── ServerActivityDetailViewModel.swift │ ├── ServerActivityViewModel.swift │ ├── ServerTaskObserver.swift │ ├── ServerTasksViewModel.swift │ ├── ServerUserAdminViewModel.swift │ └── ServerUsersViewModel.swift │ ├── ChannelLibraryViewModel.swift │ ├── ConnectToServerViewModel.swift │ ├── DownloadListViewModel.swift │ ├── FilterViewModel.swift │ ├── HomeViewModel.swift │ ├── ItemAdministration │ ├── DeleteItemViewModel.swift │ ├── IdentifyItemViewModel.swift │ ├── ItemEditorViewModel │ │ ├── GenreEditorViewModel.swift │ │ ├── ItemEditorViewModel.swift │ │ ├── PeopleEditorViewModel.swift │ │ ├── StudioEditorViewModel.swift │ │ └── TagEditorViewModel.swift │ ├── ItemImagesViewModel.swift │ ├── RefreshMetadataViewModel.swift │ └── RemoteImageInfoViewModel.swift │ ├── ItemViewModel │ ├── CollectionItemViewModel.swift │ ├── EpisodeItemViewModel.swift │ ├── ItemViewModel.swift │ ├── MovieItemViewModel.swift │ ├── SeasonItemViewModel.swift │ └── SeriesItemViewModel.swift │ ├── LibraryViewModel │ ├── ItemLibraryViewModel.swift │ ├── LatestInLibraryViewModel.swift │ ├── NextUpLibraryViewModel.swift │ ├── PagingLibraryViewModel.swift │ └── RecentlyAddedViewModel.swift │ ├── LiveVideoPlayerManager.swift │ ├── MediaViewModel │ ├── MediaType.swift │ └── MediaViewModel.swift │ ├── ParentalRatingsViewModel.swift │ ├── ProgramsViewModel.swift │ ├── QuickConnectAuthorizeViewModel.swift │ ├── ResetUserPasswordViewModel.swift │ ├── SearchViewModel.swift │ ├── SelectUserViewModel.swift │ ├── ServerCheckViewModel.swift │ ├── ServerConnectionViewModel.swift │ ├── ServerLogsViewModel.swift │ ├── SettingsViewModel.swift │ ├── UserLocalSecurityViewModel.swift │ ├── UserProfileImageViewModel.swift │ ├── UserSignInViewModel.swift │ ├── VideoPlayerManager │ ├── DownloadVideoPlayerManager.swift │ ├── OnlineVideoPlayerManager.swift │ └── VideoPlayerManager.swift │ ├── VideoPlayerViewModel.swift │ └── ViewModel.swift ├── Swiftfin tvOS ├── App │ ├── PreferenceUIHosting │ │ ├── PreferenceUIHostingController.swift │ │ └── PreferenceUIHostingSwizzling.swift │ └── SwiftfinApp.swift ├── Components │ ├── CinematicBackgroundView.swift │ ├── CinematicItemSelector.swift │ ├── DotHStack.swift │ ├── EnumPickerView.swift │ ├── ErrorView.swift │ ├── LandscapePosterProgressBar.swift │ ├── ListRowButton.swift │ ├── ListRowMenu.swift │ ├── NonePosterButton.swift │ ├── OrderedSectionSelectorView.swift │ ├── PosterButton.swift │ ├── PosterHStack.swift │ ├── SFSymbolButton.swift │ ├── SeeAllPosterButton.swift │ ├── ServerButton.swift │ ├── SplitFormWindowView.swift │ ├── SplitLoginWindowView.swift │ └── StepperView.swift ├── Extensions │ └── View │ │ ├── Modifiers │ │ └── NavigationBarMenuButton.swift │ │ └── View-tvOS.swift ├── ImageButtonStyle.swift ├── Objects │ └── FocusGuide.swift ├── Resources │ ├── Assets.xcassets │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - App Store.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ ├── 1280x768-back.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ ├── 512.png │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ ├── 400x240-back.png │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── Webp.net-resizeimage.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ ├── 216.png │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Webp.net-resizeimage-2.png │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Untitled-1.png │ │ │ │ ├── Untitled-2.png │ │ │ │ ├── top shelf-1.png │ │ │ │ └── top shelf.png │ │ │ └── Top Shelf Image.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Untitled-1.png │ │ │ │ ├── Untitled-2.png │ │ │ │ ├── top shelf-1.png │ │ │ │ └── top shelf.png │ │ ├── Contents.json │ │ ├── jellyfin-blob-blue.imageset │ │ │ ├── Contents.json │ │ │ └── jellyfin-blob.svg │ │ ├── tomato.fresh.symbolset │ │ │ ├── Contents.json │ │ │ └── tomato.fresh.svg │ │ └── tomato.rotten.symbolset │ │ │ ├── Contents.json │ │ │ └── tomato.rotten.svg │ └── Info.plist └── Views │ ├── AppLoadingView.swift │ ├── AppSettingsView │ ├── AppSettingsView.swift │ └── Components │ │ ├── HourMinutePicker.swift │ │ └── SignOutIntervalSection.swift │ ├── ChannelLibraryView │ ├── ChannelLibraryView.swift │ └── Components │ │ └── WideChannelGridItem.swift │ ├── ConnectToServerView │ ├── Components │ │ └── LocalServerButton.swift │ └── ConnectToServerView.swift │ ├── FontPickerView.swift │ ├── HomeView │ ├── Components │ │ ├── CinematicRecentlyAddedView.swift │ │ ├── CinematicResumeItemView.swift │ │ ├── LatestInLibraryView.swift │ │ ├── NextUpView.swift │ │ └── RecentlyAddedView.swift │ └── HomeView.swift │ ├── ItemOverviewView.swift │ ├── ItemView │ ├── CinematicCollectionItemView.swift │ ├── CinematicEpisodeItemView.swift │ ├── CinematicItemAboutView.swift │ ├── CinematicItemViewTopRow.swift │ ├── CinematicSeasonItemView.swift │ ├── CollectionItemView │ │ ├── CollectionItemContentView.swift │ │ └── CollectionItemView.swift │ ├── Components │ │ ├── AboutView │ │ │ ├── AboutView.swift │ │ │ └── Components │ │ │ │ ├── AboutViewCard.swift │ │ │ │ ├── ImageCard.swift │ │ │ │ ├── MediaSourcesCard.swift │ │ │ │ ├── OverviewCard.swift │ │ │ │ └── RatingsCard.swift │ │ ├── ActionButton.swift │ │ ├── ActionButtonHStack │ │ │ ├── ActionButtonHStack.swift │ │ │ └── Components │ │ │ │ ├── RefreshMetadataButton.swift │ │ │ │ └── TrailerMenu.swift │ │ ├── AttributeHStack.swift │ │ ├── CastAndCrewHStack.swift │ │ ├── EpisodeSelector │ │ │ ├── Components │ │ │ │ ├── EmptyCard.swift │ │ │ │ ├── EpisodeCard.swift │ │ │ │ ├── EpisodeContent.swift │ │ │ │ ├── ErrorCard.swift │ │ │ │ ├── HStacks │ │ │ │ │ ├── EpisodeHStack.swift │ │ │ │ │ └── SeasonHStack.swift │ │ │ │ └── LoadingCard.swift │ │ │ └── EpisodeSelector.swift │ │ ├── PlayButton │ │ │ ├── Components │ │ │ │ └── VersionMenu.swift │ │ │ └── PlayButton.swift │ │ ├── SimilarItemsHStack.swift │ │ └── SpecialFeaturesHStack.swift │ ├── EpisodeItemView │ │ ├── EpisodeItemContentView.swift │ │ └── EpisodeItemView.swift │ ├── ItemView.swift │ ├── MovieItemView │ │ ├── MovieItemContentView.swift │ │ └── MovieItemView.swift │ ├── ScrollViews │ │ └── CinematicScrollView.swift │ └── SeriesItemView │ │ ├── SeriesItemContentView.swift │ │ └── SeriesItemView.swift │ ├── LearnMoreModal.swift │ ├── MediaSourceInfoView.swift │ ├── MediaView │ ├── Components │ │ └── MediaItem.swift │ └── MediaView.swift │ ├── PagingLibraryView │ ├── Components │ │ ├── LibraryRow.swift │ │ └── ListRow.swift │ └── PagingLibraryView.swift │ ├── ProgramsView │ ├── Components │ │ ├── ProgramButtonContent.swift │ │ └── ProgramProgressOverlay.swift │ └── ProgramsView.swift │ ├── QuickConnectView.swift │ ├── SearchView.swift │ ├── SelectUserView │ ├── Components │ │ ├── AddUserBottomButton.swift │ │ ├── AddUserGridButton.swift │ │ ├── SelectUserBottomBar.swift │ │ ├── ServerSelectionMenu.swift │ │ └── UserGridButton.swift │ └── SelectUserView.swift │ ├── ServerDetailView.swift │ ├── SettingsView │ ├── CustomDeviceProfileSettingsView │ │ ├── Components │ │ │ ├── CustomProfileButton.swift │ │ │ └── EditCustomDeviceProfileView.swift │ │ └── CustomDeviceProfileSettingsView.swift │ ├── CustomizeViewsSettings │ │ ├── Components │ │ │ ├── ListColumnsPickerView.swift │ │ │ └── Sections │ │ │ │ ├── HomeSection.swift │ │ │ │ ├── ItemSection.swift │ │ │ │ └── LibrarySection.swift │ │ └── CustomizeViewsSettings.swift │ ├── ExperimentalSettingsView.swift │ ├── IndicatorSettingsView.swift │ ├── PlaybackQualitySettingsView.swift │ ├── SettingsView.swift │ ├── UserProfileSettingsView │ │ ├── UserLocalSecurityView.swift │ │ └── UserProfileSettingsView.swift │ └── VideoPlayerSettingsView.swift │ ├── UserSignInView │ ├── Components │ │ └── PublicUserButton.swift │ └── UserSignInView.swift │ └── VideoPlayer │ ├── Components │ └── LoadingView.swift │ ├── LiveNativeVideoPlayer.swift │ ├── LiveOverlays │ ├── Components │ │ └── LiveBottomBarView.swift │ ├── LiveLoadingOverlay.swift │ ├── LiveMainOverlay.swift │ └── LiveOverlay.swift │ ├── LiveVideoPlayer.swift │ ├── NativeVideoPlayer.swift │ ├── Overlays │ ├── ChapterOverlay.swift │ ├── Components │ │ ├── ActionButtons │ │ │ ├── ActionButtons.swift │ │ │ ├── AutoPlayActionButton.swift │ │ │ ├── ChaptersActionButton.swift │ │ │ ├── PlayNextItemActionButton.swift │ │ │ ├── PlayPreviousItemActionButton.swift │ │ │ └── SubtitleButton.swift │ │ ├── BarActionButtons.swift │ │ ├── BottomBarView.swift │ │ └── tvOSSLider │ │ │ ├── SliderView.swift │ │ │ └── tvOSSlider.swift │ ├── ConfirmCloseOverlay.swift │ ├── MainOverlay.swift │ ├── Overlay.swift │ └── SmallMenuOverlay.swift │ └── VideoPlayer.swift ├── Swiftfin.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ ├── IDETemplateMacros.plist │ └── xcschemes │ ├── Swiftfin tvOS.xcscheme │ └── Swiftfin.xcscheme ├── Swiftfin ├── App │ ├── AppDelegate.swift │ ├── SwiftfinApp+ValueObservation.swift │ └── SwiftfinApp.swift ├── Components │ ├── BasicStepper.swift │ ├── CircularProgressView.swift │ ├── CountryPicker.swift │ ├── DelayedProgressView.swift │ ├── DotHStack.swift │ ├── ErrorView.swift │ ├── GestureView.swift │ ├── HourMinutePicker.swift │ ├── LandscapePosterProgressBar.swift │ ├── LanguagePicker.swift │ ├── LearnMoreButton.swift │ ├── LetterPickerBar │ │ ├── Components │ │ │ └── LetterPickerButton.swift │ │ └── LetterPickerBar.swift │ ├── ListRow.swift │ ├── ListRowButton.swift │ ├── ListTitleSection.swift │ ├── NavigationBarFilterDrawer │ │ ├── FilterDrawerButton.swift │ │ └── NavigationBarFilterDrawer.swift │ ├── OrderedSectionSelectorView.swift │ ├── PillHStack.swift │ ├── PosterButton.swift │ ├── PosterHStack.swift │ ├── PrimaryButton.swift │ ├── SeeAllButton.swift │ ├── SettingsBarButton.swift │ ├── Slider │ │ ├── CapsuleSlider.swift │ │ ├── Slider.swift │ │ └── ThumbSlider.swift │ ├── SplitContentView.swift │ ├── UnmaskSecureField.swift │ ├── UpdateView.swift │ ├── Video3DFormatPicker.swift │ └── iOS15View.swift ├── Extensions │ ├── ButtonStyle-iOS.swift │ ├── Label-iOS.swift │ └── View │ │ ├── Modifiers │ │ ├── DetectOrientationModifier.swift │ │ ├── NavigationBarCloseButton.swift │ │ ├── NavigationBarDrawerButtons │ │ │ ├── NavigationBarDrawerModifier.swift │ │ │ └── NavigationBarDrawerView.swift │ │ ├── NavigationBarMenuButton.swift │ │ └── NavigationBarOffset │ │ │ ├── NavigationBarOffsetModifier.swift │ │ │ └── NavigationBarOffsetView.swift │ │ └── View-iOS.swift ├── Objects │ ├── AppURLHandler.swift │ └── DeepLink.swift ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon-dark-blue.imageset │ │ │ ├── AppIcon-dark-blue.svg │ │ │ └── Contents.json │ │ ├── AppIcon-dark-green.imageset │ │ │ ├── AppIcon-dark-green.svg │ │ │ └── Contents.json │ │ ├── AppIcon-dark-jellyfin.imageset │ │ │ ├── AppIcon-dark-jellyfin.svg │ │ │ └── Contents.json │ │ ├── AppIcon-dark-orange.imageset │ │ │ ├── AppIcon-dark-orange.svg │ │ │ └── Contents.json │ │ ├── AppIcon-dark-red.imageset │ │ │ ├── AppIcon-dark-red.svg │ │ │ └── Contents.json │ │ ├── AppIcon-dark-yellow.imageset │ │ │ ├── AppIcon-dark-yellow.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-blue.imageset │ │ │ ├── AppIcon-invertedDark-blue.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-green.imageset │ │ │ ├── AppIcon-invertedDark-green.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-jellyfin.imageset │ │ │ ├── AppIcon-invertedDark-jellyfin.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-orange.imageset │ │ │ ├── AppIcon-invertedDark-orange.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-red.imageset │ │ │ ├── AppIcon-invertedDark-red.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedDark-yellow.imageset │ │ │ ├── AppIcon-invertedDark-yellow.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-blue.imageset │ │ │ ├── AppIcon-invertedLight-blue.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-green.imageset │ │ │ ├── AppIcon-invertedLight-green.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-jellyfin.imageset │ │ │ ├── AppIcon-invertedLight-jellyfin.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-orange.imageset │ │ │ ├── AppIcon-invertedLight-orange.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-red.imageset │ │ │ ├── AppIcon-invertedLight-red.svg │ │ │ └── Contents.json │ │ ├── AppIcon-invertedLight-yellow.imageset │ │ │ ├── AppIcon-invertedLight-yellow.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-blue.imageset │ │ │ ├── AppIcon-light-blue.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-green.imageset │ │ │ ├── AppIcon-light-green.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-jellyfin.imageset │ │ │ ├── AppIcon-light-jellyfin.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-orange.imageset │ │ │ ├── AppIcon-light-orange.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-red.imageset │ │ │ ├── AppIcon-light-red.svg │ │ │ └── Contents.json │ │ ├── AppIcon-light-yellow.imageset │ │ │ ├── AppIcon-light-yellow.svg │ │ │ └── Contents.json │ │ ├── AppIcon-primary-primary.imageset │ │ │ ├── AppIcon-primary-primary.svg │ │ │ └── Contents.json │ │ ├── AppIcons │ │ │ ├── Contents.json │ │ │ ├── Dark │ │ │ │ ├── AppIcon-dark-blue.appiconset │ │ │ │ │ ├── AppIcon-dark-blue.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-dark-green.appiconset │ │ │ │ │ ├── AppIcon-dark-green.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-dark-jellyfin.appiconset │ │ │ │ │ ├── AppIcon-dark-jellyfin.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-dark-orange.appiconset │ │ │ │ │ ├── AppIcon-dark-orange.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-dark-red.appiconset │ │ │ │ │ ├── AppIcon-dark-red.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-dark-yellow.appiconset │ │ │ │ │ ├── AppIcon-dark-yellow.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Inverted-Dark │ │ │ │ ├── AppIcon-invertedDark-blue.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── blue.png │ │ │ │ ├── AppIcon-invertedDark-green.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── green.png │ │ │ │ ├── AppIcon-invertedDark-jellyfin.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── jellyfin.png │ │ │ │ ├── AppIcon-invertedDark-orange.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── orange.png │ │ │ │ ├── AppIcon-invertedDark-red.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── red.png │ │ │ │ ├── AppIcon-invertedDark-yellow.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── yellow.png │ │ │ │ └── Contents.json │ │ │ ├── Inverted-Light │ │ │ │ ├── AppIcon-invertedLight-blue.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── blue.png │ │ │ │ ├── AppIcon-invertedLight-green.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── green.png │ │ │ │ ├── AppIcon-invertedLight-jellyfin.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── jellyfin.png │ │ │ │ ├── AppIcon-invertedLight-orange.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── orange.png │ │ │ │ ├── AppIcon-invertedLight-red.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── red.png │ │ │ │ ├── AppIcon-invertedLight-yellow.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── yellow.png │ │ │ │ └── Contents.json │ │ │ ├── Light │ │ │ │ ├── AppIcon-light-blue.appiconset │ │ │ │ │ ├── AppIcon-light-blue.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-light-green.appiconset │ │ │ │ │ ├── AppIcon-light-green.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-light-jellyfin.appiconset │ │ │ │ │ ├── AppIcon-light-jellyfin.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-light-orange.appiconset │ │ │ │ │ ├── AppIcon-light-orange.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-light-red.appiconset │ │ │ │ │ ├── AppIcon-light-red.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon-light-yellow.appiconset │ │ │ │ │ ├── AppIcon-light-yellow.png │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Primary │ │ │ │ ├── AppIcon-primary-primary.appiconset │ │ │ │ ├── AppIcon-primary-primary.png │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── DeviceIcons │ │ │ ├── Browsers │ │ │ │ ├── Contents.json │ │ │ │ ├── Device-browser-chrome.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── chrome.svg │ │ │ │ ├── Device-browser-edge.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── edge.svg │ │ │ │ ├── Device-browser-edgechromium.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── edgechromium.svg │ │ │ │ ├── Device-browser-firefox.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── firefox.svg │ │ │ │ ├── Device-browser-html5.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── html5.svg │ │ │ │ ├── Device-browser-msie.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── msie.svg │ │ │ │ ├── Device-browser-opera.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── opera.svg │ │ │ │ └── Device-browser-safari.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── safari.svg │ │ │ ├── Clients │ │ │ │ ├── Contents.json │ │ │ │ ├── Device-client-android.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── android.svg │ │ │ │ ├── Device-client-apple.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── apple.svg │ │ │ │ ├── Device-client-finamp.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── finamp.svg │ │ │ │ ├── Device-client-kodi.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── kodi.svg │ │ │ │ ├── Device-client-playstation.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── playstation.svg │ │ │ │ ├── Device-client-roku.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── roku.svg │ │ │ │ ├── Device-client-samsungtv.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── samsungtv.svg │ │ │ │ ├── Device-client-webos.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── webOS.svg │ │ │ │ ├── Device-client-windows.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── windows.svg │ │ │ │ └── Device-client-xbox.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── xbox.svg │ │ │ ├── Contents.json │ │ │ └── Other │ │ │ │ ├── Contents.json │ │ │ │ ├── Device-other-homeassistant.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── home-assistant.svg │ │ │ │ └── Device-other-other.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── other.svg │ │ ├── git.commit.symbolset │ │ │ ├── Contents.json │ │ │ └── git.commit.svg │ │ ├── jellyfin-blob-blue.imageset │ │ │ ├── Contents.json │ │ │ └── jellyfin-blob.svg │ │ ├── logo.github.symbolset │ │ │ ├── Contents.json │ │ │ └── logo.github.svg │ │ ├── tomato.fresh.symbolset │ │ │ ├── Contents.json │ │ │ └── tomato.fresh.svg │ │ └── tomato.rotten.symbolset │ │ │ ├── Contents.json │ │ │ └── tomato.rotten.svg │ ├── Info.plist │ └── Swiftfin.entitlements └── Views │ ├── AboutAppView.swift │ ├── AdminDashboardView │ ├── APIKeyView │ │ ├── APIKeysView.swift │ │ └── Components │ │ │ └── APIKeysRow.swift │ ├── ActiveSessions │ │ ├── ActiveSessionDetailView │ │ │ ├── Components │ │ │ │ ├── StreamSection.swift │ │ │ │ └── TranscodeSection.swift │ │ │ └── ServerSessionDetailView.swift │ │ └── ActiveSessionsView │ │ │ ├── ActiveSessionsView.swift │ │ │ └── Components │ │ │ ├── ActiveSessionProgressSection.swift │ │ │ └── ActiveSessionRow.swift │ ├── AdminDashboardView.swift │ ├── Components │ │ ├── DeviceSection.swift │ │ ├── MediaItemSection.swift │ │ └── UserSection.swift │ ├── ServerActivity │ │ ├── ServerActivityDetailsView │ │ │ └── ServerActivityDetailsView.swift │ │ └── ServerActivityView │ │ │ ├── Components │ │ │ └── ServerActivityEntry.swift │ │ │ └── ServerActivityView.swift │ ├── ServerDevices │ │ ├── DeviceDetailsView │ │ │ ├── Components │ │ │ │ └── Sections │ │ │ │ │ ├── CompatibilitiesSection.swift │ │ │ │ │ └── CustomDeviceNameSection.swift │ │ │ └── DeviceDetailsView.swift │ │ └── DevicesView │ │ │ ├── Components │ │ │ └── DeviceRow.swift │ │ │ └── DevicesView.swift │ ├── ServerLogsView │ │ └── ServerLogsView.swift │ ├── ServerTasks │ │ ├── AddTaskTriggerView │ │ │ ├── AddTaskTriggerView.swift │ │ │ └── Components │ │ │ │ ├── DayOfWeekRow.swift │ │ │ │ ├── IntervalRow.swift │ │ │ │ ├── TimeLimitSection.swift │ │ │ │ ├── TimeRow.swift │ │ │ │ └── TriggerTypeRow.swift │ │ ├── EditServerTaskView │ │ │ ├── Components │ │ │ │ ├── Sections │ │ │ │ │ ├── DetailsSection.swift │ │ │ │ │ ├── LastErrorSection.swift │ │ │ │ │ ├── LastRunSection.swift │ │ │ │ │ ├── ServerTaskProgressSection.swift │ │ │ │ │ └── TriggersSection.swift │ │ │ │ └── TriggerRow.swift │ │ │ └── EditServerTaskView.swift │ │ └── ServerTasksView │ │ │ ├── Components │ │ │ ├── DestructiveServerTask.swift │ │ │ └── ServerTaskRow.swift │ │ │ └── ServerTasksView.swift │ └── ServerUsers │ │ ├── AddServerUserView │ │ └── AddServerUserView.swift │ │ ├── ServerUserDetailsView │ │ └── ServerUserDetailsView.swift │ │ ├── ServerUserSettings │ │ ├── ServerUserAccessSchedule │ │ │ ├── AddAccessScheduleView │ │ │ │ └── AddAccessScheduleView.swift │ │ │ └── EditAccessScheduleView │ │ │ │ ├── Components │ │ │ │ └── EditAccessScheduleRow.swift │ │ │ │ └── EditAccessScheduleView.swift │ │ ├── ServerUserAccessTags │ │ │ ├── AddServerUserAccessTagsView │ │ │ │ ├── AddServerUserAccessTagsView.swift │ │ │ │ └── Components │ │ │ │ │ ├── AccessTagSearchResultsSection.swift │ │ │ │ │ └── TagInput.swift │ │ │ └── EditServerUserAccessTagsView │ │ │ │ ├── Components │ │ │ │ └── EditAccessTagRow.swift │ │ │ │ └── EditServerUserAccessTagsView.swift │ │ ├── ServerUserAccessView │ │ │ └── ServerUserAccessView.swift │ │ ├── ServerUserDeviceAccessView │ │ │ └── ServerUserDeviceAccessView.swift │ │ ├── ServerUserLiveTVAccessView │ │ │ └── ServerUserLiveTVAccessView.swift │ │ ├── ServerUserParentalRatingView │ │ │ └── ServerUserParentalRatingView.swift │ │ └── ServerUserPermissionsView │ │ │ ├── Components │ │ │ └── Sections │ │ │ │ ├── ExternalAccessSection.swift │ │ │ │ ├── ManagementSection.swift │ │ │ │ ├── MediaPlaybackSection.swift │ │ │ │ ├── PermissionSection.swift │ │ │ │ ├── RemoteControlSection.swift │ │ │ │ ├── SessionsSection.swift │ │ │ │ ├── StatusSection.swift │ │ │ │ └── SyncPlaySection.swift │ │ │ └── ServerUserPermissionsView.swift │ │ └── ServerUsersView │ │ ├── Components │ │ └── ServerUsersRow.swift │ │ └── ServerUsersView.swift │ ├── AppIconSelectorView.swift │ ├── AppLoadingView.swift │ ├── AppSettingsView │ ├── AppSettingsView.swift │ └── Components │ │ └── SignOutIntervalSection.swift │ ├── ChannelLibraryView │ ├── ChannelLibraryView.swift │ └── Components │ │ ├── CompactChannelView.swift │ │ └── DetailedChannelView.swift │ ├── ConnectToServerView.swift │ ├── DownloadListView.swift │ ├── DownloadTaskView │ ├── DownloadTaskContentView.swift │ └── DownloadTaskView.swift │ ├── EditServerView.swift │ ├── FilterView.swift │ ├── FontPickerView.swift │ ├── HomeView │ ├── Components │ │ ├── ContinueWatchingView.swift │ │ ├── LatestInLibraryView.swift │ │ ├── NextUpView.swift │ │ └── RecentlyAddedView.swift │ └── HomeView.swift │ ├── ItemEditorView │ ├── Components │ │ └── RefreshMetadataButton.swift │ ├── IdentifyItemView │ │ ├── Components │ │ │ ├── RemoteSearchResultRow.swift │ │ │ └── RemoteSearchResultView.swift │ │ └── IdentifyItemView.swift │ ├── ItemEditorView.swift │ ├── ItemElements │ │ ├── AddItemElementView │ │ │ ├── AddItemElementView.swift │ │ │ └── Components │ │ │ │ ├── NameInput.swift │ │ │ │ └── SearchResultsSection.swift │ │ └── EditItemElementView │ │ │ ├── Components │ │ │ └── EditItemElementRow.swift │ │ │ └── EditItemElementView.swift │ ├── ItemImages │ │ ├── AddItemImageView.swift │ │ ├── ItemImageDetailsView │ │ │ ├── Components │ │ │ │ ├── ItemImageDetailsDeleteButton.swift │ │ │ │ ├── ItemImageDetailsDetailsSection.swift │ │ │ │ └── ItemImageDetailsHeaderSection.swift │ │ │ └── ItemImageDetailsView.swift │ │ ├── ItemImagesView.swift │ │ └── ItemPhotoPickerView │ │ │ ├── Components │ │ │ └── ItemPhotoCropView.swift │ │ │ └── ItemPhotoPickerView.swift │ └── ItemMetadata │ │ ├── AddItemElementView │ │ ├── AddItemElementView.swift │ │ └── Components │ │ │ ├── NameInput.swift │ │ │ └── SearchResultsSection.swift │ │ ├── EditItemElementView │ │ ├── Components │ │ │ └── EditItemElementRow.swift │ │ └── EditItemElementView.swift │ │ └── EditMetadataView │ │ ├── Components │ │ └── Sections │ │ │ ├── DateSection.swift │ │ │ ├── DisplayOrderSection.swift │ │ │ ├── EpisodeSection.swift │ │ │ ├── LocalizationSection.swift │ │ │ ├── LockMetadataSection.swift │ │ │ ├── MediaFormatSection.swift │ │ │ ├── OverviewSection.swift │ │ │ ├── ParentialRatingsSection.swift │ │ │ ├── ReviewsSection.swift │ │ │ ├── SeriesSection.swift │ │ │ └── TitleSection.swift │ │ └── EditMetadataView.swift │ ├── ItemOverviewView.swift │ ├── ItemView │ ├── CollectionItemContentView.swift │ ├── Components │ │ ├── AboutView │ │ │ ├── AboutView.swift │ │ │ └── Components │ │ │ │ ├── AboutView+Card.swift │ │ │ │ ├── ImageCard.swift │ │ │ │ ├── MediaSourcesCard.swift │ │ │ │ ├── OverviewCard.swift │ │ │ │ └── RatingsCard.swift │ │ ├── ActionButton │ │ │ └── ActionButton.swift │ │ ├── ActionButtonHStack │ │ │ ├── ActionButtonHStack.swift │ │ │ └── Components │ │ │ │ ├── TrailerMenu.swift │ │ │ │ └── VersionMenu.swift │ │ ├── AttributeHStack.swift │ │ ├── CastAndCrewHStack.swift │ │ ├── DownloadTaskButton.swift │ │ ├── EpisodeSelector │ │ │ ├── Components │ │ │ │ ├── EmptyCard.swift │ │ │ │ ├── EpisodeCard.swift │ │ │ │ ├── EpisodeContent.swift │ │ │ │ ├── EpisodeHStack.swift │ │ │ │ ├── ErrorCard.swift │ │ │ │ └── LoadingCard.swift │ │ │ └── EpisodeSelector.swift │ │ ├── GenresHStack.swift │ │ ├── OffsetScrollView.swift │ │ ├── OverviewView.swift │ │ ├── PlayButton.swift │ │ ├── SimilarItemsHStack.swift │ │ ├── SpecialFeatureHStack.swift │ │ └── StudiosHStack.swift │ ├── EpisodeItemContentView.swift │ ├── ItemView.swift │ ├── MovieItemContentView.swift │ ├── ScrollViews │ │ ├── CinematicScrollView.swift │ │ ├── CompactLogoScrollView.swift │ │ ├── CompactPortraitScrollView.swift │ │ ├── SimpleScrollView.swift │ │ └── iPadOSCinematicScrollView.swift │ └── SeriesItemContentView.swift │ ├── MediaSourceInfoView.swift │ ├── MediaStreamInfoView.swift │ ├── MediaView │ ├── Components │ │ └── MediaItem.swift │ └── MediaView.swift │ ├── PagingLibraryView │ ├── Components │ │ ├── LibraryRow.swift │ │ └── LibraryViewTypeToggle.swift │ └── PagingLibraryView.swift │ ├── PhotoPickerView │ ├── Components │ │ └── PhotoCropView.swift │ └── PhotoPickerView.swift │ ├── ProgramsView │ ├── Components │ │ ├── ProgramButtonContent.swift │ │ └── ProgramProgressOverlay.swift │ └── ProgramsView.swift │ ├── QuickConnectView.swift │ ├── ResetUserPasswordView │ └── ResetUserPasswordView.swift │ ├── SearchView.swift │ ├── SelectUserView │ ├── Components │ │ ├── AddUserGridButton.swift │ │ ├── AddUserListRow.swift │ │ ├── ServerSelectionMenu.swift │ │ ├── UserGridButton.swift │ │ └── UserListRow.swift │ └── SelectUserView.swift │ ├── ServerCheckView.swift │ ├── SettingsView │ ├── CustomDeviceProfileSettingsView │ │ ├── Components │ │ │ ├── CustomProfileButton.swift │ │ │ └── EditCustomDeviceProfileView.swift │ │ └── CustomDeviceProfileSettingsView.swift │ ├── CustomizeViewsSettings │ │ ├── Components │ │ │ └── Sections │ │ │ │ ├── HomeSection.swift │ │ │ │ └── ItemSection.swift │ │ └── CustomizeViewsSettings.swift │ ├── DebugSettingsView.swift │ ├── ExperimentalSettingsView.swift │ ├── GestureSettingsView.swift │ ├── IndicatorSettingsView.swift │ ├── NativeVideoPlayerSettingsView.swift │ ├── PlaybackQualitySettingsView.swift │ ├── SettingsView │ │ └── SettingsView.swift │ ├── UserProfileSettingsView │ │ ├── QuickConnectAuthorizeView.swift │ │ ├── UserLocalSecurityView.swift │ │ └── UserProfileSettingsView.swift │ └── VideoPlayerSettingsView │ │ ├── Components │ │ ├── ActionButtonSelectorView.swift │ │ └── Sections │ │ │ ├── ButtonSection.swift │ │ │ ├── SliderSection.swift │ │ │ ├── SubtitleSection.swift │ │ │ ├── TimestampSection.swift │ │ │ └── TransitionSection.swift │ │ └── VideoPlayerSettingsView.swift │ ├── UserProfileImagePicker │ ├── Components │ │ └── UserProfileImageCropView.swift │ └── UserProfileImagePickerView.swift │ ├── UserSignInView │ ├── Components │ │ ├── PublicUserRow.swift │ │ └── UserSignInSecurityView.swift │ └── UserSignInView.swift │ └── VideoPlayer │ ├── Components │ ├── LoadingView.swift │ └── PlaybackSettingsView.swift │ ├── LiveNativeVideoPlayer.swift │ ├── LiveOverlays │ ├── Components │ │ ├── LiveBottomBarView.swift │ │ ├── LiveTopBarView.swift │ │ └── PlaybackButtons │ │ │ ├── LiveLargePlaybackButtons.swift │ │ │ └── LiveSmallPlaybackButton.swift │ ├── LiveMainOverlay.swift │ └── LiveOverlay.swift │ ├── LiveVideoPlayer.swift │ ├── NativeVideoPlayer.swift │ ├── Overlays │ ├── ChapterOverlay.swift │ ├── Components │ │ ├── ActionButtons │ │ │ ├── ActionButtons.swift │ │ │ ├── AdvancedActionButton.swift │ │ │ ├── AspectFillActionButton.swift │ │ │ ├── AudioActionButton.swift │ │ │ ├── AutoPlayActionButton.swift │ │ │ ├── ChaptersActionButton.swift │ │ │ ├── PlayNextItemActionButton.swift │ │ │ ├── PlayPreviousItemActionButton.swift │ │ │ ├── PlaybackSpeedActionButton.swift │ │ │ └── SubtitleActionButton.swift │ │ ├── BarActionButtons.swift │ │ ├── BottomBarView.swift │ │ ├── ChapterTrack.swift │ │ ├── OverlayMenu.swift │ │ ├── PlaybackButtons │ │ │ ├── LargePlaybackButtons.swift │ │ │ └── SmallPlaybackButtons.swift │ │ ├── Timestamp │ │ │ ├── CompactTimeStamp.swift │ │ │ └── SplitTimestamp.swift │ │ └── TopBarView.swift │ ├── MainOverlay.swift │ └── Overlay.swift │ ├── VideoPlayer+Actions.swift │ ├── VideoPlayer+KeyCommands.swift │ └── VideoPlayer.swift ├── Translations ├── ar.lproj │ └── Localizable.strings ├── bg.lproj │ └── Localizable.strings ├── ca.lproj │ └── Localizable.strings ├── cs.lproj │ └── Localizable.strings ├── da.lproj │ └── Localizable.strings ├── de.lproj │ └── Localizable.strings ├── el.lproj │ └── Localizable.strings ├── en.lproj │ └── Localizable.strings ├── eo.lproj │ └── Localizable.strings ├── es.lproj │ └── Localizable.strings ├── eu.lproj │ └── Localizable.strings ├── fi.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── he.lproj │ └── Localizable.strings ├── hi.lproj │ └── Localizable.strings ├── hr.lproj │ └── Localizable.strings ├── hu.lproj │ └── Localizable.strings ├── id.lproj │ └── Localizable.strings ├── it.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── kk.lproj │ └── Localizable.strings ├── ko.lproj │ └── Localizable.strings ├── lb.lproj │ └── Localizable.strings ├── lt.lproj │ └── Localizable.strings ├── mk.lproj │ └── Localizable.strings ├── nb-NO.lproj │ └── Localizable.strings ├── nl.lproj │ └── Localizable.strings ├── nn.lproj │ └── Localizable.strings ├── pl.lproj │ └── Localizable.strings ├── ps.lproj │ └── Localizable.strings ├── pt-BR.lproj │ └── Localizable.strings ├── pt.lproj │ └── Localizable.strings ├── ro.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings ├── sk.lproj │ └── Localizable.strings ├── sl.lproj │ └── Localizable.strings ├── sq.lproj │ └── Localizable.strings ├── sv.lproj │ └── Localizable.strings ├── ta.lproj │ └── Localizable.strings ├── th.lproj │ └── Localizable.strings ├── tr.lproj │ └── Localizable.strings ├── uk.lproj │ └── Localizable.strings ├── vi.lproj │ └── Localizable.strings ├── zh-Hans.lproj │ └── Localizable.strings └── zh-Hant.lproj │ └── Localizable.strings ├── fastlane ├── Appfile.swift ├── Fastfile.swift ├── FastlaneRunner └── swift │ ├── Actions.swift │ ├── Appfile.swift │ ├── ArgumentProcessor.swift │ ├── Atomic.swift │ ├── ControlCommand.swift │ ├── Deliverfile.swift │ ├── DeliverfileProtocol.swift │ ├── Fastfile.swift │ ├── Fastlane.swift │ ├── FastlaneSwiftRunner │ ├── FastlaneSwiftRunner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── FastlaneRunner.xcscheme │ └── README.txt │ ├── Gymfile.swift │ ├── GymfileProtocol.swift │ ├── LaneFileProtocol.swift │ ├── MainProcess.swift │ ├── Matchfile.swift │ ├── MatchfileProtocol.swift │ ├── OptionalConfigValue.swift │ ├── Plugins.swift │ ├── Precheckfile.swift │ ├── PrecheckfileProtocol.swift │ ├── RubyCommand.swift │ ├── RubyCommandable.swift │ ├── Runner.swift │ ├── RunnerArgument.swift │ ├── Scanfile.swift │ ├── ScanfileProtocol.swift │ ├── Screengrabfile.swift │ ├── ScreengrabfileProtocol.swift │ ├── Snapshotfile.swift │ ├── SnapshotfileProtocol.swift │ ├── SocketClient.swift │ ├── SocketClientDelegateProtocol.swift │ ├── SocketResponse.swift │ ├── formatting │ ├── Brewfile │ ├── Brewfile.lock.json │ └── Rakefile │ ├── main.swift │ └── upgrade_manifest.json └── swiftgen.yml /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Jellyfin documentation 4 | url: https://jellyfin.org/docs/general/getting-help.html 5 | about: Our documentation contains lots of help for common issues 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Request a new feature 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Describe the feature you'd like** 10 | 11 | 12 | **Additional context** 13 | 14 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - ignore-for-release 7 | categories: 8 | - title: New Features 🎉 9 | labels: 10 | - * 11 | - title: Bug Fixes 🛠 12 | labels: 13 | - bug 14 | - crash 15 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr.yaml: -------------------------------------------------------------------------------- 1 | name: "Lint 🧹" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | name: "Lint 🧹" 15 | if: github.event.pull_request.draft == false 16 | runs-on: macos-14 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run Swiftformat 23 | run: swiftformat . --lint --config ".swiftformat" 24 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/MobileVLCKit.json" == 3.6.0 2 | binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/TVVLCKit.json" == 3.6.0 3 | # binary "ChromeCastFramework.json" -------------------------------------------------------------------------------- /ChromeCastFramework.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0": "https://dl.google.com/dl/chromecast/sdk/ios/GoogleCastSDK-ios-4.8.0_static_xcframework.zip" 3 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /PreferencesView/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PreferencesView/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swizzleswift", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/MarioIannotta/SwizzleSwift", 7 | "state" : { 8 | "branch" : "master", 9 | "revision" : "e2d31c646182bf94a496b173c6ee5ad191230e9a" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /PreferencesView/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "PreferencesView", 7 | platforms: [ 8 | .iOS(.v15), 9 | .tvOS(.v15), 10 | ], 11 | products: [ 12 | .library( 13 | name: "PreferencesView", 14 | targets: ["PreferencesView"] 15 | ), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/MarioIannotta/SwizzleSwift", branch: "master"), 19 | ], 20 | targets: [ 21 | .target( 22 | name: "PreferencesView", 23 | dependencies: [.product(name: "SwizzleSwift", package: "SwizzleSwift")] 24 | ), 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /PreferencesView/README.md: -------------------------------------------------------------------------------- 1 | # PreferencesView -------------------------------------------------------------------------------- /PreferencesView/Sources/PreferencesView/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | class Box { 10 | 11 | weak var value: UIPreferencesHostingController? 12 | 13 | init() {} 14 | } 15 | -------------------------------------------------------------------------------- /PreferencesView/Sources/PreferencesView/PreferencesView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | import SwizzleSwift 11 | 12 | public struct PreferencesView: UIViewControllerRepresentable { 13 | 14 | private var content: () -> Content 15 | 16 | public init(@ViewBuilder content: @escaping () -> Content) { 17 | _ = UIViewController.swizzlePreferences 18 | self.content = content 19 | } 20 | 21 | public func makeUIViewController(context: Context) -> UIPreferencesHostingController { 22 | UIPreferencesHostingController(content: content) 23 | } 24 | 25 | public func updateUIViewController(_ uiViewController: UIPreferencesHostingController, context: Context) {} 26 | } 27 | -------------------------------------------------------------------------------- /PreferencesView/Sources/PreferencesView/PressCommandAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct PressCommandAction { 13 | 14 | let title: String 15 | let press: UIPress.PressType 16 | let action: () -> Void 17 | 18 | public init( 19 | title: String, 20 | press: UIPress.PressType, 21 | action: @escaping () -> Void 22 | ) { 23 | self.title = title 24 | self.press = press 25 | self.action = action 26 | } 27 | } 28 | 29 | extension PressCommandAction: Equatable { 30 | 31 | public static func == (lhs: PressCommandAction, rhs: PressCommandAction) -> Bool { 32 | lhs.press == rhs.press 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Shared/AppIcons/DarkAppIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum DarkAppIcon: String, AppIcon { 12 | 13 | case blue 14 | case green 15 | case orange 16 | case red 17 | case yellow 18 | case jellyfin 19 | 20 | var displayTitle: String { 21 | switch self { 22 | case .blue: 23 | return L10n.blue 24 | case .green: 25 | return L10n.green 26 | case .orange: 27 | return L10n.orange 28 | case .red: 29 | return L10n.red 30 | case .yellow: 31 | return L10n.yellow 32 | case .jellyfin: 33 | return "Jellyfin" 34 | } 35 | } 36 | 37 | static let tag: String = "dark" 38 | } 39 | -------------------------------------------------------------------------------- /Shared/AppIcons/PrimaryAppIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum PrimaryAppIcon: String, AppIcon { 12 | 13 | case primary 14 | 15 | var displayTitle: String { 16 | switch self { 17 | case .primary: 18 | return L10n.primary 19 | } 20 | } 21 | 22 | static let tag: String = "primary" 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Components/AssertionFailureView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct AssertionFailureView: View { 12 | 13 | let message: String 14 | 15 | init(_ message: String) { 16 | self.message = message 17 | 18 | assertionFailure(message) 19 | } 20 | 21 | var body: some View { 22 | EmptyView() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Components/BlurView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | import UIKit 11 | 12 | struct BlurView: UIViewRepresentable { 13 | 14 | let style: UIBlurEffect.Style 15 | 16 | init(style: UIBlurEffect.Style = .regular) { 17 | self.style = style 18 | } 19 | 20 | func makeUIView(context: Context) -> UIVisualEffectView { 21 | let view = UIVisualEffectView(effect: UIBlurEffect(style: style)) 22 | view.translatesAutoresizingMaskIntoConstraints = false 23 | return view 24 | } 25 | 26 | func updateUIView(_ uiView: UIVisualEffectView, context: Context) { 27 | uiView.effect = UIBlurEffect(style: style) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Shared/Components/PosterIndicators/FavoriteIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct FavoriteIndicator: View { 12 | 13 | let size: CGFloat 14 | 15 | var body: some View { 16 | ZStack(alignment: .bottomLeading) { 17 | Color.clear 18 | 19 | Image(systemName: "heart.circle.fill") 20 | .resizable() 21 | .frame(width: size, height: size) 22 | .symbolRenderingMode(.palette) 23 | .foregroundStyle(.white, .pink) 24 | .padding(3) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Components/PosterIndicators/ProgressIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import JellyfinAPI 11 | import SwiftUI 12 | 13 | struct ProgressIndicator: View { 14 | 15 | @Default(.accentColor) 16 | private var accentColor 17 | 18 | let progress: CGFloat 19 | let height: CGFloat 20 | 21 | var body: some View { 22 | VStack { 23 | Spacer() 24 | 25 | accentColor 26 | .scaleEffect(x: progress, y: 1, anchor: .leading) 27 | .frame(height: height) 28 | } 29 | .frame(maxWidth: .infinity) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Components/PosterIndicators/WatchedIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | struct WatchedIndicator: View { 13 | 14 | @Default(.accentColor) 15 | private var accentColor 16 | 17 | let size: CGFloat 18 | 19 | var body: some View { 20 | ZStack(alignment: .bottomTrailing) { 21 | Color.clear 22 | 23 | Image(systemName: "checkmark.circle.fill") 24 | .resizable() 25 | .frame(width: size, height: size) 26 | .symbolRenderingMode(.palette) 27 | .foregroundStyle(.white, accentColor) 28 | .padding(3) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Components/RowDivider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct RowDivider: View { 12 | 13 | var body: some View { 14 | Color.secondarySystemFill 15 | .frame(height: 1) 16 | .edgePadding(.horizontal) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Shared/Components/WrappedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | // TODO: mainly used as a view to hold views for states 12 | // but doesn't work with animations/transitions. 13 | // Look at alternative with just ZStack and remove 14 | 15 | struct WrappedView: View { 16 | 17 | @ViewBuilder 18 | let content: () -> Content 19 | 20 | var body: some View { 21 | content() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Coordinators/DownloadTaskCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | #if os(iOS) 10 | import Foundation 11 | import Stinsen 12 | import SwiftUI 13 | 14 | final class DownloadTaskCoordinator: NavigationCoordinatable { 15 | 16 | let stack = NavigationStack(initial: \DownloadTaskCoordinator.start) 17 | 18 | @Root 19 | var start = makeStart 20 | 21 | let downloadTask: DownloadTask 22 | 23 | init(downloadTask: DownloadTask) { 24 | self.downloadTask = downloadTask 25 | } 26 | 27 | @ViewBuilder 28 | private func makeStart() -> DownloadTaskView { 29 | DownloadTaskView(downloadTask: downloadTask) 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Shared/Coordinators/LiveTVCoordinator/iOSLiveTVCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | import Stinsen 12 | import SwiftUI 13 | 14 | final class LiveTVCoordinator: NavigationCoordinatable { 15 | 16 | let stack = NavigationStack(initial: \LiveTVCoordinator.start) 17 | 18 | @Root 19 | var start = makeStart 20 | 21 | @Route(.push) 22 | var channels = makeChannels 23 | 24 | func makeChannels() -> ChannelLibraryView { 25 | ChannelLibraryView() 26 | } 27 | 28 | @ViewBuilder 29 | func makeStart() -> some View { 30 | ProgramsView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Shared/Extensions/Button.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Button where Label: View { 12 | 13 | /// Creates a Button with an empty action and a custom label. 14 | init(role: ButtonRole? = nil, @ViewBuilder label: @escaping () -> Label) { 15 | self.init {} label: { 16 | label() 17 | } 18 | } 19 | } 20 | 21 | extension Button where Label == Text { 22 | 23 | /// Creates a Button with an empty action and a plain text label. 24 | init(_ title: String, role: ButtonRole? = nil) { 25 | self.init(role: role) { 26 | Text(title) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Shared/Extensions/CGPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGPoint { 12 | 13 | func isNear(_ other: CGPoint, padding: CGFloat) -> Bool { 14 | let xRange = (x - padding) ... (x + padding) 15 | let yRange = (y - padding) ... (y + padding) 16 | 17 | return xRange.contains(other.x) && yRange.contains(other.y) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Shared/Extensions/CGSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import UIKit 10 | 11 | extension CGSize { 12 | 13 | static func Square(length: CGFloat) -> CGSize { 14 | CGSize(width: length, height: length) 15 | } 16 | 17 | var isLandscape: Bool { 18 | width >= height 19 | } 20 | 21 | var isPortrait: Bool { 22 | height >= width 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/Collection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Collection { 12 | 13 | var asArray: [Element] { 14 | Array(self) 15 | } 16 | 17 | var isNotEmpty: Bool { 18 | !isEmpty 19 | } 20 | 21 | subscript(safe index: Index) -> Element? { 22 | indices.contains(index) ? self[index] : nil 23 | } 24 | 25 | func keyed(using: KeyPath) -> [Key: Element] { 26 | Dictionary(uniqueKeysWithValues: map { ($0[keyPath: using], $0) }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Extensions/CoreStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import CoreStore 10 | import Foundation 11 | import Logging 12 | 13 | extension CoreStore.LogLevel { 14 | 15 | var asSwiftLog: Logger.Level { 16 | switch self { 17 | case .trace: 18 | return .trace 19 | case .notice: 20 | return .debug 21 | case .warning: 22 | return .warning 23 | case .fatal: 24 | return .critical 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Extensions/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | subscript(key: Key?) -> Value? { 14 | guard let key else { return nil } 15 | return self[key] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/Extensions/Double.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Double { 12 | 13 | var rateLabel: String { 14 | String(format: "%.2f", self).appending("x") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Shared/Extensions/Edge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Edge.Set { 12 | 13 | var asUIRectEdge: UIRectEdge { 14 | switch self { 15 | case .top: 16 | .top 17 | case .leading: 18 | .left 19 | case .bottom: 20 | .bottom 21 | case .trailing: 22 | .right 23 | case .all: 24 | .all 25 | case .horizontal: 26 | [.left, .right] 27 | case .vertical: 28 | [.top, .bottom] 29 | default: 30 | .all 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Shared/Extensions/Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Equatable { 12 | 13 | func mutating(_ keyPath: WritableKeyPath, with newValue: Value) -> Self { 14 | var copy = self 15 | copy[keyPath: keyPath] = newValue 16 | return copy 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Shared/Extensions/HorizontalAlignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | // TODO: remove and just use overlay + offset 12 | extension HorizontalAlignment { 13 | 14 | struct VideoPlayerTitleAlignment: AlignmentID { 15 | static func defaultValue(in context: ViewDimensions) -> CGFloat { 16 | context[HorizontalAlignment.leading] 17 | } 18 | } 19 | 20 | static let VideoPlayerTitleAlignmentGuide = HorizontalAlignment(VideoPlayerTitleAlignment.self) 21 | } 22 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/ActiveSessionsPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum ActiveSessionsPolicy: Int, Displayable, CaseIterable { 12 | 13 | case unlimited = 0 14 | case custom = 1 // Default to 1 Active Session 15 | 16 | // MARK: - Display Title 17 | 18 | var displayTitle: String { 19 | switch self { 20 | case .unlimited: 21 | return L10n.unlimited 22 | case .custom: 23 | return L10n.custom 24 | } 25 | } 26 | 27 | init?(rawValue: Int?) { 28 | guard let rawValue else { return nil } 29 | self.init(rawValue: rawValue) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/ActivityLogEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension ActivityLogEntry: Poster { 13 | var displayTitle: String { 14 | name ?? L10n.unknown 15 | } 16 | 17 | var unwrappedIDHashOrZero: Int { 18 | id?.hashValue ?? 0 19 | } 20 | 21 | var systemImage: String { 22 | "text.document" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/CollectionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension CollectionType: SupportedCaseIterable { 13 | 14 | static var supportedCases: [CollectionType] { 15 | [ 16 | .boxsets, 17 | .folders, 18 | .movies, 19 | .tvshows, 20 | .livetv, 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/DayOfWeek.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension DayOfWeek { 13 | 14 | var displayTitle: String? { 15 | let newLineRemoved = rawValue.replacingOccurrences(of: "\n", with: "") 16 | 17 | guard let index = DateFormatter().weekdaySymbols.firstIndex(of: newLineRemoved) else { 18 | return nil 19 | } 20 | 21 | return Calendar.current 22 | .weekdaySymbols[index] 23 | .localizedCapitalized 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/DeviceInfoDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension DeviceInfoDto { 13 | 14 | var type: DeviceType { 15 | DeviceType( 16 | client: appName, 17 | deviceName: name 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/ItemFields.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension ItemFields { 13 | 14 | /// The minimum cases to use when retrieving an item or items 15 | /// for basic presentation. Depending on the context, using 16 | /// more fields and including user data may also be necessary. 17 | static let MinimumFields: [ItemFields] = [ 18 | .mediaSources, 19 | .overview, 20 | .parentID, 21 | .taglines, 22 | ] 23 | } 24 | 25 | extension Array where Element == ItemFields { 26 | 27 | static var MinimumFields: Self { 28 | ItemFields.MinimumFields 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/JellyfinAPIError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | // TODO: rename to `ErrorMessage` and remove other implementation 12 | 13 | /// A basic error that holds a message, useful for debugging. 14 | /// 15 | /// - Important: Only really use for debugging. For practical errors, 16 | /// statically define errors for each domain/context. 17 | struct JellyfinAPIError: LocalizedError, Hashable { 18 | 19 | private let message: String 20 | 21 | init(_ message: String) { 22 | self.message = message 23 | } 24 | 25 | var errorDescription: String? { 26 | message 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/LoginFailurePolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum LoginFailurePolicy: Int, Displayable, CaseIterable { 12 | 13 | case unlimited = -1 14 | case userDefault = 0 15 | case custom = 1 // Default to 1 16 | 17 | var displayTitle: String { 18 | switch self { 19 | case .unlimited: 20 | return L10n.unlimited 21 | case .userDefault: 22 | return L10n.default 23 | case .custom: 24 | return L10n.custom 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/MaxBitratePolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum MaxBitratePolicy: Int, Displayable, CaseIterable { 12 | 13 | case unlimited = 0 14 | case custom = 10_000_000 // Default to 10mbps 15 | 16 | // MARK: - Display Title 17 | 18 | var displayTitle: String { 19 | switch self { 20 | case .unlimited: 21 | return L10n.unlimited 22 | case .custom: 23 | return L10n.custom 24 | } 25 | } 26 | 27 | init?(rawValue: Int?) { 28 | guard let rawValue else { return nil } 29 | self.init(rawValue: rawValue) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/MediaSourceInfo/MediaSourceInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension MediaSourceInfo: Displayable { 13 | 14 | var displayTitle: String { 15 | name ?? .emptyDash 16 | } 17 | } 18 | 19 | extension MediaSourceInfo { 20 | 21 | var audioStreams: [MediaStream]? { 22 | mediaStreams?.filter { $0.type == .audio } 23 | } 24 | 25 | var subtitleStreams: [MediaStream]? { 26 | mediaStreams?.filter { $0.type == .subtitle } 27 | } 28 | 29 | var videoStreams: [MediaStream]? { 30 | mediaStreams?.filter { $0.type == .video } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/NameGuidPair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension NameGuidPair: Displayable { 13 | 14 | var displayTitle: String { 15 | name ?? .emptyDash 16 | } 17 | } 18 | 19 | // TODO: strong type studios and implement as `LibraryParent` 20 | extension NameGuidPair: LibraryParent { 21 | 22 | var libraryType: BaseItemKind? { 23 | .studio 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/PlayMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | import SwiftUI 12 | 13 | extension PlayMethod: Displayable { 14 | 15 | var displayTitle: String { 16 | switch self { 17 | case .transcode: 18 | return L10n.transcode 19 | case .directStream: 20 | return L10n.directStream 21 | case .directPlay: 22 | return L10n.directPlay 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/PlayerStateInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension PlayerStateInfo { 13 | 14 | var positionSeconds: Int? { 15 | guard let positionTicks else { return nil } 16 | return positionTicks / 10_000_000 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/RemoteImageInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | import SwiftUI 12 | 13 | extension RemoteImageInfo: @retroactive Identifiable, Poster { 14 | 15 | var displayTitle: String { 16 | providerName ?? L10n.unknown 17 | } 18 | 19 | var unwrappedIDHashOrZero: Int { 20 | id 21 | } 22 | 23 | var subtitle: String? { 24 | language 25 | } 26 | 27 | var systemImage: String { 28 | "photo" 29 | } 30 | 31 | public var id: Int { 32 | hashValue 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/SyncPlayUserAccessType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension SyncPlayUserAccessType: Displayable { 13 | 14 | var displayTitle: String { 15 | switch self { 16 | case .createAndJoinGroups: 17 | L10n.createAndJoinGroups 18 | case .joinGroups: 19 | L10n.joinGroups 20 | case .none: 21 | L10n.none 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/TaskCompletionStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension TaskCompletionStatus: Displayable { 13 | 14 | var displayTitle: String { 15 | switch self { 16 | case .completed: 17 | return L10n.taskCompleted 18 | case .failed: 19 | return L10n.taskFailed 20 | case .cancelled: 21 | return L10n.taskCancelled 22 | case .aborted: 23 | return L10n.taskAborted 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/TaskState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension TaskState: Displayable { 13 | 14 | var displayTitle: String { 15 | switch self { 16 | case .cancelling: 17 | return L10n.cancelling 18 | case .idle: 19 | return L10n.idle 20 | case .running: 21 | return L10n.running 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/UserDto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension UserDto { 13 | 14 | func profileImageSource( 15 | client: JellyfinClient, 16 | maxWidth: CGFloat? = nil 17 | ) -> ImageSource { 18 | UserState( 19 | id: id ?? "", 20 | serverID: "", 21 | username: "" 22 | ) 23 | .profileImageSource( 24 | client: client, 25 | maxWidth: maxWidth 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Extensions/JellyfinAPI/Video3DFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | extension Video3DFormat { 13 | var displayTitle: String { 14 | switch self { 15 | case .halfSideBySide: 16 | return L10n.halfSideBySide 17 | case .fullSideBySide: 18 | return L10n.fullSideBySide 19 | case .fullTopAndBottom: 20 | return L10n.fullTopAndBottom 21 | case .halfTopAndBottom: 22 | return L10n.halfTopAndBottom 23 | case .mvc: 24 | return L10n.mvc 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Extensions/NavigationCoordinatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Stinsen 10 | import SwiftUI 11 | 12 | extension NavigationViewCoordinator { 13 | 14 | convenience init(@ViewBuilder content: @escaping () -> Content) { 15 | self.init(BasicNavigationViewCoordinator(content)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/Extensions/Optional.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Optional where Wrapped: Collection { 12 | 13 | var isNilOrEmpty: Bool { 14 | self?.isEmpty ?? true 15 | } 16 | 17 | mutating func appendedOrInit(_ element: Wrapped.Element) -> [Wrapped.Element] { 18 | if let self { 19 | return self + [element] 20 | } else { 21 | return [element] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/PersistentLogHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import Logging 11 | import PulseLogHandler 12 | 13 | extension PersistentLogHandler { 14 | 15 | func withLogLevel(_ level: Logger.Level) -> Self { 16 | var copy = self 17 | copy.logLevel = level 18 | return copy 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Shared/Extensions/RatingType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | 11 | extension RatingType: Displayable { 12 | 13 | var displayTitle: String { 14 | switch self { 15 | case .score: 16 | return L10n.score 17 | case .likes: 18 | return L10n.likes 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Shared/Extensions/Set.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension Set { 12 | 13 | mutating func toggle(value: Element) { 14 | if contains(value) { 15 | remove(value) 16 | } else { 17 | insert(value) 18 | } 19 | } 20 | 21 | mutating func insert(contentsOf elements: [Element]) { 22 | for element in elements { 23 | insert(element) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Extensions/Task.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | 12 | extension Task { 13 | 14 | @inlinable 15 | func asAnyCancellable() -> AnyCancellable { 16 | AnyCancellable(cancel) 17 | } 18 | 19 | func store(in set: inout Set) { 20 | set.insert(asAnyCancellable()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Shared/Extensions/Text.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Text { 12 | 13 | init(_ content: some Displayable) { 14 | self.init(verbatim: "\(content.displayTitle)") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Shared/Extensions/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | var overlayColor: UIColor { 13 | var red: CGFloat = 0 14 | var green: CGFloat = 0 15 | var blue: CGFloat = 0 16 | var alpha: CGFloat = 0 17 | 18 | getRed(&red, green: &green, blue: &blue, alpha: &alpha) 19 | 20 | let brightness = ((red * 299) + (green * 587) + (blue * 114)) / 1000 21 | 22 | return brightness < 0.5 ? .white : .black 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Extensions/UIGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension UIGestureRecognizer { 12 | 13 | func unitPoint(in view: UIView) -> UnitPoint { 14 | let location = location(in: view) 15 | return .init(x: location.x / view.frame.width, y: location.y / view.frame.height) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/Extensions/UIScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIScreen { 12 | 13 | func scale(_ x: Int) -> Int { 14 | Int(nativeScale) * x 15 | } 16 | 17 | func scale(_ x: CGFloat) -> Int { 18 | Int(nativeScale * x) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Shared/Extensions/URLComponents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension URLComponents { 12 | 13 | func addingQueryItem(key: String, value: String?) -> Self { 14 | var copy = self 15 | 16 | if copy.queryItems == nil { 17 | copy.queryItems = [] 18 | } 19 | 20 | copy.queryItems?.append(.init(name: key, value: value)) 21 | return copy 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Extensions/URLResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension URLResponse { 12 | 13 | var mimeSubtype: String? { 14 | guard let subtype = mimeType?.split(separator: "/")[safe: 1] else { return nil } 15 | return String(subtype) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/Extensions/URLSessionConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension URLSessionConfiguration { 12 | 13 | /// A session configuration object built upon the default 14 | /// configuration with values for Swiftfin. 15 | static let swiftfin: URLSessionConfiguration = { 16 | .default.mutating(\.timeoutIntervalForRequest, with: 20) 17 | }() 18 | } 19 | -------------------------------------------------------------------------------- /Shared/Extensions/VerticalAlignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension VerticalAlignment { 12 | 13 | private struct SliderCenterAlignment: AlignmentID { 14 | static func defaultValue(in context: ViewDimensions) -> CGFloat { 15 | context[VerticalAlignment.center] 16 | } 17 | } 18 | 19 | static let sliderCenterAlignmentGuide = VerticalAlignment( 20 | SliderCenterAlignment.self 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /Shared/Extensions/ViewExtensions/Backport/BackPort+ScrollIndicatorVisibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Backport { 13 | 14 | enum ScrollIndicatorVisibility { 15 | 16 | case automatic 17 | case visible 18 | case hidden 19 | case never 20 | 21 | @available(iOS 16, tvOS 16, *) 22 | var supportedValue: SwiftUI.ScrollIndicatorVisibility { 23 | switch self { 24 | case .automatic: .automatic 25 | case .visible: .visible 26 | case .hidden: .hidden 27 | case .never: .never 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Extensions/ViewExtensions/Modifiers/OnFirstAppearModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct OnFirstAppearModifier: ViewModifier { 12 | 13 | @State 14 | private var didAppear = false 15 | 16 | let action: () -> Void 17 | 18 | func body(content: Content) -> some View { 19 | content 20 | .onAppear { 21 | guard !didAppear else { return } 22 | didAppear = true 23 | action() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Extensions/ViewExtensions/Modifiers/OnReceiveNotificationModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct OnReceiveNotificationModifier>: ViewModifier { 12 | 13 | let key: K 14 | let onReceive: (P) -> Void 15 | 16 | func body(content: Content) -> some View { 17 | content 18 | .onReceive(key.publisher, perform: onReceive) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Shared/Extensions/ViewExtensions/Modifiers/OnScenePhaseChangedModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct OnScenePhaseChangedModifier: ViewModifier { 12 | 13 | @Environment(\.scenePhase) 14 | private var scenePhase 15 | 16 | let phase: ScenePhase 17 | let action: () -> Void 18 | 19 | func body(content: Content) -> some View { 20 | content.onChange(of: scenePhase) { newValue in 21 | if newValue == phase { 22 | action() 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Extensions/ViewExtensions/Modifiers/SinceLastDisappearModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SinceLastDisappearModifier: ViewModifier { 12 | 13 | @State 14 | private var lastDisappear: Date? = nil 15 | 16 | let action: (TimeInterval) -> Void 17 | 18 | func body(content: Content) -> some View { 19 | content 20 | .onAppear { 21 | guard let lastDisappear else { return } 22 | let interval = Date.now.timeIntervalSince(lastDisappear) 23 | action(interval) 24 | } 25 | .onDisappear { 26 | lastDisappear = Date.now 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Shared/Objects/BindingBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Combine 10 | import SwiftUI 11 | 12 | /// Utility for views that are passed a `Binding` that 13 | /// may not be able to respond to view updates from 14 | /// the source 15 | class BindingBox: ObservableObject { 16 | 17 | @Published 18 | var value: Wrapped 19 | 20 | private let source: Binding 21 | private var valueObserver: AnyCancellable! 22 | 23 | init(source: Binding) { 24 | self.source = source 25 | self.value = source.wrappedValue 26 | valueObserver = nil 27 | 28 | valueObserver = $value.sink { [weak self] in 29 | self?.source.wrappedValue = $0 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Shared/Objects/CommaStringBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// Result builder that build a comma-separated string from its components 12 | @resultBuilder 13 | struct CommaStringBuilder where Component: RawRepresentable { 14 | 15 | static func buildBlock(_ components: String...) -> String { 16 | components.joined(separator: ",") 17 | } 18 | 19 | static func buildExpression(_ expression: Component) -> String { 20 | expression.rawValue 21 | } 22 | 23 | static func buildExpression(_ expression: [Component]) -> String { 24 | expression.map(\.rawValue) 25 | .joined(separator: ",") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Objects/CustomDeviceProfileAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum CustomDeviceProfileAction: String, CaseIterable, Displayable, Storable { 13 | 14 | case add 15 | case replace 16 | 17 | var displayTitle: String { 18 | switch self { 19 | case .add: 20 | return L10n.add 21 | case .replace: 22 | return L10n.replace 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Objects/DisplayOrder/BoxSetDisplayOrder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum BoxSetDisplayOrder: String, CaseIterable, Identifiable { 12 | case dateModified = "DateModified" 13 | case sortName = "SortName" 14 | case premiereDate = "PremiereDate" 15 | 16 | var id: String { rawValue } 17 | 18 | var displayTitle: String { 19 | switch self { 20 | case .dateModified: 21 | return L10n.dateModified 22 | case .sortName: 23 | return L10n.sortName 24 | case .premiereDate: 25 | return L10n.premiereDate 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Objects/Displayable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// A type that is displayed with a title 12 | protocol Displayable { 13 | 14 | var displayTitle: String { get } 15 | } 16 | -------------------------------------------------------------------------------- /Shared/Objects/Eventful.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | 12 | protocol Eventful { 13 | 14 | associatedtype Event 15 | 16 | var events: AnyPublisher { get } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/Objects/ImageSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an image source along with a blur hash and a system image 12 | /// to act as placeholders. 13 | /// 14 | /// If `blurHash` is `nil`, the given system image is used instead. 15 | struct ImageSource: Hashable { 16 | 17 | let url: URL? 18 | let blurHash: String? 19 | 20 | init( 21 | url: URL? = nil, 22 | blurHash: String? = nil 23 | ) { 24 | self.url = url 25 | self.blurHash = blurHash 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Objects/ItemFilter/AnyItemFilter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// A type-erased instance of an item filter. 12 | struct AnyItemFilter: Displayable, Hashable, ItemFilter { 13 | 14 | let displayTitle: String 15 | let value: String 16 | 17 | init( 18 | displayTitle: String, 19 | value: String 20 | ) { 21 | self.displayTitle = displayTitle 22 | self.value = value 23 | } 24 | 25 | init(from anyFilter: AnyItemFilter) { 26 | self.displayTitle = anyFilter.displayTitle 27 | self.value = anyFilter.value 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Shared/Objects/ItemFilter/ItemGenre.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | struct ItemGenre: Codable, ExpressibleByStringLiteral, Hashable, ItemFilter { 12 | 13 | let value: String 14 | 15 | var displayTitle: String { 16 | value 17 | } 18 | 19 | init(stringLiteral value: String) { 20 | self.value = value 21 | } 22 | 23 | init(from anyFilter: AnyItemFilter) { 24 | self.value = anyFilter.value 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Objects/ItemFilter/ItemTag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | struct ItemTag: Codable, ExpressibleByStringLiteral, Hashable, ItemFilter { 12 | 13 | let value: String 14 | 15 | var displayTitle: String { 16 | value 17 | } 18 | 19 | init(stringLiteral value: String) { 20 | self.value = value 21 | } 22 | 23 | init(from anyFilter: AnyItemFilter) { 24 | self.value = anyFilter.value 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Objects/ItemFilter/ItemYear.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | struct ItemYear: Codable, ExpressibleByIntegerLiteral, Hashable, ItemFilter { 12 | 13 | let value: String 14 | 15 | var displayTitle: String { 16 | value 17 | } 18 | 19 | var intValue: Int { 20 | Int(value)! 21 | } 22 | 23 | init(integerLiteral value: IntegerLiteralType) { 24 | self.value = "\(value)" 25 | } 26 | 27 | init(from anyFilter: AnyItemFilter) { 28 | self.value = anyFilter.value 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Shared/Objects/ItemViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum ItemViewType: String, CaseIterable, Displayable, Defaults.Serializable { 13 | 14 | case compactPoster 15 | case compactLogo 16 | case cinematic 17 | 18 | var displayTitle: String { 19 | switch self { 20 | case .compactPoster: 21 | return L10n.compactPoster 22 | case .compactLogo: 23 | return L10n.compactLogo 24 | case .cinematic: 25 | return L10n.cinematic 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Objects/LibraryDisplayType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | import UIKit 12 | 13 | enum LibraryDisplayType: String, CaseIterable, Displayable, Storable, SystemImageable { 14 | 15 | case grid 16 | case list 17 | 18 | // TODO: localize 19 | var displayTitle: String { 20 | switch self { 21 | case .grid: 22 | L10n.grid 23 | case .list: 24 | L10n.list 25 | } 26 | } 27 | 28 | var systemImage: String { 29 | switch self { 30 | case .grid: 31 | "square.grid.2x2.fill" 32 | case .list: 33 | "square.fill.text.grid.1x2" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Shared/Objects/LibraryParent/TitledLibraryParent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | /// A basic structure conforming to `LibraryParent` that is meant to only define its `displayTitle` 13 | struct TitledLibraryParent: LibraryParent { 14 | 15 | let displayTitle: String 16 | let id: String? 17 | let libraryType: BaseItemKind? = nil 18 | 19 | init(displayTitle: String, id: String? = nil) { 20 | self.displayTitle = displayTitle 21 | self.id = id 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Objects/NotificationSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// A container for `Notifications.Key`. 12 | struct NotificationSet { 13 | 14 | private var names: Set = [] 15 | 16 | func contains

(_ key: Notifications.Key

) -> Bool { 17 | names.contains(key.name.rawValue) 18 | } 19 | 20 | mutating func insert

(_ key: Notifications.Key

) { 21 | names.insert(key.name.rawValue) 22 | } 23 | 24 | mutating func remove

(_ key: Notifications.Key

) { 25 | names.remove(key.name.rawValue) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Objects/PlaybackCompatibility/PlaybackCompatibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import JellyfinAPI 11 | 12 | enum PlaybackCompatibility: String, CaseIterable, Defaults.Serializable, Displayable { 13 | 14 | case auto 15 | case mostCompatible 16 | case directPlay 17 | case custom 18 | 19 | var displayTitle: String { 20 | switch self { 21 | case .auto: 22 | return L10n.auto 23 | case .mostCompatible: 24 | return L10n.compatible 25 | case .directPlay: 26 | return L10n.direct 27 | case .custom: 28 | return L10n.custom 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Objects/PosterDisplayType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | // TODO: think about what to do for square (music) 13 | enum PosterDisplayType: String, CaseIterable, Displayable, Storable, SystemImageable { 14 | 15 | case landscape 16 | case portrait 17 | 18 | var displayTitle: String { 19 | switch self { 20 | case .landscape: 21 | L10n.landscape 22 | case .portrait: 23 | L10n.portrait 24 | } 25 | } 26 | 27 | var systemImage: String { 28 | switch self { 29 | case .landscape: 30 | "rectangle.fill" 31 | case .portrait: 32 | "rectangle.portrait.fill" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Shared/Objects/RoundedCorner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct RoundedCorner: Shape { 12 | 13 | let radius: CGFloat 14 | let corners: UIRectCorner 15 | 16 | func path(in rect: CGRect) -> Path { 17 | Path(UIBezierPath( 18 | roundedRect: rect, 19 | byRoundingCorners: corners, 20 | cornerRadii: CGSize(width: radius, height: radius) 21 | ).cgPath) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Objects/ScalingButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ScalingButtonStyle: ButtonStyle { 12 | 13 | private let animation: Animation 14 | private let scale: CGFloat 15 | 16 | init(scale: CGFloat = 0.8, animation: Animation = .linear(duration: 0.1)) { 17 | self.animation = animation 18 | self.scale = scale 19 | } 20 | 21 | func makeBody(configuration: Configuration) -> some View { 22 | configuration.label 23 | .scaleEffect(configuration.isPressed ? scale : 1) 24 | .animation(animation, value: configuration.isPressed) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Objects/SeriesStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | enum SeriesStatus: String, CaseIterable { 12 | case continuing = "Continuing" 13 | case ended = "Ended" 14 | case unreleased = "Unreleased" 15 | 16 | var displayTitle: String { 17 | switch self { 18 | case .continuing: 19 | return L10n.continuing 20 | case .ended: 21 | return L10n.ended 22 | case .unreleased: 23 | return L10n.unreleased 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Shared/Objects/SliderType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum SliderType: String, CaseIterable, Displayable, Defaults.Serializable { 13 | 14 | case thumb 15 | case capsule 16 | 17 | var displayTitle: String { 18 | switch self { 19 | case .thumb: 20 | return L10n.thumbSlider 21 | case .capsule: 22 | return L10n.capsule 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Objects/Storable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | /// A type that is able to be stored within: 13 | /// 14 | /// - `Defaults`: UserDefaults 15 | /// - `StoredValue`: AnyData 16 | protocol Storable: Codable, Defaults.Serializable {} 17 | -------------------------------------------------------------------------------- /Shared/Objects/SupportedCaseIterable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | /// A type that provides a collection of a subset of all of its values. 12 | /// 13 | /// Using types that conform to `CaseIterable` may contain values that 14 | /// aren't supported or valid in certain scenarios. 15 | protocol SupportedCaseIterable: CaseIterable { 16 | 17 | associatedtype SupportedCases: Collection = [Self] where Self == Self.SupportedCases.Element 18 | 19 | static var supportedCases: Self.SupportedCases { get } 20 | } 21 | 22 | extension SupportedCaseIterable where SupportedCases.Element: Equatable { 23 | 24 | var isSupported: Bool { 25 | Self.supportedCases.contains(self) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Shared/Objects/SystemImageable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | protocol SystemImageable { 12 | 13 | var systemImage: String { get } 14 | } 15 | -------------------------------------------------------------------------------- /Shared/Objects/TextPair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | struct TextPair: Displayable, Identifiable { 12 | 13 | let title: String 14 | let subtitle: String 15 | 16 | var displayTitle: String { 17 | title 18 | } 19 | 20 | var id: String { 21 | displayTitle.appending(subtitle) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/Objects/TimeStampType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum TimestampType: String, CaseIterable, Defaults.Serializable, Displayable { 13 | 14 | case split 15 | case compact 16 | 17 | var displayTitle: String { 18 | switch self { 19 | case .split: 20 | return L10n.split 21 | case .compact: 22 | return L10n.compact 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Objects/TrailingTimestampType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum TrailingTimestampType: String, CaseIterable, Displayable, Defaults.Serializable { 13 | 14 | case timeLeft 15 | case totalTime 16 | 17 | var displayTitle: String { 18 | switch self { 19 | case .timeLeft: 20 | return L10n.timeLeft 21 | case .totalTime: 22 | return L10n.totalTime 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/Objects/UserAccessPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | // TODO: require remote sign in every time 12 | // - actually found to be a bit difficult? 13 | // TODO: rename to not confuse with server access/UserDto 14 | 15 | enum UserAccessPolicy: String, CaseIterable, Codable, Displayable { 16 | 17 | case none 18 | case requireDeviceAuthentication 19 | case requirePin 20 | 21 | var displayTitle: String { 22 | switch self { 23 | case .none: 24 | L10n.none 25 | case .requireDeviceAuthentication: 26 | L10n.deviceAuth 27 | case .requirePin: 28 | L10n.pin 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shared/Objects/UserSignInState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import Foundation 11 | 12 | enum UserSignInState: RawRepresentable, Codable, Defaults.Serializable, Equatable, Hashable { 13 | 14 | case signedOut 15 | case signedIn(userID: String) 16 | 17 | var rawValue: String { 18 | switch self { 19 | case .signedOut: 20 | "" 21 | case let .signedIn(userID): 22 | userID 23 | } 24 | } 25 | 26 | init?(rawValue: String) { 27 | if rawValue.isEmpty { 28 | self = .signedOut 29 | } else { 30 | self = .signedIn(userID: rawValue) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Shared/Objects/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | @inlinable 12 | func clamp(_ x: T, min y: T, max z: T) -> T { 13 | min(max(x, y), z) 14 | } 15 | 16 | @inlinable 17 | func round(_ value: T, toNearest: T) -> T { 18 | round(value / toNearest) * toNearest 19 | } 20 | 21 | @inlinable 22 | func round(_ value: T, toNearest: T) -> T { 23 | T(round(Double(value), toNearest: Double(toNearest))) 24 | } 25 | -------------------------------------------------------------------------------- /Shared/Services/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Factory 10 | import Foundation 11 | import KeychainSwift 12 | 13 | extension Container { 14 | 15 | // TODO: take a look at all security options 16 | var keychainService: Factory { self { KeychainSwift() }.singleton } 17 | } 18 | -------------------------------------------------------------------------------- /Shared/ViewModels/DownloadListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Factory 10 | import SwiftUI 11 | 12 | class DownloadListViewModel: ViewModel { 13 | 14 | @Injected(\.downloadManager) 15 | private var downloadManager 16 | 17 | @Published 18 | var items: [DownloadTask] = [] 19 | 20 | override init() { 21 | super.init() 22 | 23 | items = downloadManager.downloadedItems() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Shared/ViewModels/ItemViewModel/MovieItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | import JellyfinAPI 12 | 13 | final class MovieItemViewModel: ItemViewModel {} 14 | -------------------------------------------------------------------------------- /Shared/ViewModels/VideoPlayerManager/OnlineVideoPlayerManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | final class OnlineVideoPlayerManager: VideoPlayerManager { 13 | 14 | init(item: BaseItemDto, mediaSource: MediaSourceInfo) { 15 | super.init() 16 | 17 | Task { 18 | let viewModel = try await item.videoPlayerViewModel(with: mediaSource) 19 | 20 | await MainActor.run { 21 | self.currentViewModel = viewModel 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Components/DotHStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DotHStack: View { 12 | 13 | @ViewBuilder 14 | var content: () -> Content 15 | 16 | var body: some View { 17 | SeparatorHStack(content) 18 | .separator { 19 | Circle() 20 | .frame(width: 5, height: 5) 21 | .padding(.horizontal, 10) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Extensions/View/View-tvOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | import SwiftUIIntrospect 12 | 13 | extension View { 14 | 15 | @ViewBuilder 16 | func navigationBarBranding( 17 | isLoading: Bool = false 18 | ) -> some View { 19 | modifier( 20 | NavigationBarBrandingModifier( 21 | isLoading: isLoading 22 | ) 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Swiftfin tvOS/ImageButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | struct ImageButtonStyle: ButtonStyle { 10 | 11 | let focused: Bool 12 | func makeBody(configuration: Configuration) -> some View { 13 | configuration 14 | .label 15 | .padding(6) 16 | .foregroundColor(Color.white) 17 | .background(Color.blue) 18 | .cornerRadius(100) 19 | .shadow(color: .black, radius: self.focused ? 20 : 0, x: 0, y: 0) // 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/1280x768-back.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1280x768-back.png", 5 | "idiom" : "tv" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Back.imagestacklayer" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/512.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "512.png", 5 | "idiom" : "tv" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/400x240-back.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "400x240-back.png", 5 | "idiom" : "tv", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Webp.net-resizeimage.png", 10 | "idiom" : "tv", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Webp.net-resizeimage.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Back.imagestacklayer" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/216.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "216.png", 5 | "idiom" : "tv", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Webp.net-resizeimage-2.png", 10 | "idiom" : "tv", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "author" : "xcode", 16 | "version" : 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Webp.net-resizeimage-2.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "App Icon - App Store.imagestack", 5 | "idiom" : "tv", 6 | "role" : "primary-app-icon", 7 | "size" : "1280x768" 8 | }, 9 | { 10 | "filename" : "App Icon.imagestack", 11 | "idiom" : "tv", 12 | "role" : "primary-app-icon", 13 | "size" : "400x240" 14 | }, 15 | { 16 | "filename" : "Top Shelf Image Wide.imageset", 17 | "idiom" : "tv", 18 | "role" : "top-shelf-image-wide", 19 | "size" : "2320x720" 20 | }, 21 | { 22 | "filename" : "Top Shelf Image.imageset", 23 | "idiom" : "tv", 24 | "role" : "top-shelf-image", 25 | "size" : "1920x720" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "top shelf.png", 5 | "idiom" : "tv", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Untitled-1.png", 10 | "idiom" : "tv", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "top shelf-1.png", 15 | "idiom" : "tv-marketing", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "filename" : "Untitled-2.png", 20 | "idiom" : "tv-marketing", 21 | "scale" : "2x" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-1.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Untitled-2.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf-1.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/top shelf.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "top shelf.png", 5 | "idiom" : "tv", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Untitled-2.png", 10 | "idiom" : "tv", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "top shelf-1.png", 15 | "idiom" : "tv-marketing", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "filename" : "Untitled-1.png", 20 | "idiom" : "tv-marketing", 21 | "scale" : "2x" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-1.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Untitled-2.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf-1.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin tvOS/Resources/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/top shelf.png -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "jellyfin-blob.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "tomato.fresh.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "tomato.rotten.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/ItemView/CollectionItemView/CollectionItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct CollectionItemView: View { 12 | 13 | @ObservedObject 14 | var viewModel: CollectionItemViewModel 15 | 16 | var body: some View { 17 | ItemView.CinematicScrollView(viewModel: viewModel) { 18 | ContentView(viewModel: viewModel) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/ItemView/EpisodeItemView/EpisodeItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct EpisodeItemView: View { 12 | 13 | @ObservedObject 14 | var viewModel: EpisodeItemViewModel 15 | 16 | var body: some View { 17 | ItemView.CinematicScrollView(viewModel: viewModel) { 18 | ContentView(viewModel: viewModel) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/ItemView/MovieItemView/MovieItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MovieItemView: View { 12 | 13 | @ObservedObject 14 | var viewModel: MovieItemViewModel 15 | 16 | var body: some View { 17 | ItemView.CinematicScrollView(viewModel: viewModel) { 18 | ContentView(viewModel: viewModel) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/ItemView/SeriesItemView/SeriesItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SeriesItemView: View { 12 | 13 | @ObservedObject 14 | var viewModel: SeriesItemViewModel 15 | 16 | var body: some View { 17 | ItemView.CinematicScrollView(viewModel: viewModel) { 18 | ContentView(viewModel: viewModel) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/ListColumnsPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | struct ListColumnsPickerView: View { 13 | 14 | @Binding 15 | var selection: Int 16 | 17 | var body: some View { 18 | StepperView( 19 | title: L10n.columns, 20 | value: $selection, 21 | range: 1 ... 3, 22 | step: 1 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/SettingsView/ExperimentalSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | // Note: Used for experimental settings that may be removed or implemented 13 | // officially. Keep for future settings. 14 | 15 | struct ExperimentalSettingsView: View { 16 | 17 | var body: some View { 18 | SplitFormWindowView() 19 | .descriptionView { 20 | Image(systemName: "gearshape") 21 | .resizable() 22 | .aspectRatio(contentMode: .fit) 23 | .frame(maxWidth: 400) 24 | } 25 | .contentView {} 26 | .navigationTitle(L10n.experimental) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/VideoPlayer/Overlays/Components/ActionButtons/ActionButtons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension VideoPlayer.Overlay { 12 | 13 | enum ActionButtons {} 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin tvOS/Views/VideoPlayer/Overlays/ConfirmCloseOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ConfirmCloseOverlay: View { 12 | var body: some View { 13 | VStack { 14 | HStack { 15 | Image(systemName: "xmark.circle.fill") 16 | .font(.system(size: 96)) 17 | .padding(3) 18 | .background(Color.black.opacity(0.4).mask(Circle())) 19 | 20 | Spacer() 21 | } 22 | .padding() 23 | 24 | Spacer() 25 | } 26 | .padding() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swiftfin.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // Swiftfin is subject to the terms of the Mozilla Public 8 | // License, v2.0. If a copy of the MPL was not distributed with this 9 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 10 | // 11 | // Copyright (c) ___YEAR___ Jellyfin & Jellyfin Contributors 12 | // 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swiftfin/Components/DotHStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DotHStack: View { 12 | 13 | @ViewBuilder 14 | var content: () -> Content 15 | 16 | var body: some View { 17 | SeparatorHStack(content) 18 | .separator { 19 | Circle() 20 | .frame(width: 2, height: 2) 21 | .padding(.horizontal, 5) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Swiftfin/Components/Video3DFormatPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | struct Video3DFormatPicker: View { 13 | let title: String 14 | @Binding 15 | var selectedFormat: Video3DFormat? 16 | 17 | var body: some View { 18 | Picker(title, selection: $selectedFormat) { 19 | Text(L10n.none).tag(nil as Video3DFormat?) 20 | ForEach(Video3DFormat.allCases, id: \.self) { format in 21 | Text(format.displayTitle).tag(format as Video3DFormat?) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Swiftfin/Components/iOS15View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | // TODO: remove when iOS 15 support removed 12 | struct iOS15View: View { 13 | 14 | let iOS15: () -> iOS15Content 15 | let content: () -> Content 16 | 17 | var body: some View { 18 | if #available(iOS 16, *) { 19 | content() 20 | } else { 21 | iOS15() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Swiftfin/Extensions/View/Modifiers/DetectOrientationModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetectOrientation: ViewModifier { 12 | 13 | @Binding 14 | var orientation: UIDeviceOrientation 15 | 16 | func body(content: Content) -> some View { 17 | content 18 | .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in 19 | orientation = UIDevice.current.orientation 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Swiftfin/Extensions/View/Modifiers/NavigationBarDrawerButtons/NavigationBarDrawerModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NavigationBarDrawerModifier: ViewModifier { 12 | 13 | private let drawer: () -> Drawer 14 | 15 | init(@ViewBuilder drawer: @escaping () -> Drawer) { 16 | self.drawer = drawer 17 | } 18 | 19 | func body(content: Content) -> some View { 20 | NavigationBarDrawerView { 21 | drawer() 22 | .ignoresSafeArea() 23 | } content: { 24 | content 25 | } 26 | .ignoresSafeArea() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin/Extensions/View/Modifiers/NavigationBarOffset/NavigationBarOffsetModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NavigationBarOffsetModifier: ViewModifier { 12 | 13 | @Binding 14 | var scrollViewOffset: CGFloat 15 | 16 | let start: CGFloat 17 | let end: CGFloat 18 | 19 | func body(content: Content) -> some View { 20 | NavigationBarOffsetView( 21 | scrollViewOffset: $scrollViewOffset, 22 | start: start, 23 | end: end 24 | ) { 25 | content 26 | } 27 | .ignoresSafeArea() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swiftfin/Objects/DeepLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | import JellyfinAPI 11 | 12 | enum DeepLink { 13 | case item(BaseItemDto) 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-blue.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-green.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-jellyfin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-jellyfin.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-orange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-orange.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-red.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-dark-yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-yellow.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-blue.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-green.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-jellyfin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-jellyfin.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-orange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-orange.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-red.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedDark-yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedDark-yellow.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-blue.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-green.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-jellyfin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-jellyfin.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-orange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-orange.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-red.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-invertedLight-yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-invertedLight-yellow.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-blue.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-green.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-jellyfin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-jellyfin.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-orange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-orange.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-red.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-light-yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-yellow.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcon-primary-primary.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-primary-primary.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/AppIcon-dark-blue.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-blue.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-blue.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/AppIcon-dark-green.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-green.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-green.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/AppIcon-dark-jellyfin.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-jellyfin.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-jellyfin.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/AppIcon-dark-orange.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-orange.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-orange.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/AppIcon-dark-red.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-red.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-red.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/AppIcon-dark-yellow.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/AppIcon-dark-yellow.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-dark-yellow.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Dark/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blue.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-blue.appiconset/blue.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "green.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-green.appiconset/green.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "jellyfin.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-jellyfin.appiconset/jellyfin.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "orange.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-orange.appiconset/orange.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "red.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-red.appiconset/red.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "yellow.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/AppIcon-invertedDark-yellow.appiconset/yellow.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Dark/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "blue.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-blue.appiconset/blue.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "green.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-green.appiconset/green.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "jellyfin.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-jellyfin.appiconset/jellyfin.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "orange.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-orange.appiconset/orange.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "red.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-red.appiconset/red.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "yellow.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/AppIcon-invertedLight-yellow.appiconset/yellow.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Inverted-Light/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/AppIcon-light-blue.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-blue.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-blue.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/AppIcon-light-green.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-green.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-green.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/AppIcon-light-jellyfin.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-jellyfin.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-jellyfin.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/AppIcon-light-orange.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-orange.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-orange.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/AppIcon-light-red.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-red.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-red.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/AppIcon-light-yellow.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/AppIcon-light-yellow.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-light-yellow.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Light/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/AppIcon-primary-primary.png -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/AppIcon-primary-primary.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-primary-primary.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/AppIcons/Primary/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-chrome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "chrome.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-chrome.imageset/chrome.svg: -------------------------------------------------------------------------------- 1 | Google Chrome icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-edge.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "edge.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-edgechromium.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "edgechromium.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-firefox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "firefox.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-html5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "html5.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-html5.imageset/html5.svg: -------------------------------------------------------------------------------- 1 | HTML5 icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-msie.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "msie.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-opera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "opera.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-opera.imageset/opera.svg: -------------------------------------------------------------------------------- 1 | Opera icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Browsers/Device-browser-safari.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "safari.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-android.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "android.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-android.imageset/android.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-apple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "apple.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-apple.imageset/apple.svg: -------------------------------------------------------------------------------- 1 | Apple 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-finamp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "finamp.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-kodi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "kodi.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-playstation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "playstation.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-playstation.imageset/playstation.svg: -------------------------------------------------------------------------------- 1 | PlayStation icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-roku.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "roku.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-samsungtv.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "samsungtv.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-webos.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "webOS.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-windows.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "windows.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-windows.imageset/windows.svg: -------------------------------------------------------------------------------- 1 | Windows icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-xbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "xbox.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Clients/Device-client-xbox.imageset/xbox.svg: -------------------------------------------------------------------------------- 1 | Xbox icon 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Other/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Other/Device-other-homeassistant.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "home-assistant.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Other/Device-other-other.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "other.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/DeviceIcons/Other/Device-other-other.imageset/other.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/git.commit.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "git.commit.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/jellyfin-blob-blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "jellyfin-blob.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/logo.github.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "logo.github.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/tomato.fresh.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "tomato.fresh.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Assets.xcassets/tomato.rotten.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "tomato.rotten.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Swiftfin/Resources/Swiftfin.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Swiftfin/Views/AdminDashboardView/ServerDevices/DeviceDetailsView/Components/Sections/CustomDeviceNameSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension DeviceDetailsView { 13 | struct CustomDeviceNameSection: View { 14 | @Binding 15 | var customName: String 16 | 17 | // MARK: - Body 18 | 19 | var body: some View { 20 | Section(L10n.name) { 21 | TextField( 22 | L10n.name, 23 | text: $customName 24 | ) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin/Views/AdminDashboardView/ServerTasks/EditServerTaskView/Components/Sections/DetailsSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension EditServerTaskView { 12 | 13 | struct DetailsSection: View { 14 | 15 | let category: String 16 | 17 | var body: some View { 18 | Section(L10n.details) { 19 | TextPairView(leading: L10n.category, trailing: category) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Swiftfin/Views/AdminDashboardView/ServerTasks/EditServerTaskView/Components/Sections/LastErrorSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension EditServerTaskView { 13 | 14 | struct LastErrorSection: View { 15 | 16 | let message: String 17 | 18 | var body: some View { 19 | Section(L10n.errorDetails) { 20 | Text(message) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUserSettings/ServerUserPermissionsView/Components/Sections/StatusSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension ServerUserPermissionsView { 13 | 14 | struct StatusSection: View { 15 | 16 | @Binding 17 | var policy: UserPolicy 18 | 19 | var body: some View { 20 | Section(L10n.status) { 21 | 22 | Toggle(L10n.active, isOn: Binding( 23 | get: { !(policy.isDisabled ?? false) }, 24 | set: { policy.isDisabled = !$0 } 25 | )) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swiftfin/Views/AdminDashboardView/ServerUsers/ServerUserSettings/ServerUserPermissionsView/Components/Sections/SyncPlaySection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension ServerUserPermissionsView { 13 | 14 | struct SyncPlaySection: View { 15 | 16 | @Binding 17 | var policy: UserPolicy 18 | 19 | var body: some View { 20 | Section(L10n.syncPlay) { 21 | 22 | CaseIterablePicker( 23 | L10n.permissions, 24 | selection: $policy.syncPlayAccess.coalesce(.none) 25 | ) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swiftfin/Views/DownloadTaskView/DownloadTaskView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | struct DownloadTaskView: View { 13 | 14 | @EnvironmentObject 15 | private var router: DownloadTaskCoordinator.Router 16 | 17 | @ObservedObject 18 | var downloadTask: DownloadTask 19 | 20 | var body: some View { 21 | ScrollView(showsIndicators: false) { 22 | ContentView(downloadTask: downloadTask) 23 | } 24 | .navigationBarCloseButton { 25 | router.dismissCoordinator() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swiftfin/Views/FontPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | import UIKit 11 | 12 | struct FontPickerView: View { 13 | 14 | @Binding 15 | var selection: String 16 | 17 | var body: some View { 18 | SelectorView( 19 | selection: $selection, 20 | sources: UIFont.familyNames 21 | ) 22 | .label { fontFamily in 23 | Text(fontFamily) 24 | .foregroundColor(.primary) 25 | .font(.custom(fontFamily, size: 18)) 26 | } 27 | .navigationTitle(L10n.subtitleFont) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swiftfin/Views/ItemEditorView/ItemImages/ItemPhotoPickerView/ItemPhotoPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ItemImagePicker: View { 12 | 13 | // MARK: - Observed, & Environment Objects 14 | 15 | @EnvironmentObject 16 | private var router: ItemImagePickerCoordinator.Router 17 | 18 | // MARK: - Body 19 | 20 | var body: some View { 21 | PhotoPickerView { 22 | router.route(to: \.cropImage, $0) 23 | } onCancel: { 24 | router.dismissCoordinator() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Swiftfin/Views/ItemView/Components/StudiosHStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import JellyfinAPI 10 | import SwiftUI 11 | 12 | extension ItemView { 13 | 14 | struct StudiosHStack: View { 15 | 16 | @EnvironmentObject 17 | private var router: ItemCoordinator.Router 18 | 19 | let studios: [NameGuidPair] 20 | 21 | var body: some View { 22 | PillHStack( 23 | title: L10n.studios, 24 | items: studios 25 | ).onSelect { studio in 26 | let viewModel = ItemLibraryViewModel(parent: studio) 27 | router.route(to: \.library, viewModel) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Swiftfin/Views/SettingsView/DebugSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | // NOTE: All settings *MUST* be surrounded by DEBUG compiler conditional as usage site 13 | 14 | #if DEBUG 15 | struct DebugSettingsView: View { 16 | 17 | @Default(.sendProgressReports) 18 | private var sendProgressReports 19 | 20 | var body: some View { 21 | Form { 22 | 23 | Toggle("Send Progress Reports", isOn: $sendProgressReports) 24 | } 25 | .navigationTitle("Debug") 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Swiftfin/Views/SettingsView/ExperimentalSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | // Note: Used for experimental settings that may be removed or implemented 13 | // officially. Keep for future settings. 14 | 15 | struct ExperimentalSettingsView: View { 16 | 17 | var body: some View { 18 | Form {} 19 | .navigationTitle(L10n.experimental) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/ActionButtonSelectorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | struct ActionButtonSelectorView: View { 13 | 14 | @Binding 15 | var selection: [VideoPlayerActionButton] 16 | 17 | var body: some View { 18 | OrderedSectionSelectorView( 19 | selection: $selection, 20 | sources: VideoPlayerActionButton.allCases 21 | ) 22 | .label { button in 23 | HStack { 24 | Image(systemName: button.settingsSystemImage) 25 | 26 | Text(button.displayTitle) 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swiftfin/Views/SettingsView/VideoPlayerSettingsView/Components/Sections/TransitionSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Defaults 10 | import SwiftUI 11 | 12 | extension VideoPlayerSettingsView { 13 | struct TransitionSection: View { 14 | @Default(.VideoPlayer.Transition.pauseOnBackground) 15 | private var pauseOnBackground 16 | @Default(.VideoPlayer.Transition.playOnActive) 17 | private var playOnActive 18 | 19 | var body: some View { 20 | Section(L10n.transition) { 21 | 22 | Toggle(L10n.pauseOnBackground, isOn: $pauseOnBackground) 23 | Toggle(L10n.playOnActive, isOn: $playOnActive) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Swiftfin/Views/UserProfileImagePicker/UserProfileImagePickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct UserProfileImagePickerView: View { 12 | 13 | // MARK: - Observed, & Environment Objects 14 | 15 | @EnvironmentObject 16 | private var router: UserProfileImageCoordinator.Router 17 | 18 | // MARK: - Body 19 | 20 | var body: some View { 21 | PhotoPickerView { 22 | router.route(to: \.cropImage, $0) 23 | } onCancel: { 24 | router.dismissCoordinator() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Swiftfin/Views/VideoPlayer/Overlays/Components/ActionButtons/ActionButtons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import Foundation 10 | 11 | extension VideoPlayer.Overlay { 12 | 13 | enum ActionButtons {} 14 | } 15 | -------------------------------------------------------------------------------- /Swiftfin/Views/VideoPlayer/VideoPlayer+Actions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Swiftfin is subject to the terms of the Mozilla Public 3 | // License, v2.0. If a copy of the MPL was not distributed with this 4 | // file, you can obtain one at https://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2025 Jellyfin & Jellyfin Contributors 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension VideoPlayer { 12 | 13 | enum Action { 14 | 15 | // MARK: Aspect Fill 16 | 17 | func aspectFill( 18 | state: UIGestureRecognizer.State, 19 | unitPoint: UnitPoint, 20 | scale: CGFloat 21 | ) {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Translations/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ar.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/bg.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/bg.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ca.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ca.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/cs.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/cs.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/da.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/da.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/el.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/el.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/eo.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/eo.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/es.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/eu.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/eu.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/fi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/fi.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/fr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/he.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/he.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/hi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/hi.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/hr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/hr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/hu.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/hu.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/id.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/id.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/it.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ja.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/kk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/kk.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ko.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/lb.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/lb.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/lt.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/lt.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/mk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/mk.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/nb-NO.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/nb-NO.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/nl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/nn.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/nn.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/pl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/pl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ps.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ps.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/pt-BR.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/pt.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ro.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ro.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/sk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/sk.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/sl.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/sl.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/sq.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/sq.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/sv.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/sv.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/ta.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/ta.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/th.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/th.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/tr.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/uk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/uk.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/vi.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /Translations/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/Translations/zh-Hant.lproj/Localizable.strings -------------------------------------------------------------------------------- /fastlane/Appfile.swift: -------------------------------------------------------------------------------- 1 | var appIdentifier: String { return "[[APP_IDENTIFIER]]" } // The bundle identifier of your app 2 | var appleID: String { return "[[APPLE_ID]]" } // Your Apple Developer Portal username 3 | 4 | 5 | 6 | // For more information about the Appfile, see: 7 | // https://docs.fastlane.tools/advanced/#appfile 8 | -------------------------------------------------------------------------------- /fastlane/FastlaneRunner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/Swiftfin/11824cf828fd1cf660db16151eadad86d387e7da/fastlane/FastlaneRunner -------------------------------------------------------------------------------- /fastlane/swift/Actions.swift: -------------------------------------------------------------------------------- 1 | // Actions.swift 2 | // Copyright (c) 2025 FastlaneTools 3 | 4 | // This autogenerated file will be overwritten or replaced when running "fastlane generate_swift" 5 | // 6 | // ** NOTE ** 7 | // This file is provided by fastlane and WILL be overwritten in future updates 8 | // If you want to add extra functionality to this project, create a new file in a 9 | // new group so that it won't be marked for upgrade 10 | // 11 | 12 | import Foundation 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.56] 17 | -------------------------------------------------------------------------------- /fastlane/swift/Appfile.swift: -------------------------------------------------------------------------------- 1 | // Appfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | var appIdentifier: String { return "" } // The bundle identifier of your app 5 | var appleID: String { return "" } // Your Apple Developer Portal username 6 | 7 | var teamID: String { return "" } // Developer Portal Team ID 8 | var itcTeam: String? { return nil } // App Store Connect Team ID (may be nil if no team) 9 | 10 | // you can even provide different app identifiers, Apple IDs and team names per lane: 11 | // More information: https://docs.fastlane.tools/advanced/#appfile 12 | 13 | // Please don't remove the lines below 14 | // They are used to detect outdated files 15 | // FastlaneRunnerAPIVersion [0.9.1] 16 | -------------------------------------------------------------------------------- /fastlane/swift/Deliverfile.swift: -------------------------------------------------------------------------------- 1 | // Deliverfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `deliver` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Deliverfile: DeliverfileProtocol { 15 | // If you want to enable `deliver`, run `fastlane deliver init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/Fastfile.swift: -------------------------------------------------------------------------------- 1 | // This class is automatically included in FastlaneRunner during build 2 | // If you have a custom Fastfile.swift, this file will be replaced by it 3 | // Don't modify this file unless you are familiar with how fastlane's swift code generation works 4 | // *** This file will be overwritten or replaced during build time *** 5 | 6 | import Foundation 7 | 8 | open class Fastfile: LaneFile { 9 | override public init() { 10 | super.init() 11 | } 12 | } 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.1] 17 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /fastlane/swift/FastlaneSwiftRunner/README.txt: -------------------------------------------------------------------------------- 1 | Don't modify the structure of this group including but not limited to: 2 | - renaming this group 3 | - adding sub groups 4 | - removing sub groups 5 | - adding new files 6 | - removing files 7 | 8 | If you modify anything in this folder, future fastlane upgrades may not be able to be applied automatically. 9 | 10 | If you need to add new groups, please add them at the root of the "Fastlane Runner" group. 11 | -------------------------------------------------------------------------------- /fastlane/swift/Gymfile.swift: -------------------------------------------------------------------------------- 1 | // Gymfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `gym` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Gymfile: GymfileProtocol { 15 | // If you want to enable `gym`, run `fastlane gym init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/Matchfile.swift: -------------------------------------------------------------------------------- 1 | // Matchfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `match` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Matchfile: MatchfileProtocol { 15 | // If you want to enable `match`, run `fastlane match init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/Plugins.swift: -------------------------------------------------------------------------------- 1 | // Plugins.swift 2 | // Copyright (c) 2025 FastlaneTools 3 | 4 | // This autogenerated file will be overwritten or replaced when installing/updating plugins or running "fastlane generate_swift" 5 | // 6 | // ** NOTE ** 7 | // This file is provided by fastlane and WILL be overwritten in future updates 8 | // If you want to add extra functionality to this project, create a new file in a 9 | // new group so that it won't be marked for upgrade 10 | // 11 | 12 | import Foundation 13 | 14 | // Please don't remove the lines below 15 | // They are used to detect outdated files 16 | // FastlaneRunnerAPIVersion [0.9.56] 17 | -------------------------------------------------------------------------------- /fastlane/swift/Precheckfile.swift: -------------------------------------------------------------------------------- 1 | // Precheckfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `precheck` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Precheckfile: PrecheckfileProtocol { 15 | // If you want to enable `precheck`, run `fastlane precheck init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/RunnerArgument.swift: -------------------------------------------------------------------------------- 1 | // RunnerArgument.swift 2 | // Copyright (c) 2025 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | struct RunnerArgument { 14 | let name: String 15 | let value: String 16 | } 17 | 18 | // Please don't remove the lines below 19 | // They are used to detect outdated files 20 | // FastlaneRunnerAPIVersion [0.9.2] 21 | -------------------------------------------------------------------------------- /fastlane/swift/Scanfile.swift: -------------------------------------------------------------------------------- 1 | // Scanfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `scan` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Scanfile: ScanfileProtocol { 15 | // If you want to enable `scan`, run `fastlane scan init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/Screengrabfile.swift: -------------------------------------------------------------------------------- 1 | // Screengrabfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `screengrab` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Screengrabfile: ScreengrabfileProtocol { 15 | // If you want to enable `screengrab`, run `fastlane screengrab init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/Snapshotfile.swift: -------------------------------------------------------------------------------- 1 | // Snapshotfile.swift 2 | // Copyright (c) 2024 FastlaneTools 3 | 4 | // This class is automatically included in FastlaneRunner during build 5 | 6 | // This autogenerated file will be overwritten or replaced during build time, or when you initialize `snapshot` 7 | // 8 | // ** NOTE ** 9 | // This file is provided by fastlane and WILL be overwritten in future updates 10 | // If you want to add extra functionality to this project, create a new file in a 11 | // new group so that it won't be marked for upgrade 12 | // 13 | 14 | public class Snapshotfile: SnapshotfileProtocol { 15 | // If you want to enable `snapshot`, run `fastlane snapshot init` 16 | // After, this file will be replaced with a custom implementation that contains values you supplied 17 | // during the `init` process, and you won't see this message 18 | } 19 | 20 | // Generated with fastlane 2.220.0 21 | -------------------------------------------------------------------------------- /fastlane/swift/SocketClientDelegateProtocol.swift: -------------------------------------------------------------------------------- 1 | // SocketClientDelegateProtocol.swift 2 | // Copyright (c) 2025 FastlaneTools 3 | 4 | // 5 | // ** NOTE ** 6 | // This file is provided by fastlane and WILL be overwritten in future updates 7 | // If you want to add extra functionality to this project, create a new file in a 8 | // new group so that it won't be marked for upgrade 9 | // 10 | 11 | import Foundation 12 | 13 | protocol SocketClientDelegateProtocol: AnyObject { 14 | func connectionsOpened() 15 | func connectionsClosed() 16 | func commandExecuted(serverResponse: SocketClientResponse, completion: (SocketClient) -> Void) 17 | } 18 | 19 | // Please don't remove the lines below 20 | // They are used to detect outdated files 21 | // FastlaneRunnerAPIVersion [0.9.2] 22 | -------------------------------------------------------------------------------- /fastlane/swift/formatting/Brewfile: -------------------------------------------------------------------------------- 1 | brew("swiftformat") 2 | -------------------------------------------------------------------------------- /fastlane/swift/formatting/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | task(default: %w[setup]) 4 | 5 | task(setup: [:brew, :lint]) 6 | 7 | task(:brew) do 8 | raise '`brew` is required. Please install brew. https://brew.sh/' unless system('which brew') 9 | 10 | puts('➡️ Brew') 11 | sh('brew bundle') 12 | end 13 | 14 | task(:lint) do 15 | Dir.chdir('..') do 16 | sh("swiftformat . --config formatting/.swiftformat --verbose --selfrequired waitWithPolling --exclude Fastfile.swift --swiftversion 4.0") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /fastlane/swift/upgrade_manifest.json: -------------------------------------------------------------------------------- 1 | {"Actions.swift":"Autogenerated API","Fastlane.swift":"Autogenerated API","DeliverfileProtocol.swift":"Autogenerated API","GymfileProtocol.swift":"Autogenerated API","MatchfileProtocol.swift":"Autogenerated API","Plugins.swift":"Autogenerated API","PrecheckfileProtocol.swift":"Autogenerated API","ScanfileProtocol.swift":"Autogenerated API","ScreengrabfileProtocol.swift":"Autogenerated API","SnapshotfileProtocol.swift":"Autogenerated API","LaneFileProtocol.swift":"Fastfile Components","OptionalConfigValue.swift":"Fastfile Components","Atomic.swift":"Networking","ControlCommand.swift":"Networking","RubyCommand.swift":"Networking","RubyCommandable.swift":"Networking","Runner.swift":"Networking","SocketClient.swift":"Networking","SocketClientDelegateProtocol.swift":"Networking","SocketResponse.swift":"Networking","main.swift":"Runner Code","ArgumentProcessor.swift":"Runner Code","RunnerArgument.swift":"Runner Code"} -------------------------------------------------------------------------------- /swiftgen.yml: -------------------------------------------------------------------------------- 1 | strings: 2 | inputs: Translations/en.lproj 3 | outputs: 4 | - templateName: structured-swift5 5 | output: Shared/Strings/Strings.swift 6 | --------------------------------------------------------------------------------