This commit is contained in:
Ethan Pippin 2024-05-27 19:59:18 -06:00 committed by GitHub
parent b987d6d7ae
commit fd4052ed53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 201 additions and 172 deletions

View File

@ -22,4 +22,22 @@ enum LetterPickerOrientation: String, CaseIterable, Defaults.Serializable, Displ
return L10n.right 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
}
}
} }

View File

@ -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
}
}
}
}

View File

@ -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) #if os(iOS)
// TODO: - remove comment when migrated away from Stinsen // TODO: - remove comment when migrated away from Stinsen

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -328,13 +328,13 @@ extension View {
) )
} }
func scroll(ifLargerThan height: CGFloat) -> some View { func scrollIfLargerThanContainer(padding: CGFloat = 0) -> some View {
modifier(ScrollIfLargerThanModifier(height: height)) modifier(ScrollIfLargerThanContainerModifier(padding: padding))
} }
// MARK: debug // MARK: debug
// Useful modifiers during development for layout // Useful modifiers during development for layout without RocketSim
#if DEBUG #if DEBUG
func debugBackground<S: ShapeStyle>(_ fill: S = .red.opacity(0.5)) -> some View { func debugBackground<S: ShapeStyle>(_ fill: S = .red.opacity(0.5)) -> some View {

View File

@ -190,7 +190,7 @@ struct SelectUserView: View {
gridContentView gridContentView
} }
.scroll(ifLargerThan: contentSize.height - 100) .scrollIfLargerThanContainer(padding: 100)
.scrollViewOffset($scrollViewOffset) .scrollViewOffset($scrollViewOffset)
} }

View File

@ -16,7 +16,6 @@
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; }; 4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4E8B34EB2AB91B6E0018F305 /* 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 */; }; 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; }; 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.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 */; }; E145EB422BE0A6EE003BF6F3 /* ServerSelectionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB412BE0A6EE003BF6F3 /* ServerSelectionMenu.swift */; };
E145EB452BE0AD4E003BF6F3 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB442BE0AD4E003BF6F3 /* Set.swift */; }; E145EB452BE0AD4E003BF6F3 /* Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB442BE0AD4E003BF6F3 /* Set.swift */; };
E145EB462BE0AD4E003BF6F3 /* 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 */; }; E145EB4B2BE16849003BF6F3 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E145EB4A2BE16849003BF6F3 /* KeychainSwift */; };
E145EB4D2BE1688E003BF6F3 /* SwiftinStore+UserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E145EB4C2BE1688E003BF6F3 /* SwiftinStore+UserState.swift */; }; 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 */; }; 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 */; }; E174121029AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E174120E29AE9D94003EF3B5 /* NavigationCoordinatable.swift */; };
E175AFF3299AC117004DCF52 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E175AFF2299AC117004DCF52 /* DebugSettingsView.swift */; }; E175AFF3299AC117004DCF52 /* DebugSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E175AFF2299AC117004DCF52 /* DebugSettingsView.swift */; };
E17639F82BF2E25B004DF6AB /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A92BF077130082B8B2 /* Keychain.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 */; }; E1763A272BF303C9004DF6AB /* ServerSelectionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A262BF303C9004DF6AB /* ServerSelectionMenu.swift */; };
E1763A292BF3046A004DF6AB /* AddUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A282BF3046A004DF6AB /* AddUserButton.swift */; }; E1763A292BF3046A004DF6AB /* AddUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A282BF3046A004DF6AB /* AddUserButton.swift */; };
E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.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 */; }; E1D842912933F87500D1041A /* ItemFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D842902933F87500D1041A /* ItemFields.swift */; };
E1D8429329340B8300D1041A /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8429229340B8300D1041A /* Utilities.swift */; }; E1D8429329340B8300D1041A /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8429229340B8300D1041A /* Utilities.swift */; };
E1D8429529346C6400D1041A /* BasicStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D8429429346C6400D1041A /* BasicStepper.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 */; }; E1D9F475296E86D400129AF3 /* NativeVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D9F474296E86D400129AF3 /* NativeVideoPlayer.swift */; };
E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */; }; E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */; };
E1DA656F28E78C9900592A73 /* EpisodeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DA656E28E78C9900592A73 /* EpisodeSelector.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; E1DA656E28E78C9900592A73 /* EpisodeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeSelector.swift; sourceTree = "<group>"; };
@ -1656,7 +1657,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4E16FD502C0183DB00110147 /* LetterPickerButton.swift */, 4E16FD502C0183DB00110147 /* LetterPickerButton.swift */,
4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2499,7 +2499,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E170D101294CE4C10017224C /* Modifiers */, E170D101294CE4C10017224C /* Modifiers */,
E15D4F062B1B12C300442DB8 /* Backport.swift */, E1D90D742C051D3B000EA787 /* Backport */,
E1E1E24C28DF8A2E000DF5FD /* PreferenceKeys.swift */, E1E1E24C28DF8A2E000DF5FD /* PreferenceKeys.swift */,
6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */, 6220D0AC26D5EABB00B8E046 /* ViewExtensions.swift */,
); );
@ -2839,7 +2839,7 @@
E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */, E129428428F080B500796AC6 /* OnReceiveNotificationModifier.swift */,
E43918652AD5C8310045A18C /* OnScenePhaseChangedModifier.swift */, E43918652AD5C8310045A18C /* OnScenePhaseChangedModifier.swift */,
E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */, E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */,
E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift */, E145EB472BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift */,
E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */, E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */,
E1E2F8442B757E3400B75998 /* SinceLastDisappearModifier.swift */, E1E2F8442B757E3400B75998 /* SinceLastDisappearModifier.swift */,
); );
@ -3367,6 +3367,15 @@
path = Slider; path = Slider;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E1D90D742C051D3B000EA787 /* Backport */ = {
isa = PBXGroup;
children = (
E15D4F062B1B12C300442DB8 /* Backport.swift */,
E1D90D752C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift */,
);
path = Backport;
sourceTree = "<group>";
};
E1DABAD62A26E28E008AC34A /* Resources */ = { E1DABAD62A26E28E008AC34A /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -4102,7 +4111,7 @@
E1575E5D293E77B5001665B1 /* ItemViewType.swift in Sources */, E1575E5D293E77B5001665B1 /* ItemViewType.swift in Sources */,
E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */, E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
E1575E7A293E77B5001665B1 /* TimeStampType.swift in Sources */, E1575E7A293E77B5001665B1 /* TimeStampType.swift in Sources */,
E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanModifier.swift in Sources */, E1763A252BF2F77B004DF6AB /* ScrollIfLargerThanContainerModifier.swift in Sources */,
E11E374E293E7F08009EF240 /* MediaSourceInfo.swift in Sources */, E11E374E293E7F08009EF240 /* MediaSourceInfo.swift in Sources */,
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */, E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */,
E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */, E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */,
@ -4152,6 +4161,7 @@
E10B1ECB2BD9AF8200A92EAF /* SwiftfinStore+V1.swift in Sources */, E10B1ECB2BD9AF8200A92EAF /* SwiftfinStore+V1.swift in Sources */,
E154966B296CA2EF00C4EF88 /* DownloadManager.swift in Sources */, E154966B296CA2EF00C4EF88 /* DownloadManager.swift in Sources */,
535870632669D21600D05A09 /* SwiftfinApp.swift in Sources */, 535870632669D21600D05A09 /* SwiftfinApp.swift in Sources */,
E1D90D772C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */, E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */,
E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */, E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */,
E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */, E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */,
@ -4253,7 +4263,6 @@
E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */, E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */,
E129429828F4785200796AC6 /* CaseIterablePicker.swift in Sources */, E129429828F4785200796AC6 /* CaseIterablePicker.swift in Sources */,
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */, E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */,
4EF7A3E22C031FEB00CC58A2 /* LetterPickerOverflow.swift in Sources */,
E154965E296CA2EF00C4EF88 /* DownloadTask.swift in Sources */, E154965E296CA2EF00C4EF88 /* DownloadTask.swift in Sources */,
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
E1E2F8422B757E0900B75998 /* OnFirstAppearModifier.swift in Sources */, E1E2F8422B757E0900B75998 /* OnFirstAppearModifier.swift in Sources */,
@ -4343,6 +4352,7 @@
E1B5784128F8AFCB00D42911 /* WrappedView.swift in Sources */, E1B5784128F8AFCB00D42911 /* WrappedView.swift in Sources */,
E1921B7428E61914003A5238 /* SpecialFeatureHStack.swift in Sources */, E1921B7428E61914003A5238 /* SpecialFeatureHStack.swift in Sources */,
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */, E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
E1D90D762C051D44000EA787 /* BackPort+ScrollIndicatorVisibility.swift in Sources */,
6264E88C273850380081A12A /* Strings.swift in Sources */, 6264E88C273850380081A12A /* Strings.swift in Sources */,
E145EB252BE055AD003BF6F3 /* ServerResponse.swift in Sources */, E145EB252BE055AD003BF6F3 /* ServerResponse.swift in Sources */,
E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */, E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */,
@ -4392,7 +4402,7 @@
E170D107294D23BA0017224C /* MediaSourceInfoCoordinator.swift in Sources */, E170D107294D23BA0017224C /* MediaSourceInfoCoordinator.swift in Sources */,
E102313B2BCF8A3C009D71FC /* ProgramProgressOverlay.swift in Sources */, E102313B2BCF8A3C009D71FC /* ProgramProgressOverlay.swift in Sources */,
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */, E1937A61288F32DB00CB80AA /* Poster.swift in Sources */,
E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanModifier.swift in Sources */, E145EB482BE0C136003BF6F3 /* ScrollIfLargerThanContainerModifier.swift in Sources */,
E1CAF65F2BA345830087D991 /* MediaViewModel.swift in Sources */, E1CAF65F2BA345830087D991 /* MediaViewModel.swift in Sources */,
E1EA9F6A28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */, E1EA9F6A28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */,
E133328D2953AE4B00EE76AB /* CircularProgressView.swift in Sources */, E133328D2953AE4B00EE76AB /* CircularProgressView.swift in Sources */,

View File

@ -34,7 +34,7 @@
"location" : "https://github.com/LePips/CollectionVGrid", "location" : "https://github.com/LePips/CollectionVGrid",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "b50b5241df5fc1d71e5a09f6a87731c67c2a79e5" "revision" : "91ba930a502761924204ae74a59ded05f3b7ef89"
} }
}, },
{ {

View File

@ -19,36 +19,31 @@ extension LetterPickerBar {
@Environment(\.isSelected) @Environment(\.isSelected)
private var isSelected private var isSelected
private let filterLetter: ItemLetter private let letter: ItemLetter
private let viewModel: FilterViewModel private let viewModel: FilterViewModel
init(filterLetter: ItemLetter, viewModel: FilterViewModel) { init(letter: ItemLetter, viewModel: FilterViewModel) {
self.filterLetter = filterLetter self.letter = letter
self.viewModel = viewModel self.viewModel = viewModel
} }
var body: some View { var body: some View {
Button { Button {
if !viewModel.currentFilters.letter.contains(filterLetter) { if !viewModel.currentFilters.letter.contains(letter) {
viewModel.currentFilters.letter = [ItemLetter(stringLiteral: filterLetter.value)] viewModel.currentFilters.letter = [ItemLetter(stringLiteral: letter.value)]
} else { } else {
viewModel.currentFilters.letter = [] viewModel.currentFilters.letter = []
} }
} label: { } label: {
Text( ZStack {
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 {
RoundedRectangle(cornerRadius: 5) RoundedRectangle(cornerRadius: 5)
.frame(width: 20, height: 20) .foregroundStyle(isSelected ? accentColor : Color.clear)
.foregroundStyle(isSelected ? accentColor.opacity(0.5) : Color.clear)
Text(letter.value)
.font(.headline)
.foregroundStyle(isSelected ? accentColor.overlayColor : accentColor)
} }
.frame(width: 20, height: 20)
} }
} }
} }

View File

@ -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())
}
}

View File

@ -10,28 +10,26 @@ import Defaults
import SwiftUI import SwiftUI
struct LetterPickerBar: View { struct LetterPickerBar: View {
@ObservedObject
private var viewModel: FilterViewModel
init(viewModel: FilterViewModel) { @ObservedObject
self.viewModel = viewModel var viewModel: FilterViewModel
}
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
Spacer() Spacer()
ForEach(ItemLetter.allCases, id: \.hashValue) { filterLetter in ForEach(ItemLetter.allCases, id: \.hashValue) { filterLetter in
LetterPickerButton( LetterPickerButton(
filterLetter: filterLetter, letter: filterLetter,
viewModel: viewModel viewModel: viewModel
) )
.environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter)) .environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter))
.frame(maxWidth: .infinity)
} }
Spacer() Spacer()
} }
.scrollOnOverflow() .scrollIfLargerThanContainer()
.frame(width: 30, alignment: .center) .frame(width: 30, alignment: .center)
} }
} }

View File

@ -180,6 +180,7 @@ struct PagingLibraryView<Element: Poster>: View {
// Note: if parent is a folders then other items will have labels, // Note: if parent is a folders then other items will have labels,
// so an empty content view is necessary // so an empty content view is necessary
@ViewBuilder
private func landscapeGridItemView(item: Element) -> some View { private func landscapeGridItemView(item: Element) -> some View {
PosterButton(item: item, type: .landscape) PosterButton(item: item, type: .landscape)
.content { .content {
@ -199,6 +200,7 @@ struct PagingLibraryView<Element: Poster>: View {
} }
} }
@ViewBuilder
private func portraitGridItemView(item: Element) -> some View { private func portraitGridItemView(item: Element) -> some View {
PosterButton(item: item, type: .portrait) PosterButton(item: item, type: .portrait)
.content { .content {
@ -218,6 +220,7 @@ struct PagingLibraryView<Element: Poster>: View {
} }
} }
@ViewBuilder
private func listItemView(item: Element, posterType: PosterDisplayType) -> some View { private func listItemView(item: Element, posterType: PosterDisplayType) -> some View {
LibraryRow(item: item, posterType: posterType) LibraryRow(item: item, posterType: posterType)
.onSelect { .onSelect {
@ -233,7 +236,8 @@ struct PagingLibraryView<Element: Poster>: View {
} }
} }
private var contentView: some View { @ViewBuilder
private var gridView: some View {
CollectionVGrid( CollectionVGrid(
$viewModel.elements, $viewModel.elements,
layout: $layout layout: $layout
@ -256,33 +260,40 @@ struct PagingLibraryView<Element: Poster>: View {
viewModel.send(.getNextPage) viewModel.send(.getNextPage)
} }
.proxy(collectionVGridProxy) .proxy(collectionVGridProxy)
.scrollIndicatorsVisible(false)
} }
@ViewBuilder @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 { if letterPickerEnabled, let filterViewModel = viewModel.filterViewModel {
switch letterPickerOrientation { ZStack(alignment: letterPickerOrientation.alignment) {
case .trailing: innerContent
HStack(spacing: 0) { .padding(letterPickerOrientation.edge, 35)
content .frame(maxWidth: .infinity)
.frame(maxWidth: .infinity)
LetterPickerBar(viewModel: filterViewModel) LetterPickerBar(viewModel: filterViewModel)
.padding(.top, safeArea.top) .padding(.top, safeArea.top)
.padding(.bottom, safeArea.bottom) .padding(.bottom, safeArea.bottom)
} .padding(letterPickerOrientation.edge, 10)
case .leading:
HStack(spacing: 0) {
LetterPickerBar(viewModel: filterViewModel)
.padding(.top, safeArea.top)
.padding(.bottom, safeArea.bottom)
content
.frame(maxWidth: .infinity)
}
} }
} else { } else {
content innerContent
} }
} }
@ -292,17 +303,13 @@ struct PagingLibraryView<Element: Poster>: View {
var body: some View { var body: some View {
ZStack { ZStack {
Color.clear
switch viewModel.state { switch viewModel.state {
case .content: case .content, .initial, .refreshing:
if viewModel.elements.isEmpty { contentView
contentLetterBarView(content: L10n.noResults.text)
} else {
contentLetterBarView(content: contentView)
}
case let .error(error): case let .error(error):
errorView(with: error) errorView(with: error)
case .initial, .refreshing:
contentLetterBarView(content: DelayedProgressView())
} }
} }
.animation(.linear(duration: 0.1), value: viewModel.state) .animation(.linear(duration: 0.1), value: viewModel.state)

View File

@ -20,8 +20,6 @@ import SwiftUI
// TODO: user ordering // TODO: user ordering
// - name // - name
// - last signed in date // - last signed in date
// TODO: for random splash screen, instead have a random sorted array
// for failure cases
struct SelectUserView: View { struct SelectUserView: View {
@ -45,8 +43,6 @@ struct SelectUserView: View {
@EnvironmentObject @EnvironmentObject
private var router: SelectUserCoordinator.Router private var router: SelectUserCoordinator.Router
@State
private var contentSafeAreaInsets: EdgeInsets = .zero
@State @State
private var contentSize: CGSize = .zero private var contentSize: CGSize = .zero
@State @State
@ -70,7 +66,7 @@ struct SelectUserView: View {
@State @State
private var selectedUsers: Set<UserState> = [] private var selectedUsers: Set<UserState> = []
@State @State
private var splashScreenImageSource: ImageSource? = nil private var splashScreenImageSources: [ImageSource] = []
@StateObject @StateObject
private var viewModel = SelectUserViewModel() private var viewModel = SelectUserViewModel()
@ -115,25 +111,27 @@ struct SelectUserView: View {
} }
// For all server selection, .all is random // For all server selection, .all is random
private func makeSplashScreenImageSource( private func makeSplashScreenImageSources(
serverSelection: SelectUserServerSelection, serverSelection: SelectUserServerSelection,
allServersSelection: SelectUserServerSelection allServersSelection: SelectUserServerSelection
) -> ImageSource? { ) -> [ImageSource] {
switch (serverSelection, allServersSelection) { switch (serverSelection, allServersSelection) {
case (.all, .all): case (.all, .all):
return viewModel return viewModel
.servers .servers
.keys .keys
.randomElement()? .shuffled()
.splashScreenImageSource() .map { $0.splashScreenImageSource() }
// need to evaluate server with id selection first // need to evaluate server with id selection first
case let (.server(id), _), let (.all, .server(id)): case let (.server(id), _), let (.all, .server(id)):
return viewModel return [
.servers viewModel
.keys .servers
.first { $0.id == id }? .keys
.splashScreenImageSource() .first { $0.id == id }?
.splashScreenImageSource() ?? .init(),
]
} }
} }
@ -252,7 +250,7 @@ struct SelectUserView: View {
} }
} }
.edgePadding() .edgePadding()
.scroll(ifLargerThan: contentSize.height - 100) .scrollIfLargerThanContainer(padding: 100)
.onChange(of: gridItemSize) { newValue in .onChange(of: gridItemSize) { newValue in
let columns = Int(contentSize.width / (newValue.width + EdgeInsets.edgePadding)) let columns = Int(contentSize.width / (newValue.width + EdgeInsets.edgePadding))
@ -274,7 +272,7 @@ struct SelectUserView: View {
} }
} }
.edgePadding() .edgePadding()
.scroll(ifLargerThan: contentSize.height - 100) .scrollIfLargerThanContainer(padding: 100)
} }
@ViewBuilder @ViewBuilder
@ -386,9 +384,8 @@ struct SelectUserView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
ZStack { ZStack {
Color.clear Color.clear
.onSizeChanged { size, safeAreaInsets in .onSizeChanged { size, _ in
contentSize = size contentSize = size
contentSafeAreaInsets = safeAreaInsets
} }
switch userListDisplayType { switch userListDisplayType {
@ -433,16 +430,16 @@ struct SelectUserView: View {
} }
} }
.background { .background {
if selectUserUseSplashscreen, let splashScreenImageSource { if selectUserUseSplashscreen, splashScreenImageSources.isNotEmpty {
ZStack { ZStack {
Color.clear Color.clear
ImageView(splashScreenImageSource) ImageView(splashScreenImageSources)
.pipeline(.Swiftfin.branding) .pipeline(.Swiftfin.branding)
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
.id(splashScreenImageSource) .id(splashScreenImageSources)
.transition(.opacity) .transition(.opacity)
.animation(.linear, value: splashScreenImageSource) .animation(.linear, value: splashScreenImageSources)
Color.black Color.black
.opacity(0.9) .opacity(0.9)
@ -515,7 +512,7 @@ struct SelectUserView: View {
.onAppear { .onAppear {
viewModel.send(.getServers) viewModel.send(.getServers)
splashScreenImageSource = makeSplashScreenImageSource( splashScreenImageSources = makeSplashScreenImageSources(
serverSelection: serverSelection, serverSelection: serverSelection,
allServersSelection: selectUserAllServersSplashscreen allServersSelection: selectUserAllServersSplashscreen
) )
@ -537,7 +534,7 @@ struct SelectUserView: View {
} }
} }
.onChange(of: selectUserAllServersSplashscreen) { newValue in .onChange(of: selectUserAllServersSplashscreen) { newValue in
splashScreenImageSource = makeSplashScreenImageSource( splashScreenImageSources = makeSplashScreenImageSources(
serverSelection: serverSelection, serverSelection: serverSelection,
allServersSelection: newValue allServersSelection: newValue
) )
@ -545,7 +542,7 @@ struct SelectUserView: View {
.onChange(of: serverSelection) { newValue in .onChange(of: serverSelection) { newValue in
gridItems = makeGridItems(for: newValue) gridItems = makeGridItems(for: newValue)
splashScreenImageSource = makeSplashScreenImageSource( splashScreenImageSources = makeSplashScreenImageSources(
serverSelection: newValue, serverSelection: newValue,
allServersSelection: selectUserAllServersSplashscreen allServersSelection: selectUserAllServersSplashscreen
) )