mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-11-27 00:00:37 +00:00
Cleanup (#1075)
This commit is contained in:
parent
b987d6d7ae
commit
fd4052ed53
@ -22,4 +22,22 @@ enum LetterPickerOrientation: String, CaseIterable, Defaults.Serializable, Displ
|
||||
return L10n.right
|
||||
}
|
||||
}
|
||||
|
||||
var alignment: Alignment {
|
||||
switch self {
|
||||
case .leading:
|
||||
.leading
|
||||
case .trailing:
|
||||
.trailing
|
||||
}
|
||||
}
|
||||
|
||||
var edge: Edge.Set {
|
||||
switch self {
|
||||
case .leading:
|
||||
.leading
|
||||
case .trailing:
|
||||
.trailing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension Backport {
|
||||
|
||||
enum ScrollIndicatorVisibility {
|
||||
|
||||
case automatic
|
||||
case visible
|
||||
case hidden
|
||||
case never
|
||||
|
||||
@available(iOS 16, tvOS 16, *)
|
||||
var supportedValue: SwiftUI.ScrollIndicatorVisibility {
|
||||
switch self {
|
||||
case .automatic: .automatic
|
||||
case .visible: .visible
|
||||
case .hidden: .hidden
|
||||
case .never: .never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -51,6 +51,18 @@ extension Backport where Content: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func scrollIndicators(_ visibility: Backport.ScrollIndicatorVisibility) -> some View {
|
||||
if #available(iOS 16, tvOS 16, *) {
|
||||
content.scrollIndicators(visibility.supportedValue)
|
||||
} else {
|
||||
content.introspect(.scrollView, on: .iOS(.v15), .tvOS(.v15)) { scrollView in
|
||||
scrollView.showsHorizontalScrollIndicator = visibility == .visible
|
||||
scrollView.showsVerticalScrollIndicator = visibility == .visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
// TODO: - remove comment when migrated away from Stinsen
|
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// TODO: both axes
|
||||
|
||||
struct ScrollIfLargerThanContainerModifier: ViewModifier {
|
||||
|
||||
@State
|
||||
private var contentSize: CGSize = .zero
|
||||
@State
|
||||
private var layoutSize: CGSize = .zero
|
||||
|
||||
let padding: CGFloat
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
AlternateLayoutView {
|
||||
Color.clear
|
||||
.trackingSize($layoutSize)
|
||||
} content: {
|
||||
ScrollView {
|
||||
content
|
||||
.trackingSize($contentSize)
|
||||
}
|
||||
.frame(maxHeight: contentSize.height >= layoutSize.height ? .infinity : contentSize.height)
|
||||
.backport
|
||||
.scrollDisabled(contentSize.height < layoutSize.height)
|
||||
.backport
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ScrollIfLargerThanModifier: ViewModifier {
|
||||
|
||||
@State
|
||||
private var contentSize: CGSize = .zero
|
||||
|
||||
let height: CGFloat
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
ScrollView {
|
||||
content
|
||||
.trackingSize($contentSize)
|
||||
}
|
||||
.backport
|
||||
.scrollDisabled(contentSize.height < height)
|
||||
.frame(maxHeight: contentSize.height >= height ? .infinity : contentSize.height)
|
||||
}
|
||||
}
|
@ -328,13 +328,13 @@ extension View {
|
||||
)
|
||||
}
|
||||
|
||||
func scroll(ifLargerThan height: CGFloat) -> some View {
|
||||
modifier(ScrollIfLargerThanModifier(height: height))
|
||||
func scrollIfLargerThanContainer(padding: CGFloat = 0) -> some View {
|
||||
modifier(ScrollIfLargerThanContainerModifier(padding: padding))
|
||||
}
|
||||
|
||||
// MARK: debug
|
||||
|
||||
// Useful modifiers during development for layout
|
||||
// Useful modifiers during development for layout without RocketSim
|
||||
|
||||
#if DEBUG
|
||||
func debugBackground<S: ShapeStyle>(_ fill: S = .red.opacity(0.5)) -> some View {
|
||||
|
@ -190,7 +190,7 @@ struct SelectUserView: View {
|
||||
|
||||
gridContentView
|
||||
}
|
||||
.scroll(ifLargerThan: contentSize.height - 100)
|
||||
.scrollIfLargerThanContainer(padding: 100)
|
||||
.scrollViewOffset($scrollViewOffset)
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
|
||||
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
|
||||
4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
|
||||
4EF7A3E22C031FEB00CC58A2 /* LetterPickerOverflow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */; };
|
||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
|
||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
|
||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
||||
@ -378,7 +377,7 @@
|
||||
E145EB422BE0A6EE003BF6F3 /* ServerSelectionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB412BE0A6EE003BF6F3 /* ServerSelectionMenu.swift */; };
|
||||
E145EB452BE0AD4E003BF6F3 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB442BE0AD4E003BF6F3 /* Set.swift */; };
|
||||
E145EB462BE0AD4E003BF6F3 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB442BE0AD4E003BF6F3 /* Set.swift */; };
|
||||
E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift */; };
|
||||
E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift */; };
|
||||
E145EB4B2BE16849003BF6F3 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E145EB4A2BE16849003BF6F3 /* KeychainSwift */; };
|
||||
E145EB4D2BE1688E003BF6F3 /* SwiftinStore+UserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB4C2BE1688E003BF6F3 /* SwiftinStore+UserState.swift */; };
|
||||
E145EB4F2BE168AC003BF6F3 /* SwiftfinStore+ServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB4E2BE168AC003BF6F3 /* SwiftfinStore+ServerState.swift */; };
|
||||
@ -545,7 +544,7 @@
|
||||
E174121029AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E174120E29AE9D94003EF3B5 /* NavigationCoordinatable.swift */; };
|
||||
E175AFF3299AC117004DCF52 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E175AFF2299AC117004DCF52 /* DebugSettingsView.swift */; };
|
||||
E17639F82BF2E25B004DF6AB /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A92BF077130082B8B2 /* Keychain.swift */; };
|
||||
E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift */; };
|
||||
E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanContainerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift */; };
|
||||
E1763A272BF303C9004DF6AB /* ServerSelectionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A262BF303C9004DF6AB /* ServerSelectionMenu.swift */; };
|
||||
E1763A292BF3046A004DF6AB /* AddUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A282BF3046A004DF6AB /* AddUserButton.swift */; };
|
||||
E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */; };
|
||||
@ -806,6 +805,8 @@
|
||||
E1D842912933F87500D1041A /* ItemFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D842902933F87500D1041A /* ItemFields.swift */; };
|
||||
E1D8429329340B8300D1041A /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8429229340B8300D1041A /* Utilities.swift */; };
|
||||
E1D8429529346C6400D1041A /* BasicStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8429429346C6400D1041A /* BasicStepper.swift */; };
|
||||
E1D90D762C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D90D752C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift */; };
|
||||
E1D90D772C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D90D752C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift */; };
|
||||
E1D9F475296E86D400129AF3 /* NativeVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D9F474296E86D400129AF3 /* NativeVideoPlayer.swift */; };
|
||||
E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */; };
|
||||
E1DA656F28E78C9900592A73 /* EpisodeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA656E28E78C9900592A73 /* EpisodeSelector.swift */; };
|
||||
@ -927,7 +928,6 @@
|
||||
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOrientation.swift; sourceTree = "<group>"; };
|
||||
4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
||||
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
|
||||
4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOverflow.swift; sourceTree = "<group>"; };
|
||||
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||
531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
|
||||
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -1198,7 +1198,7 @@
|
||||
E145EB242BE055AD003BF6F3 /* ServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerResponse.swift; sourceTree = "<group>"; };
|
||||
E145EB412BE0A6EE003BF6F3 /* ServerSelectionMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionMenu.swift; sourceTree = "<group>"; };
|
||||
E145EB442BE0AD4E003BF6F3 /* Set.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Set.swift; sourceTree = "<group>"; };
|
||||
E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollIfLargerThanModifier.swift; sourceTree = "<group>"; };
|
||||
E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollIfLargerThanContainerModifier.swift; sourceTree = "<group>"; };
|
||||
E145EB4C2BE1688E003BF6F3 /* SwiftinStore+UserState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftinStore+UserState.swift"; sourceTree = "<group>"; };
|
||||
E145EB4E2BE168AC003BF6F3 /* SwiftfinStore+ServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftfinStore+ServerState.swift"; sourceTree = "<group>"; };
|
||||
E146A9D72BE6E9830034DA1E /* StoredValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredValue.swift; sourceTree = "<group>"; };
|
||||
@ -1458,6 +1458,7 @@
|
||||
E1D842902933F87500D1041A /* ItemFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemFields.swift; sourceTree = "<group>"; };
|
||||
E1D8429229340B8300D1041A /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
E1D8429429346C6400D1041A /* BasicStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicStepper.swift; sourceTree = "<group>"; };
|
||||
E1D90D752C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BackPort+ScrollIndicatorVisibility.swift"; sourceTree = "<group>"; };
|
||||
E1D9F474296E86D400129AF3 /* NativeVideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoPlayer.swift; sourceTree = "<group>"; };
|
||||
E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialFeatureType.swift; sourceTree = "<group>"; };
|
||||
E1DA656E28E78C9900592A73 /* EpisodeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeSelector.swift; sourceTree = "<group>"; };
|
||||
@ -1656,7 +1657,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E16FD502C0183DB00110147 /* LetterPickerButton.swift */,
|
||||
4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@ -2499,7 +2499,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E170D101294CE4C10017224C /* Modifiers */,
|
||||
E15D4F062B1B12C300442DB8 /* Backport.swift */,
|
||||
E1D90D742C051D3B000EA787 /* Backport */,
|
||||
E1E1E24C28DF8A2E000DF5FD /* PreferenceKeys.swift */,
|
||||
6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */,
|
||||
);
|
||||
@ -2839,7 +2839,7 @@
|
||||
E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */,
|
||||
E43918652AD5C8310045A18C /* OnScenePhaseChangedModifier.swift */,
|
||||
E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */,
|
||||
E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift */,
|
||||
E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift */,
|
||||
E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */,
|
||||
E1E2F8442B757E3400B75998 /* SinceLastDisappearModifier.swift */,
|
||||
);
|
||||
@ -3367,6 +3367,15 @@
|
||||
path = Slider;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1D90D742C051D3B000EA787 /* Backport */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E15D4F062B1B12C300442DB8 /* Backport.swift */,
|
||||
E1D90D752C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift */,
|
||||
);
|
||||
path = Backport;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E1DABAD62A26E28E008AC34A /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -4102,7 +4111,7 @@
|
||||
E1575E5D293E77B5001665B1 /* ItemViewType.swift in Sources */,
|
||||
E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
||||
E1575E7A293E77B5001665B1 /* TimeStampType.swift in Sources */,
|
||||
E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanModifier.swift in Sources */,
|
||||
E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanContainerModifier.swift in Sources */,
|
||||
E11E374E293E7F08009EF240 /* MediaSourceInfo.swift in Sources */,
|
||||
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */,
|
||||
E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */,
|
||||
@ -4152,6 +4161,7 @@
|
||||
E10B1ECB2BD9AF8200A92EAF /* SwiftfinStore+V1.swift in Sources */,
|
||||
E154966B296CA2EF00C4EF88 /* DownloadManager.swift in Sources */,
|
||||
535870632669D21600D05A09 /* SwiftfinApp.swift in Sources */,
|
||||
E1D90D772C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
|
||||
E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */,
|
||||
E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */,
|
||||
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */,
|
||||
@ -4253,7 +4263,6 @@
|
||||
E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */,
|
||||
E129429828F4785200796AC6 /* CaseIterablePicker.swift in Sources */,
|
||||
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */,
|
||||
4EF7A3E22C031FEB00CC58A2 /* LetterPickerOverflow.swift in Sources */,
|
||||
E154965E296CA2EF00C4EF88 /* DownloadTask.swift in Sources */,
|
||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
E1E2F8422B757E0900B75998 /* OnFirstAppearModifier.swift in Sources */,
|
||||
@ -4343,6 +4352,7 @@
|
||||
E1B5784128F8AFCB00D42911 /* WrappedView.swift in Sources */,
|
||||
E1921B7428E61914003A5238 /* SpecialFeatureHStack.swift in Sources */,
|
||||
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
||||
E1D90D762C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
|
||||
6264E88C273850380081A12A /* Strings.swift in Sources */,
|
||||
E145EB252BE055AD003BF6F3 /* ServerResponse.swift in Sources */,
|
||||
E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */,
|
||||
@ -4392,7 +4402,7 @@
|
||||
E170D107294D23BA0017224C /* MediaSourceInfoCoordinator.swift in Sources */,
|
||||
E102313B2BCF8A3C009D71FC /* ProgramProgressOverlay.swift in Sources */,
|
||||
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */,
|
||||
E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift in Sources */,
|
||||
E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift in Sources */,
|
||||
E1CAF65F2BA345830087D991 /* MediaViewModel.swift in Sources */,
|
||||
E1EA9F6A28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */,
|
||||
E133328D2953AE4B00EE76AB /* CircularProgressView.swift in Sources */,
|
||||
|
@ -34,7 +34,7 @@
|
||||
"location" : "https://github.com/LePips/CollectionVGrid",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "b50b5241df5fc1d71e5a09f6a87731c67c2a79e5"
|
||||
"revision" : "91ba930a502761924204ae74a59ded05f3b7ef89"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -19,36 +19,31 @@ extension LetterPickerBar {
|
||||
@Environment(\.isSelected)
|
||||
private var isSelected
|
||||
|
||||
private let filterLetter: ItemLetter
|
||||
private let letter: ItemLetter
|
||||
private let viewModel: FilterViewModel
|
||||
|
||||
init(filterLetter: ItemLetter, viewModel: FilterViewModel) {
|
||||
self.filterLetter = filterLetter
|
||||
init(letter: ItemLetter, viewModel: FilterViewModel) {
|
||||
self.letter = letter
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
if !viewModel.currentFilters.letter.contains(filterLetter) {
|
||||
viewModel.currentFilters.letter = [ItemLetter(stringLiteral: filterLetter.value)]
|
||||
if !viewModel.currentFilters.letter.contains(letter) {
|
||||
viewModel.currentFilters.letter = [ItemLetter(stringLiteral: letter.value)]
|
||||
} else {
|
||||
viewModel.currentFilters.letter = []
|
||||
}
|
||||
} label: {
|
||||
Text(
|
||||
filterLetter.value
|
||||
)
|
||||
.environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter))
|
||||
.font(.headline)
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundStyle(isSelected ? accentColor.overlayColor : accentColor)
|
||||
.padding(.vertical, 2)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.background {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(isSelected ? accentColor.opacity(0.5) : Color.clear)
|
||||
.foregroundStyle(isSelected ? accentColor : Color.clear)
|
||||
|
||||
Text(letter.value)
|
||||
.font(.headline)
|
||||
.foregroundStyle(isSelected ? accentColor.overlayColor : accentColor)
|
||||
}
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
//
|
||||
// Swiftfin is subject to the terms of the Mozilla Public
|
||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct LetterPickerOverflow: ViewModifier {
|
||||
@State
|
||||
private var contentOverflow: Bool = false
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
GeometryReader { geometry in
|
||||
content
|
||||
.background(
|
||||
GeometryReader { contentGeometry in
|
||||
Color.clear.onAppear {
|
||||
contentOverflow = contentGeometry.size.height > geometry.size.height
|
||||
}
|
||||
}
|
||||
)
|
||||
.wrappedInScrollView(when: contentOverflow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func wrappedInScrollView(when condition: Bool) -> some View {
|
||||
if condition {
|
||||
ScrollView(showsIndicators: false) {
|
||||
self
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
} else {
|
||||
self
|
||||
.frame(width: 30, alignment: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func scrollOnOverflow() -> some View {
|
||||
modifier(LetterPickerOverflow())
|
||||
}
|
||||
}
|
@ -10,28 +10,26 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct LetterPickerBar: View {
|
||||
@ObservedObject
|
||||
private var viewModel: FilterViewModel
|
||||
|
||||
init(viewModel: FilterViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
@ObservedObject
|
||||
var viewModel: FilterViewModel
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
ForEach(ItemLetter.allCases, id: \.hashValue) { filterLetter in
|
||||
LetterPickerButton(
|
||||
filterLetter: filterLetter,
|
||||
letter: filterLetter,
|
||||
viewModel: viewModel
|
||||
)
|
||||
.environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter))
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.scrollOnOverflow()
|
||||
.scrollIfLargerThanContainer()
|
||||
.frame(width: 30, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +180,7 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
// Note: if parent is a folders then other items will have labels,
|
||||
// so an empty content view is necessary
|
||||
|
||||
@ViewBuilder
|
||||
private func landscapeGridItemView(item: Element) -> some View {
|
||||
PosterButton(item: item, type: .landscape)
|
||||
.content {
|
||||
@ -199,6 +200,7 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func portraitGridItemView(item: Element) -> some View {
|
||||
PosterButton(item: item, type: .portrait)
|
||||
.content {
|
||||
@ -218,6 +220,7 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func listItemView(item: Element, posterType: PosterDisplayType) -> some View {
|
||||
LibraryRow(item: item, posterType: posterType)
|
||||
.onSelect {
|
||||
@ -233,7 +236,8 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var contentView: some View {
|
||||
@ViewBuilder
|
||||
private var gridView: some View {
|
||||
CollectionVGrid(
|
||||
$viewModel.elements,
|
||||
layout: $layout
|
||||
@ -256,33 +260,40 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
viewModel.send(.getNextPage)
|
||||
}
|
||||
.proxy(collectionVGridProxy)
|
||||
.scrollIndicatorsVisible(false)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func contentLetterBarView(content: some View) -> some View {
|
||||
private var innerContent: some View {
|
||||
switch viewModel.state {
|
||||
case .content:
|
||||
if viewModel.elements.isEmpty {
|
||||
L10n.noResults.text
|
||||
} else {
|
||||
gridView
|
||||
}
|
||||
case .initial, .refreshing:
|
||||
DelayedProgressView()
|
||||
default:
|
||||
AssertionFailureView("Expected view for unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var contentView: some View {
|
||||
if letterPickerEnabled, let filterViewModel = viewModel.filterViewModel {
|
||||
switch letterPickerOrientation {
|
||||
case .trailing:
|
||||
HStack(spacing: 0) {
|
||||
content
|
||||
.frame(maxWidth: .infinity)
|
||||
ZStack(alignment: letterPickerOrientation.alignment) {
|
||||
innerContent
|
||||
.padding(letterPickerOrientation.edge, 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
LetterPickerBar(viewModel: filterViewModel)
|
||||
.padding(.top, safeArea.top)
|
||||
.padding(.bottom, safeArea.bottom)
|
||||
}
|
||||
case .leading:
|
||||
HStack(spacing: 0) {
|
||||
LetterPickerBar(viewModel: filterViewModel)
|
||||
.padding(.top, safeArea.top)
|
||||
.padding(.bottom, safeArea.bottom)
|
||||
|
||||
content
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
LetterPickerBar(viewModel: filterViewModel)
|
||||
.padding(.top, safeArea.top)
|
||||
.padding(.bottom, safeArea.bottom)
|
||||
.padding(letterPickerOrientation.edge, 10)
|
||||
}
|
||||
} else {
|
||||
content
|
||||
innerContent
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,17 +303,13 @@ struct PagingLibraryView<Element: Poster>: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
|
||||
switch viewModel.state {
|
||||
case .content:
|
||||
if viewModel.elements.isEmpty {
|
||||
contentLetterBarView(content: L10n.noResults.text)
|
||||
} else {
|
||||
contentLetterBarView(content: contentView)
|
||||
}
|
||||
case .content, .initial, .refreshing:
|
||||
contentView
|
||||
case let .error(error):
|
||||
errorView(with: error)
|
||||
case .initial, .refreshing:
|
||||
contentLetterBarView(content: DelayedProgressView())
|
||||
}
|
||||
}
|
||||
.animation(.linear(duration: 0.1), value: viewModel.state)
|
||||
|
@ -20,8 +20,6 @@ import SwiftUI
|
||||
// TODO: user ordering
|
||||
// - name
|
||||
// - last signed in date
|
||||
// TODO: for random splash screen, instead have a random sorted array
|
||||
// for failure cases
|
||||
|
||||
struct SelectUserView: View {
|
||||
|
||||
@ -45,8 +43,6 @@ struct SelectUserView: View {
|
||||
@EnvironmentObject
|
||||
private var router: SelectUserCoordinator.Router
|
||||
|
||||
@State
|
||||
private var contentSafeAreaInsets: EdgeInsets = .zero
|
||||
@State
|
||||
private var contentSize: CGSize = .zero
|
||||
@State
|
||||
@ -70,7 +66,7 @@ struct SelectUserView: View {
|
||||
@State
|
||||
private var selectedUsers: Set<UserState> = []
|
||||
@State
|
||||
private var splashScreenImageSource: ImageSource? = nil
|
||||
private var splashScreenImageSources: [ImageSource] = []
|
||||
|
||||
@StateObject
|
||||
private var viewModel = SelectUserViewModel()
|
||||
@ -115,25 +111,27 @@ struct SelectUserView: View {
|
||||
}
|
||||
|
||||
// For all server selection, .all is random
|
||||
private func makeSplashScreenImageSource(
|
||||
private func makeSplashScreenImageSources(
|
||||
serverSelection: SelectUserServerSelection,
|
||||
allServersSelection: SelectUserServerSelection
|
||||
) -> ImageSource? {
|
||||
) -> [ImageSource] {
|
||||
switch (serverSelection, allServersSelection) {
|
||||
case (.all, .all):
|
||||
return viewModel
|
||||
.servers
|
||||
.keys
|
||||
.randomElement()?
|
||||
.splashScreenImageSource()
|
||||
.shuffled()
|
||||
.map { $0.splashScreenImageSource() }
|
||||
|
||||
// need to evaluate server with id selection first
|
||||
case let (.server(id), _), let (.all, .server(id)):
|
||||
return viewModel
|
||||
.servers
|
||||
.keys
|
||||
.first { $0.id == id }?
|
||||
.splashScreenImageSource()
|
||||
return [
|
||||
viewModel
|
||||
.servers
|
||||
.keys
|
||||
.first { $0.id == id }?
|
||||
.splashScreenImageSource() ?? .init(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +250,7 @@ struct SelectUserView: View {
|
||||
}
|
||||
}
|
||||
.edgePadding()
|
||||
.scroll(ifLargerThan: contentSize.height - 100)
|
||||
.scrollIfLargerThanContainer(padding: 100)
|
||||
.onChange(of: gridItemSize) { newValue in
|
||||
let columns = Int(contentSize.width / (newValue.width + EdgeInsets.edgePadding))
|
||||
|
||||
@ -274,7 +272,7 @@ struct SelectUserView: View {
|
||||
}
|
||||
}
|
||||
.edgePadding()
|
||||
.scroll(ifLargerThan: contentSize.height - 100)
|
||||
.scrollIfLargerThanContainer(padding: 100)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@ -386,9 +384,8 @@ struct SelectUserView: View {
|
||||
VStack(spacing: 0) {
|
||||
ZStack {
|
||||
Color.clear
|
||||
.onSizeChanged { size, safeAreaInsets in
|
||||
.onSizeChanged { size, _ in
|
||||
contentSize = size
|
||||
contentSafeAreaInsets = safeAreaInsets
|
||||
}
|
||||
|
||||
switch userListDisplayType {
|
||||
@ -433,16 +430,16 @@ struct SelectUserView: View {
|
||||
}
|
||||
}
|
||||
.background {
|
||||
if selectUserUseSplashscreen, let splashScreenImageSource {
|
||||
if selectUserUseSplashscreen, splashScreenImageSources.isNotEmpty {
|
||||
ZStack {
|
||||
Color.clear
|
||||
|
||||
ImageView(splashScreenImageSource)
|
||||
ImageView(splashScreenImageSources)
|
||||
.pipeline(.Swiftfin.branding)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.id(splashScreenImageSource)
|
||||
.id(splashScreenImageSources)
|
||||
.transition(.opacity)
|
||||
.animation(.linear, value: splashScreenImageSource)
|
||||
.animation(.linear, value: splashScreenImageSources)
|
||||
|
||||
Color.black
|
||||
.opacity(0.9)
|
||||
@ -515,7 +512,7 @@ struct SelectUserView: View {
|
||||
.onAppear {
|
||||
viewModel.send(.getServers)
|
||||
|
||||
splashScreenImageSource = makeSplashScreenImageSource(
|
||||
splashScreenImageSources = makeSplashScreenImageSources(
|
||||
serverSelection: serverSelection,
|
||||
allServersSelection: selectUserAllServersSplashscreen
|
||||
)
|
||||
@ -537,7 +534,7 @@ struct SelectUserView: View {
|
||||
}
|
||||
}
|
||||
.onChange(of: selectUserAllServersSplashscreen) { newValue in
|
||||
splashScreenImageSource = makeSplashScreenImageSource(
|
||||
splashScreenImageSources = makeSplashScreenImageSources(
|
||||
serverSelection: serverSelection,
|
||||
allServersSelection: newValue
|
||||
)
|
||||
@ -545,7 +542,7 @@ struct SelectUserView: View {
|
||||
.onChange(of: serverSelection) { newValue in
|
||||
gridItems = makeGridItems(for: newValue)
|
||||
|
||||
splashScreenImageSource = makeSplashScreenImageSource(
|
||||
splashScreenImageSources = makeSplashScreenImageSources(
|
||||
serverSelection: newValue,
|
||||
allServersSelection: selectUserAllServersSplashscreen
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user