[tvOS] Update ConnectToServerView & UserSignInView (#1365)

* UserSignInView and ConnectToServerView Cleanup

* Public User icon changes, move the Jellyfin 'NavigationBar' to a `View Modifier` for easier re-use.

* A better solution

* isLoading == isLoading NOT isLoading == true

* clean up

---------

Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
Joe Kribs 2024-12-19 14:30:01 -07:00 committed by GitHub
parent 7685048258
commit 97affd198e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 445 additions and 234 deletions

View File

@ -46,8 +46,8 @@ internal enum L10n {
internal static let additionalSecurityAccessDescription = L10n.tr("Localizable", "additionalSecurityAccessDescription", fallback: "Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.") internal static let additionalSecurityAccessDescription = L10n.tr("Localizable", "additionalSecurityAccessDescription", fallback: "Additional security access for users signed in to this device. This does not change any Jellyfin server user settings.")
/// Add Server /// Add Server
internal static let addServer = L10n.tr("Localizable", "addServer", fallback: "Add Server") internal static let addServer = L10n.tr("Localizable", "addServer", fallback: "Add Server")
/// Add Trigger /// Add trigger
internal static let addTrigger = L10n.tr("Localizable", "addTrigger", fallback: "Add Trigger") internal static let addTrigger = L10n.tr("Localizable", "addTrigger", fallback: "Add trigger")
/// Add URL /// Add URL
internal static let addURL = L10n.tr("Localizable", "addURL", fallback: "Add URL") internal static let addURL = L10n.tr("Localizable", "addURL", fallback: "Add URL")
/// Add User /// Add User

View File

@ -0,0 +1,73 @@
//
// 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 SplitLoginWindowView<Leading: View, Trailing: View>: View {
// MARK: - Loading State
private let isLoading: Bool
// MARK: - Content Variables
private let leadingTitle: String
private let leadingContentView: () -> Leading
private let trailingTitle: String
private let trailingContentView: () -> Trailing
// MARK: - Body
var body: some View {
HStack(alignment: .top) {
VStack(alignment: .leading) {
Section(leadingTitle) {
VStack(alignment: .leading) {
leadingContentView()
.eraseToAnyView()
}
.frame(maxWidth: .infinity)
.padding(.vertical)
}
}
Divider()
.padding(.vertical, 100)
VStack(alignment: .leading) {
Section(trailingTitle) {
VStack(alignment: .leading) {
trailingContentView()
.eraseToAnyView()
}
.frame(maxWidth: .infinity)
.padding(.vertical)
}
}
}
.navigationBarBranding(isLoading: isLoading)
}
}
extension SplitLoginWindowView {
init(
isLoading: Bool = false,
leadingTitle: String,
trailingTitle: String,
@ViewBuilder leadingContentView: @escaping () -> Leading,
@ViewBuilder trailingContentView: @escaping () -> Trailing
) {
self.isLoading = isLoading
self.leadingTitle = leadingTitle
self.trailingTitle = trailingTitle
self.leadingContentView = leadingContentView
self.trailingContentView = trailingContentView
}
}

View File

@ -0,0 +1,34 @@
//
// 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 Defaults
import SwiftUI
struct NavigationBarBrandingModifier: ViewModifier {
let isLoading: Bool
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItem(placement: .principal) {
Image(.jellyfinBlobBlue)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
.padding(.bottom, 25)
}
if isLoading {
ToolbarItem(placement: .topBarTrailing) {
ProgressView()
}
}
}
}
}

View File

@ -0,0 +1,25 @@
//
// 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 Defaults
import SwiftUI
import SwiftUIIntrospect
extension View {
@ViewBuilder
func navigationBarBranding(
isLoading: Bool = false
) -> some View {
modifier(
NavigationBarBrandingModifier(
isLoading: isLoading
)
)
}
}

View File

@ -0,0 +1,62 @@
//
// 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 Combine
import Defaults
import SwiftUI
extension ConnectToServerView {
struct LocalServerButton: View {
// MARK: - Environment Variables
@Environment(\.isEnabled)
private var isEnabled: Bool
// MARK: - Local Server Variables
private let server: ServerState
private let action: () -> Void
// MARK: - Initializer
init(server: ServerState, action: @escaping () -> Void) {
self.server = server
self.action = action
}
// MARK: - Local Server Button
var body: some View {
Button(action: action) {
HStack {
VStack(alignment: .leading) {
Text(server.name)
.font(.headline)
.fontWeight(.semibold)
Text(server.currentURL.absoluteString)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.font(.body.weight(.regular))
.foregroundStyle(.secondary)
}
.padding()
}
.disabled(!isEnabled)
.buttonStyle(.card)
.foregroundStyle(.primary)
}
}
}

View File

@ -55,82 +55,56 @@ struct ConnectToServerView: View {
@ViewBuilder @ViewBuilder
private var connectSection: some View { private var connectSection: some View {
Section(L10n.connectToServer) { TextField(L10n.serverURL, text: $url)
TextField(L10n.serverURL, text: $url) .disableAutocorrection(true)
.disableAutocorrection(true) .textInputAutocapitalization(.never)
.textInputAutocapitalization(.never) .keyboardType(.URL)
.keyboardType(.URL) .focused($isURLFocused)
.focused($isURLFocused)
}
if viewModel.state == .connecting { if viewModel.state == .connecting {
// ListRowButton(L10n.cancel) { ListRowButton(L10n.cancel) {
// viewModel.send(.cancel)
// }
Button(L10n.cancel) {
viewModel.send(.cancel) viewModel.send(.cancel)
} }
.foregroundStyle(.red, .red.opacity(0.2)) .foregroundStyle(.red, accentColor)
.padding(.vertical)
} else { } else {
// ListRowButton(L10n.connect) { ListRowButton(L10n.connect) {
// isURLFocused = false
// viewModel.send(.connect(url))
// }
Button(L10n.connect) {
isURLFocused = false isURLFocused = false
viewModel.send(.connect(url)) viewModel.send(.connect(url))
} }
.disabled(url.isEmpty) .disabled(url.isEmpty)
.foregroundStyle( .foregroundStyle(
accentColor.overlayColor, accentColor.overlayColor,
accentColor url.isEmpty ? Color.white.opacity(0.5) : accentColor
) )
.opacity(url.isEmpty ? 0.5 : 1) .opacity(url.isEmpty ? 0.5 : 1)
.padding(.vertical)
} }
} }
// MARK: - Local Server Button
private func localServerButton(for server: ServerState) -> some View {
Button {
url = server.currentURL.absoluteString
viewModel.send(.connect(server.currentURL.absoluteString))
} label: {
HStack {
VStack(alignment: .leading) {
Text(server.name)
.font(.headline)
.fontWeight(.semibold)
Text(server.currentURL.absoluteString)
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.font(.body.weight(.regular))
.foregroundColor(.secondary)
}
}
.disabled(viewModel.state == .connecting)
.buttonStyle(.plain)
}
// MARK: - Local Servers Section // MARK: - Local Servers Section
@ViewBuilder @ViewBuilder
private var localServersSection: some View { private var localServersSection: some View {
Section(L10n.localServers) { if viewModel.localServers.isEmpty {
if viewModel.localServers.isEmpty { L10n.noLocalServersFound.text
L10n.noLocalServersFound.text .font(.callout)
.font(.callout) .foregroundColor(.secondary)
.foregroundColor(.secondary) .frame(maxWidth: .infinity)
.frame(maxWidth: .infinity) } else {
} else { LazyVGrid(
ForEach(viewModel.localServers) { server in columns: Array(repeating: GridItem(.flexible()), count: 1),
localServerButton(for: server) spacing: 30
) {
ForEach(viewModel.localServers, id: \.id) { server in
LocalServerButton(server: server) {
url = server.currentURL.absoluteString
viewModel.send(.connect(server.currentURL.absoluteString))
}
.environment(
\.isEnabled,
viewModel.state != .connecting && server.currentURL.absoluteString != url
)
} }
} }
} }
@ -139,34 +113,14 @@ struct ConnectToServerView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
VStack { SplitLoginWindowView(
HStack { isLoading: viewModel.state == .connecting,
Spacer() leadingTitle: L10n.connectToServer,
trailingTitle: L10n.localServers
if viewModel.state == .connecting { ) {
ProgressView() connectSection
} } trailingContentView: {
} localServersSection
.frame(height: 100)
.overlay {
Image(.jellyfinBlobBlue)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
.edgePadding()
}
HStack(alignment: .top) {
VStack(alignment: .leading) {
connectSection
}
VStack(alignment: .leading) {
localServersSection
}
}
Spacer()
} }
.onFirstAppear { .onFirstAppear {
isURLFocused = true isURLFocused = true

View File

@ -289,6 +289,7 @@ struct SelectUserView: View {
} }
} }
.ignoresSafeArea() .ignoresSafeArea()
.navigationBarBranding()
.onAppear { .onAppear {
viewModel.send(.getServers) viewModel.send(.getServers)

View File

@ -0,0 +1,97 @@
//
// 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 JellyfinAPI
import SwiftUI
extension UserSignInView {
struct PublicUserButton: View {
// MARK: - Environment Variables
@Environment(\.isEnabled)
private var isEnabled: Bool
// MARK: - Public User Variables
private let user: UserDto
private let client: JellyfinClient
private let action: () -> Void
// MARK: - Initializer
init(
user: UserDto,
client: JellyfinClient,
action: @escaping () -> Void
) {
self.user = user
self.client = client
self.action = action
}
// MARK: - Fallback Person View
@ViewBuilder
private var fallbackPersonView: some View {
ZStack {
Color.secondarySystemFill
RelativeSystemImageView(systemName: "person.fill", ratio: 0.5)
.foregroundStyle(.secondary)
}
.clipShape(.circle)
.aspectRatio(1, contentMode: .fill)
}
// MARK: - Person View
@ViewBuilder
private var personView: some View {
ZStack {
Color.clear
ImageView(user.profileImageSource(client: client, maxWidth: 120))
.image { image in
image
.posterBorder(ratio: 0.5, of: \.width)
}
.placeholder { _ in
fallbackPersonView
}
.failure {
fallbackPersonView
}
}
}
// MARK: - Body
var body: some View {
Button(action: action) {
personView
.aspectRatio(1, contentMode: .fill)
.posterShadow()
.clipShape(.circle)
.frame(width: 150, height: 150)
.hoverEffect(.highlight)
Text(user.name ?? .emptyDash)
.fontWeight(.semibold)
.foregroundStyle(.primary)
.lineLimit(1)
.padding(.bottom)
}
.buttonBorderShape(.circle)
.buttonStyle(.borderless)
.disabled(!isEnabled)
.foregroundStyle(.primary)
}
}
}

View File

@ -1,81 +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 JellyfinAPI
import SwiftUI
// TODO: change from list to grid button
extension UserSignInView {
struct PublicUserRow: View {
private let user: UserDto
private let client: JellyfinClient
private let action: () -> Void
init(
user: UserDto,
client: JellyfinClient,
action: @escaping () -> Void
) {
self.user = user
self.client = client
self.action = action
}
@ViewBuilder
private var personView: some View {
ZStack {
Color.secondarySystemFill
RelativeSystemImageView(systemName: "person.fill", ratio: 0.5)
.foregroundStyle(.secondary)
}
.clipShape(.circle)
.aspectRatio(1, contentMode: .fill)
}
var body: some View {
Button {
action()
} label: {
HStack {
ZStack {
Color.clear
ImageView(user.profileImageSource(client: client, maxWidth: 120))
.image { image in
image
.posterBorder(ratio: 0.5, of: \.width)
}
.placeholder { _ in
personView
}
.failure {
personView
}
}
.aspectRatio(1, contentMode: .fill)
.posterShadow()
.clipShape(.circle)
.frame(width: 50, height: 50)
Text(user.name ?? .emptyDash)
.fontWeight(.semibold)
.foregroundStyle(.primary)
.lineLimit(1)
Spacer()
}
}
.buttonStyle(.card)
.foregroundStyle(.primary)
}
}
}

View File

@ -13,8 +13,6 @@ import JellyfinAPI
import Stinsen import Stinsen
import SwiftUI import SwiftUI
// TODO: change public users from list to grid
struct UserSignInView: View { struct UserSignInView: View {
// MARK: - Defaults // MARK: - Defaults
@ -30,13 +28,16 @@ struct UserSignInView: View {
} }
@FocusState @FocusState
private var focusedTextField: FocusField? private var focusedField: FocusField?
// MARK: - State & Environment Objects // MARK: - State & Environment Objects
@EnvironmentObject @EnvironmentObject
private var router: UserSignInCoordinator.Router private var router: UserSignInCoordinator.Router
@StateObject
private var focusGuide: FocusGuide = .init()
@StateObject @StateObject
private var viewModel: UserSignInViewModel private var viewModel: UserSignInViewModel
@ -69,39 +70,37 @@ struct UserSignInView: View {
@ViewBuilder @ViewBuilder
private var signInSection: some View { private var signInSection: some View {
Section { TextField(L10n.username, text: $username)
TextField(L10n.username, text: $username) .autocorrectionDisabled()
.autocorrectionDisabled() .textInputAutocapitalization(.never)
.textInputAutocapitalization(.never) .focused($focusedField, equals: .username)
.focused($focusedTextField, equals: .username)
SecureField(L10n.password, text: $password) SecureField(L10n.password, text: $password)
.focused($focusedTextField, equals: .password) .focused($focusedField, equals: .password)
.onSubmit { .onSubmit {
guard username.isNotEmpty else { guard username.isNotEmpty else {
return return
}
viewModel.send(.signIn(username: username, password: password, policy: .none))
} }
} header: { viewModel.send(.signIn(username: username, password: password, policy: .none))
Text(L10n.signInToServer(viewModel.server.name)) }
}
if case .signingIn = viewModel.state { if case .signingIn = viewModel.state {
Button(L10n.cancel) { ListRowButton(L10n.cancel) {
viewModel.send(.cancel) viewModel.send(.cancel)
} }
.foregroundStyle(.red, .red.opacity(0.2)) .foregroundStyle(.red, accentColor)
.padding(.vertical)
} else { } else {
Button(L10n.signIn) { ListRowButton(L10n.signIn) {
viewModel.send(.signIn(username: username, password: password, policy: .none)) viewModel.send(.signIn(username: username, password: password, policy: .none))
} }
.disabled(username.isEmpty) .disabled(username.isEmpty)
.foregroundStyle( .foregroundStyle(
accentColor.overlayColor, accentColor.overlayColor,
accentColor username.isEmpty ? Color.white.opacity(0.5) : accentColor
) )
.opacity(username.isEmpty ? 0.5 : 1) .opacity(username.isEmpty ? 0.5 : 1)
.padding(.vertical)
} }
if viewModel.isQuickConnectEnabled { if viewModel.isQuickConnectEnabled {
@ -114,14 +113,17 @@ struct UserSignInView: View {
accentColor.overlayColor, accentColor.overlayColor,
accentColor accentColor
) )
.padding(.bottom)
} }
} }
if let disclaimer = viewModel.serverDisclaimer { if let disclaimer = viewModel.serverDisclaimer {
Section(L10n.disclaimer) { Section(L10n.disclaimer) {
Text(disclaimer) Text(disclaimer)
.foregroundStyle(.secondary)
.font(.callout) .font(.callout)
} }
.padding(.top)
} }
} }
@ -129,22 +131,30 @@ struct UserSignInView: View {
@ViewBuilder @ViewBuilder
private var publicUsersSection: some View { private var publicUsersSection: some View {
Section(L10n.publicUsers) { if viewModel.publicUsers.isEmpty {
if viewModel.publicUsers.isEmpty { L10n.noPublicUsers.text
L10n.noPublicUsers.text .font(.callout)
.font(.callout) .foregroundColor(.secondary)
.foregroundColor(.secondary) .frame(maxWidth: .infinity, alignment: .center)
.frame(maxWidth: .infinity) .frame(maxHeight: .infinity, alignment: .center)
} else { } else {
LazyVGrid(
columns: Array(repeating: GridItem(.flexible()), count: 4),
spacing: 30
) {
ForEach(viewModel.publicUsers, id: \.id) { user in ForEach(viewModel.publicUsers, id: \.id) { user in
PublicUserRow( PublicUserButton(
user: user, user: user,
client: viewModel.server.client client: viewModel.server.client
) { ) {
username = user.name ?? "" username = user.name ?? ""
password = "" password = ""
focusedTextField = .password focusedField = .password
} }
.environment(
\.isEnabled,
viewModel.state != .signingIn
)
} }
} }
} }
@ -153,34 +163,14 @@ struct UserSignInView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
VStack { SplitLoginWindowView(
HStack { isLoading: viewModel.state == .signingIn,
Spacer() leadingTitle: L10n.signInToServer(viewModel.server.name),
trailingTitle: L10n.publicUsers
if viewModel.state == .signingIn { ) {
ProgressView() signInSection
} } trailingContentView: {
} publicUsersSection
.frame(height: 100)
.overlay {
Image(.jellyfinBlobBlue)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 100)
.edgePadding()
}
HStack(alignment: .top) {
VStack(alignment: .leading) {
signInSection
}
VStack(alignment: .leading) {
publicUsersSection
}
}
Spacer()
} }
.onReceive(viewModel.events) { event in .onReceive(viewModel.events) { event in
switch event { switch event {
@ -198,7 +188,7 @@ struct UserSignInView: View {
} }
} }
.onFirstAppear { .onFirstAppear {
focusedTextField = .username focusedField = .username
viewModel.send(.getPublicData) viewModel.send(.getPublicData)
} }
.alert( .alert(
@ -209,11 +199,11 @@ struct UserSignInView: View {
// TODO: uncomment when duplicate user fixed // TODO: uncomment when duplicate user fixed
// Button(L10n.signIn) { // Button(L10n.signIn) {
// signInUplicate(user: user, replace: false) // signInDuplicate(user: user, replace: false)
// } // }
// Button("Replace") { // Button("Replace") {
// signInUplicate(user: user, replace: true) // signInDuplicate(user: user, replace: true)
// } // }
Button(L10n.dismiss, role: .cancel) Button(L10n.dismiss, role: .cancel)

View File

@ -84,6 +84,8 @@
4E49DEE42CE55FB900352DCD /* SyncPlayUserAccessType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */; }; 4E49DEE42CE55FB900352DCD /* SyncPlayUserAccessType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */; };
4E49DEE62CE5616800352DCD /* UserProfileImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */; }; 4E49DEE62CE5616800352DCD /* UserProfileImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */; };
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; }; 4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
4E4DAC372D11EE5E00E13FF9 /* SplitLoginWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */; };
4E4DAC3D2D11F94400E13FF9 /* LocalServerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */; };
4E4E9C672CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; }; 4E4E9C672CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; };
4E4E9C682CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; }; 4E4E9C682CFEBF2A00A6946F /* StudioEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */; };
4E4E9C6A2CFEDCA400A6946F /* PeopleEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */; }; 4E4E9C6A2CFEDCA400A6946F /* PeopleEditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */; };
@ -158,6 +160,8 @@
4E90F76A2CC72B1F00417C31 /* DetailsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F7592CC72B1F00417C31 /* DetailsSection.swift */; }; 4E90F76A2CC72B1F00417C31 /* DetailsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F7592CC72B1F00417C31 /* DetailsSection.swift */; };
4E97D1832D064748004B89AD /* ItemSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1822D064748004B89AD /* ItemSection.swift */; }; 4E97D1832D064748004B89AD /* ItemSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1822D064748004B89AD /* ItemSection.swift */; };
4E97D1852D064B43004B89AD /* RefreshMetadataButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */; }; 4E97D1852D064B43004B89AD /* RefreshMetadataButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */; };
4E98F7D22D123AD4001E7518 /* NavigationBarMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */; };
4E98F7D32D123AD4001E7518 /* View-tvOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E98F7C92D123AD4001E7518 /* View-tvOS.swift */; };
4E9A24E62C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */; }; 4E9A24E62C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */; };
4E9A24E82C82B6190023DA83 /* CustomProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */; }; 4E9A24E82C82B6190023DA83 /* CustomProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */; };
4E9A24E92C82B79D0023DA83 /* EditCustomDeviceProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8572C80332500E2879E /* EditCustomDeviceProfileCoordinator.swift */; }; 4E9A24E92C82B79D0023DA83 /* EditCustomDeviceProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8572C80332500E2879E /* EditCustomDeviceProfileCoordinator.swift */; };
@ -759,7 +763,7 @@
E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */; }; E1763A2B2BF3046E004DF6AB /* UserGridButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */; };
E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */; }; E1763A642BF3C9AA004DF6AB /* ListRowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */; };
E1763A662BF3CA83004DF6AB /* FullScreenMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */; }; E1763A662BF3CA83004DF6AB /* FullScreenMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */; };
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A692BF3D177004DF6AB /* PublicUserRow.swift */; }; E1763A6A2BF3D177004DF6AB /* PublicUserButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A692BF3D177004DF6AB /* PublicUserButton.swift */; };
E1763A712BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; }; E1763A712BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; }; E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */; };
E1763A742BF3FA4C004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */; }; E1763A742BF3FA4C004DF6AB /* AppLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */; };
@ -1227,6 +1231,8 @@
4E49DEDD2CE55F7F00352DCD /* SquareImageCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquareImageCropView.swift; sourceTree = "<group>"; }; 4E49DEDD2CE55F7F00352DCD /* SquareImageCropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquareImageCropView.swift; sourceTree = "<group>"; };
4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPlayUserAccessType.swift; sourceTree = "<group>"; }; 4E49DEE22CE55FB500352DCD /* SyncPlayUserAccessType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPlayUserAccessType.swift; sourceTree = "<group>"; };
4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileImagePicker.swift; sourceTree = "<group>"; }; 4E49DEE52CE5616800352DCD /* UserProfileImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileImagePicker.swift; sourceTree = "<group>"; };
4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitLoginWindowView.swift; sourceTree = "<group>"; };
4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalServerButton.swift; sourceTree = "<group>"; };
4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudioEditorViewModel.swift; sourceTree = "<group>"; }; 4E4E9C662CFEBF2500A6946F /* StudioEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudioEditorViewModel.swift; sourceTree = "<group>"; };
4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleEditorViewModel.swift; sourceTree = "<group>"; }; 4E4E9C692CFEDC9D00A6946F /* PeopleEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleEditorViewModel.swift; sourceTree = "<group>"; };
4E5071D62CFCEB6F003FA2AD /* TagEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEditorViewModel.swift; sourceTree = "<group>"; }; 4E5071D62CFCEB6F003FA2AD /* TagEditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEditorViewModel.swift; sourceTree = "<group>"; };
@ -1283,6 +1289,8 @@
4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditServerTaskView.swift; sourceTree = "<group>"; }; 4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditServerTaskView.swift; sourceTree = "<group>"; };
4E97D1822D064748004B89AD /* ItemSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSection.swift; sourceTree = "<group>"; }; 4E97D1822D064748004B89AD /* ItemSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSection.swift; sourceTree = "<group>"; };
4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshMetadataButton.swift; sourceTree = "<group>"; }; 4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshMetadataButton.swift; sourceTree = "<group>"; };
4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarMenuButton.swift; sourceTree = "<group>"; };
4E98F7C92D123AD4001E7518 /* View-tvOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View-tvOS.swift"; sourceTree = "<group>"; };
4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileSettingsView.swift; sourceTree = "<group>"; }; 4E9A24E52C82B5A50023DA83 /* CustomDeviceProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileSettingsView.swift; sourceTree = "<group>"; };
4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProfileButton.swift; sourceTree = "<group>"; }; 4E9A24E72C82B6190023DA83 /* CustomProfileButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomProfileButton.swift; sourceTree = "<group>"; };
4E9A24EA2C82B9ED0023DA83 /* CustomDeviceProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileCoordinator.swift; sourceTree = "<group>"; }; 4E9A24EA2C82B9ED0023DA83 /* CustomDeviceProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileCoordinator.swift; sourceTree = "<group>"; };
@ -1689,7 +1697,7 @@
E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGridButton.swift; sourceTree = "<group>"; }; E1763A2A2BF3046E004DF6AB /* UserGridButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserGridButton.swift; sourceTree = "<group>"; };
E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowButton.swift; sourceTree = "<group>"; }; E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowButton.swift; sourceTree = "<group>"; };
E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMenu.swift; sourceTree = "<group>"; }; E1763A652BF3CA83004DF6AB /* FullScreenMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMenu.swift; sourceTree = "<group>"; };
E1763A692BF3D177004DF6AB /* PublicUserRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserRow.swift; sourceTree = "<group>"; }; E1763A692BF3D177004DF6AB /* PublicUserButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicUserButton.swift; sourceTree = "<group>"; };
E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftfinStore+Mappings.swift"; sourceTree = "<group>"; }; E1763A702BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftfinStore+Mappings.swift"; sourceTree = "<group>"; };
E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; }; E1763A732BF3FA4C004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; };
E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; }; E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLoadingView.swift; sourceTree = "<group>"; };
@ -2260,6 +2268,23 @@
path = UserProfileImagePicker; path = UserProfileImagePicker;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */ = {
isa = PBXGroup;
children = (
4E4DAC3B2D11F69000E13FF9 /* Components */,
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
);
path = ConnectToServerView;
sourceTree = "<group>";
};
4E4DAC3B2D11F69000E13FF9 /* Components */ = {
isa = PBXGroup;
children = (
4E4DAC3C2D11F94000E13FF9 /* LocalServerButton.swift */,
);
path = Components;
sourceTree = "<group>";
};
4E5071D52CFCEB03003FA2AD /* ItemEditorViewModel */ = { 4E5071D52CFCEB03003FA2AD /* ItemEditorViewModel */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2499,6 +2524,31 @@
path = EditServerTaskView; path = EditServerTaskView;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4E98F7C82D123AD4001E7518 /* Modifiers */ = {
isa = PBXGroup;
children = (
4E98F7C12D123AD4001E7518 /* NavigationBarMenuButton.swift */,
);
path = Modifiers;
sourceTree = "<group>";
};
4E98F7CA2D123AD4001E7518 /* View */ = {
isa = PBXGroup;
children = (
4E98F7C82D123AD4001E7518 /* Modifiers */,
4E98F7C92D123AD4001E7518 /* View-tvOS.swift */,
);
path = View;
sourceTree = "<group>";
};
4E98F7CB2D123AD4001E7518 /* Extensions */ = {
isa = PBXGroup;
children = (
4E98F7CA2D123AD4001E7518 /* View */,
);
path = Extensions;
sourceTree = "<group>";
};
4E9A24E32C82B4700023DA83 /* CustomDeviceProfileSettingsView */ = { 4E9A24E32C82B4700023DA83 /* CustomDeviceProfileSettingsView */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2839,6 +2889,7 @@
children = ( children = (
E12186DF2718F2030010884C /* App */, E12186DF2718F2030010884C /* App */,
536D3D77267BB9650004248C /* Components */, 536D3D77267BB9650004248C /* Components */,
4E98F7CB2D123AD4001E7518 /* Extensions */,
E185920B28CEF23F00326F80 /* Objects */, E185920B28CEF23F00326F80 /* Objects */,
E1DABAD62A26E28E008AC34A /* Resources */, E1DABAD62A26E28E008AC34A /* Resources */,
E12186E02718F23B0010884C /* Views */, E12186E02718F23B0010884C /* Views */,
@ -2941,6 +2992,7 @@
E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */, E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */,
E17885A3278105170094FBCF /* SFSymbolButton.swift */, E17885A3278105170094FBCF /* SFSymbolButton.swift */,
E12E30F0296383810022FAC9 /* SplitFormWindowView.swift */, E12E30F0296383810022FAC9 /* SplitFormWindowView.swift */,
4E4DAC362D11EE4F00E13FF9 /* SplitLoginWindowView.swift */,
E187A60429AD2E25008387E6 /* StepperView.swift */, E187A60429AD2E25008387E6 /* StepperView.swift */,
); );
path = Components; path = Components;
@ -3673,7 +3725,7 @@
E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */, E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */,
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */, E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */,
E10231522BCF8AF8009D71FC /* ChannelLibraryView */, E10231522BCF8AF8009D71FC /* ChannelLibraryView */,
53ABFDEA2679753200886593 /* ConnectToServerView.swift */, 4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */,
E154967B296CBB1A00C4EF88 /* FontPickerView.swift */, E154967B296CBB1A00C4EF88 /* FontPickerView.swift */,
E1A42E4D28CBD3B200A14DCB /* HomeView */, E1A42E4D28CBD3B200A14DCB /* HomeView */,
E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */, E12376B22A33DFAC001F5B44 /* ItemOverviewView.swift */,
@ -4001,7 +4053,7 @@
E1763A682BF3D16E004DF6AB /* Components */ = { E1763A682BF3D16E004DF6AB /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E1763A692BF3D177004DF6AB /* PublicUserRow.swift */, E1763A692BF3D177004DF6AB /* PublicUserButton.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -5043,6 +5095,8 @@
E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */, E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */,
E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */, E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */,
E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */, E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */,
4E98F7D22D123AD4001E7518 /* NavigationBarMenuButton.swift in Sources */,
4E98F7D32D123AD4001E7518 /* View-tvOS.swift in Sources */,
C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */, C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */,
C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */, C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */,
E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */, E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */,
@ -5056,6 +5110,7 @@
4EF18B2A2CB993BD00343666 /* ListRow.swift in Sources */, 4EF18B2A2CB993BD00343666 /* ListRow.swift in Sources */,
4EF659E32CDD270D00E0BE5D /* ActionMenu.swift in Sources */, 4EF659E32CDD270D00E0BE5D /* ActionMenu.swift in Sources */,
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */, 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
4E4DAC372D11EE5E00E13FF9 /* SplitLoginWindowView.swift in Sources */,
4E97D1832D064748004B89AD /* ItemSection.swift in Sources */, 4E97D1832D064748004B89AD /* ItemSection.swift in Sources */,
E145EB232BDCCA43003BF6F3 /* BulletedList.swift in Sources */, E145EB232BDCCA43003BF6F3 /* BulletedList.swift in Sources */,
E104DC972B9E7E29008F506D /* AssertionFailureView.swift in Sources */, E104DC972B9E7E29008F506D /* AssertionFailureView.swift in Sources */,
@ -5263,6 +5318,7 @@
E1B4E4372CA7795200DC49DE /* OrderedDictionary.swift in Sources */, E1B4E4372CA7795200DC49DE /* OrderedDictionary.swift in Sources */,
E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */, E1AD104E26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */, E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
4E4DAC3D2D11F94400E13FF9 /* LocalServerButton.swift in Sources */,
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */, 62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */,
BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */, BD0BA22C2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */,
E1575EA2293E7B1E001665B1 /* Color.swift in Sources */, E1575EA2293E7B1E001665B1 /* Color.swift in Sources */,
@ -5313,7 +5369,7 @@
E1575E7D293E77B5001665B1 /* PosterDisplayType.swift in Sources */, E1575E7D293E77B5001665B1 /* PosterDisplayType.swift in Sources */,
E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */, E1E5D553278419D900692DFE /* ConfirmCloseOverlay.swift in Sources */,
E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */, E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */,
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */, E1763A6A2BF3D177004DF6AB /* PublicUserButton.swift in Sources */,
E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */, E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */,
4E35CE672CBED8B600DBD886 /* ServerTicks.swift in Sources */, 4E35CE672CBED8B600DBD886 /* ServerTicks.swift in Sources */,
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */, E193D549271941CC00900D82 /* UserSignInView.swift in Sources */,