mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2024-11-27 00:00:37 +00:00
Generic Button to Input from an Alert (#1273)
* Creation of an Alert Input Button based on the Chevron Button. * Only one button required for both iOS and tvOS * wip --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
43811aa50d
commit
2bda693143
96
Shared/Components/ChevronAlertButton.swift
Normal file
96
Shared/Components/ChevronAlertButton.swift
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// 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: find better name
|
||||
|
||||
struct ChevronAlertButton<Content>: View where Content: View {
|
||||
|
||||
@State
|
||||
private var isSelected = false
|
||||
|
||||
private let content: () -> Content
|
||||
private let description: String?
|
||||
private let onCancel: (() -> Void)?
|
||||
private let onSave: (() -> Void)?
|
||||
private let subtitle: Text?
|
||||
private let title: String
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
ChevronButton(title, subtitle: subtitle)
|
||||
.onSelect {
|
||||
isSelected = true
|
||||
}
|
||||
.alert(title, isPresented: $isSelected) {
|
||||
|
||||
content()
|
||||
|
||||
if let onSave {
|
||||
Button(L10n.save) {
|
||||
onSave()
|
||||
isSelected = false
|
||||
}
|
||||
}
|
||||
|
||||
if let onCancel {
|
||||
Button(L10n.cancel, role: .cancel) {
|
||||
onCancel()
|
||||
isSelected = false
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
if let description = description {
|
||||
Text(description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronAlertButton {
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String?,
|
||||
description: String? = nil,
|
||||
@ViewBuilder content: @escaping () -> Content,
|
||||
onSave: (() -> Void)? = nil,
|
||||
onCancel: (() -> Void)? = nil
|
||||
) {
|
||||
self.init(
|
||||
content: content,
|
||||
description: description,
|
||||
onCancel: onCancel,
|
||||
onSave: onSave,
|
||||
subtitle: subtitle != nil ? Text(subtitle!) : nil,
|
||||
title: title
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Initializer: Text Inputs with Save/Cancel Actions
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: Text?,
|
||||
description: String? = nil,
|
||||
@ViewBuilder content: @escaping () -> Content,
|
||||
onSave: (() -> Void)? = nil,
|
||||
onCancel: (() -> Void)? = nil
|
||||
) {
|
||||
self.init(
|
||||
content: content,
|
||||
description: description,
|
||||
onCancel: onCancel,
|
||||
onSave: onSave,
|
||||
subtitle: subtitle,
|
||||
title: title
|
||||
)
|
||||
}
|
||||
}
|
161
Shared/Components/ChevronButton.swift
Normal file
161
Shared/Components/ChevronButton.swift
Normal file
@ -0,0 +1,161 @@
|
||||
//
|
||||
// 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 ChevronButton<Icon: View>: View {
|
||||
|
||||
private let icon: Icon
|
||||
private let isExternal: Bool
|
||||
private let title: Text
|
||||
private let subtitle: Text?
|
||||
private var onSelect: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onSelect) {
|
||||
HStack {
|
||||
|
||||
icon
|
||||
.font(.body.weight(.bold))
|
||||
|
||||
title
|
||||
|
||||
Spacer()
|
||||
|
||||
if let subtitle {
|
||||
subtitle
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Image(systemName: isExternal ? "arrow.up.forward" : "chevron.right")
|
||||
.font(.body.weight(.regular))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.foregroundStyle(.primary, .secondary)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronButton where Icon == EmptyView {
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String? = nil,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: EmptyView(),
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: {
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}(),
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: Text?,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: EmptyView(),
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: subtitle,
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronButton where Icon == Image {
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String? = nil,
|
||||
systemName: String,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: Image(systemName: systemName),
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: {
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}(),
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: Text?,
|
||||
systemName: String,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: Image(systemName: systemName),
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: subtitle,
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String? = nil,
|
||||
image: Image,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: image,
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: {
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}(),
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: Text?,
|
||||
image: Image,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
icon: image,
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: subtitle,
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronButton {
|
||||
|
||||
func onSelect(perform action: @escaping () -> Void) -> Self {
|
||||
copy(modifying: \.onSelect, with: action)
|
||||
}
|
||||
}
|
@ -77,3 +77,31 @@ extension ParseableFormatStyle where Self == DayIntervalParseableFormatStyle {
|
||||
.init(range: range)
|
||||
}
|
||||
}
|
||||
|
||||
extension FormatStyle where Self == TimeIntervalFormatStyle {
|
||||
|
||||
static func interval(
|
||||
style: Date.ComponentsFormatStyle.Style,
|
||||
fields: Set<Date.ComponentsFormatStyle.Field>
|
||||
) -> TimeIntervalFormatStyle {
|
||||
TimeIntervalFormatStyle(style: style, fields: fields)
|
||||
}
|
||||
}
|
||||
|
||||
struct TimeIntervalFormatStyle: FormatStyle {
|
||||
|
||||
let style: Date.ComponentsFormatStyle.Style
|
||||
let fields: Set<Date.ComponentsFormatStyle.Field>
|
||||
|
||||
func format(_ value: TimeInterval) -> String {
|
||||
let value = abs(value)
|
||||
let t = Date.now
|
||||
|
||||
return Date.ComponentsFormatStyle(
|
||||
style: style,
|
||||
locale: .current,
|
||||
calendar: .current,
|
||||
fields: fields
|
||||
).format(t ..< t.addingTimeInterval(value))
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +230,8 @@ internal enum L10n {
|
||||
internal static let dashboard = L10n.tr("Localizable", "dashboard", fallback: "Dashboard")
|
||||
/// Perform administrative tasks for your Jellyfin server.
|
||||
internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.")
|
||||
/// Days
|
||||
internal static let days = L10n.tr("Localizable", "days", fallback: "Days")
|
||||
/// Default Scheme
|
||||
internal static let defaultScheme = L10n.tr("Localizable", "defaultScheme", fallback: "Default Scheme")
|
||||
/// Delete
|
||||
@ -434,8 +436,8 @@ internal enum L10n {
|
||||
internal static let nextUp = L10n.tr("Localizable", "nextUp", fallback: "Next Up")
|
||||
/// Days in Next Up
|
||||
internal static let nextUpDays = L10n.tr("Localizable", "nextUpDays", fallback: "Days in Next Up")
|
||||
/// Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.
|
||||
internal static let nextUpDaysDescription = L10n.tr("Localizable", "nextUpDaysDescription", fallback: "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.")
|
||||
/// Set the maximum amount of days a show should stay in the 'Next Up' list without watching it. Set the value to 0 to disable.
|
||||
internal static let nextUpDaysDescription = L10n.tr("Localizable", "nextUpDaysDescription", fallback: "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it. Set the value to 0 to disable.")
|
||||
/// Rewatching in Next Up
|
||||
internal static let nextUpRewatch = L10n.tr("Localizable", "nextUpRewatch", fallback: "Rewatching in Next Up")
|
||||
/// No Cast devices found..
|
||||
@ -638,6 +640,8 @@ internal enum L10n {
|
||||
internal static let running = L10n.tr("Localizable", "running", fallback: "Running...")
|
||||
/// Runtime
|
||||
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
|
||||
/// Save
|
||||
internal static let save = L10n.tr("Localizable", "save", fallback: "Save")
|
||||
/// Scan All Libraries
|
||||
internal static let scanAllLibraries = L10n.tr("Localizable", "scanAllLibraries", fallback: "Scan All Libraries")
|
||||
/// Scheduled Tasks
|
||||
|
@ -1,85 +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 ChevronButton: View {
|
||||
|
||||
private let isExternal: Bool
|
||||
private let title: Text
|
||||
private let subtitle: Text?
|
||||
private var leadingView: () -> any View
|
||||
private var onSelect: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
onSelect()
|
||||
} label: {
|
||||
HStack {
|
||||
|
||||
leadingView()
|
||||
.eraseToAnyView()
|
||||
|
||||
title
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if let subtitle {
|
||||
subtitle
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Image(systemName: isExternal ? "arrow.up.forward" : "chevron.right")
|
||||
.font(.body.weight(.regular))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronButton {
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String? = nil,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: {
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}(),
|
||||
leadingView: { EmptyView() },
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(_ title: String, external: Bool = false, subtitle: @autoclosure () -> Text) {
|
||||
self.init(
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: subtitle(),
|
||||
leadingView: { EmptyView() },
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
func leadingView(@ViewBuilder _ content: @escaping () -> any View) -> Self {
|
||||
copy(modifying: \.leadingView, with: content)
|
||||
}
|
||||
|
||||
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||
copy(modifying: \.onSelect, with: action)
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
extension CustomizeViewsSettings {
|
||||
|
||||
struct HomeSection: View {
|
||||
|
||||
@Default(.Customization.Home.showRecentlyAdded)
|
||||
@ -29,34 +30,23 @@ extension CustomizeViewsSettings {
|
||||
|
||||
Toggle(L10n.nextUpRewatch, isOn: $resumeNextUp)
|
||||
|
||||
ChevronButton(
|
||||
ChevronAlertButton(
|
||||
L10n.nextUpDays,
|
||||
subtitle: {
|
||||
if maxNextUp > 0 {
|
||||
return Text(
|
||||
Date.now.addingTimeInterval(-maxNextUp) ..< Date.now,
|
||||
format: .components(style: .narrow, fields: [.year, .month, .week, .day])
|
||||
)
|
||||
return Text(maxNextUp, format: .interval(style: .narrow, fields: [.day]))
|
||||
} else {
|
||||
return Text(L10n.disabled)
|
||||
}
|
||||
}()
|
||||
)
|
||||
.onSelect {
|
||||
isPresentingNextUpDays = true
|
||||
}
|
||||
.alert(L10n.nextUpDays, isPresented: $isPresentingNextUpDays) {
|
||||
|
||||
// TODO: Validate whether this says Done or a Number
|
||||
}(),
|
||||
description: L10n.nextUpDaysDescription
|
||||
) {
|
||||
TextField(
|
||||
L10n.nextUpDays,
|
||||
L10n.days,
|
||||
value: $maxNextUp,
|
||||
format: .dayInterval(range: 0 ... 1000)
|
||||
)
|
||||
.keyboardType(.numberPad)
|
||||
|
||||
} message: {
|
||||
L10n.nextUpDaysDescription.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
4E2AC4D42C6C4C1200DD600D /* OrderedSectionSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */; };
|
||||
4E2AC4D62C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D52C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift */; };
|
||||
4E2AC4D92C6C4D9400DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D72C6C4D8D00DD600D /* PlaybackQualitySettingsView.swift */; };
|
||||
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
|
||||
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
|
||||
4E63B9FA2C8A5BEF00C25378 /* UserDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */; };
|
||||
4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */; };
|
||||
@ -56,6 +57,7 @@
|
||||
4EB1A8CA2C9A766200F43898 /* ActiveSessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8C92C9A765800F43898 /* ActiveSessionsView.swift */; };
|
||||
4EB1A8CC2C9B1BA200F43898 /* ServerTaskButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CB2C9B1B9700F43898 /* ServerTaskButton.swift */; };
|
||||
4EB1A8CE2C9B2D0800F43898 /* ActiveSessionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */; };
|
||||
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
|
||||
4EBE06462C7E9509004A6C03 /* PlaybackCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE06452C7E9509004A6C03 /* PlaybackCompatibility.swift */; };
|
||||
4EBE06472C7E9509004A6C03 /* PlaybackCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE06452C7E9509004A6C03 /* PlaybackCompatibility.swift */; };
|
||||
4EBE064D2C7EB6D3004A6C03 /* VideoPlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE064C2C7EB6D3004A6C03 /* VideoPlayerType.swift */; };
|
||||
@ -389,7 +391,6 @@
|
||||
E12CC1CB28D1333400678D5D /* CinematicResumeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1CA28D1333400678D5D /* CinematicResumeItemView.swift */; };
|
||||
E12CC1CD28D135C700678D5D /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1CC28D135C700678D5D /* NextUpView.swift */; };
|
||||
E12E30F1296383810022FAC9 /* SplitFormWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E30F0296383810022FAC9 /* SplitFormWindowView.swift */; };
|
||||
E12E30F329638B140022FAC9 /* ChevronButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E30F229638B140022FAC9 /* ChevronButton.swift */; };
|
||||
E12E30F5296392EC0022FAC9 /* EnumPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */; };
|
||||
E12F038C28F8B0B100976CC3 /* EdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12F038B28F8B0B100976CC3 /* EdgeInsets.swift */; };
|
||||
E132D3C82BD200C10058A2DF /* CollectionVGrid in Frameworks */ = {isa = PBXBuildFile; productRef = E132D3C72BD200C10058A2DF /* CollectionVGrid */; };
|
||||
@ -641,6 +642,7 @@
|
||||
E17FB55B28C1266400311DFE /* GenresHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55A28C1266400311DFE /* GenresHStack.swift */; };
|
||||
E1803EA12BFBD6CF0039F90E /* Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1803EA02BFBD6CF0039F90E /* Hashable.swift */; };
|
||||
E1803EA22BFBD6CF0039F90E /* Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1803EA02BFBD6CF0039F90E /* Hashable.swift */; };
|
||||
E18121062CBE428000682985 /* ChevronButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1528728FD229500600579 /* ChevronButton.swift */; };
|
||||
E18295E429CAC6F100F91ED0 /* BasicNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154967D296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift */; };
|
||||
E18443CB2A037773002DDDC8 /* UDPBroadcast in Frameworks */ = {isa = PBXBuildFile; productRef = E18443CA2A037773002DDDC8 /* UDPBroadcast */; };
|
||||
E185920628CDAA6400326F80 /* CastAndCrewHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E185920528CDAA6400326F80 /* CastAndCrewHStack.swift */; };
|
||||
@ -1053,6 +1055,7 @@
|
||||
4EB1A8C92C9A765800F43898 /* ActiveSessionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsView.swift; sourceTree = "<group>"; };
|
||||
4EB1A8CB2C9B1B9700F43898 /* ServerTaskButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskButton.swift; sourceTree = "<group>"; };
|
||||
4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionRow.swift; sourceTree = "<group>"; };
|
||||
4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronAlertButton.swift; sourceTree = "<group>"; };
|
||||
4EBE06452C7E9509004A6C03 /* PlaybackCompatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackCompatibility.swift; sourceTree = "<group>"; };
|
||||
4EBE064C2C7EB6D3004A6C03 /* VideoPlayerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerType.swift; sourceTree = "<group>"; };
|
||||
4EBE06502C7ED0E1004A6C03 /* DeviceProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceProfile.swift; sourceTree = "<group>"; };
|
||||
@ -1307,7 +1310,6 @@
|
||||
E12CC1CA28D1333400678D5D /* CinematicResumeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicResumeItemView.swift; sourceTree = "<group>"; };
|
||||
E12CC1CC28D135C700678D5D /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
||||
E12E30F0296383810022FAC9 /* SplitFormWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitFormWindowView.swift; sourceTree = "<group>"; };
|
||||
E12E30F229638B140022FAC9 /* ChevronButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronButton.swift; sourceTree = "<group>"; };
|
||||
E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumPickerView.swift; sourceTree = "<group>"; };
|
||||
E12F038B28F8B0B100976CC3 /* EdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeInsets.swift; sourceTree = "<group>"; };
|
||||
E13316FD2ADE42B6009BF865 /* OnSizeChangedModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnSizeChangedModifier.swift; sourceTree = "<group>"; };
|
||||
@ -2195,7 +2197,6 @@
|
||||
536D3D77267BB9650004248C /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E12E30F229638B140022FAC9 /* ChevronButton.swift */,
|
||||
E1DC9818296DD1CD00982F06 /* CinematicBackgroundView.swift */,
|
||||
E1A42E4928CA6CCD00A14DCB /* CinematicItemSelector.swift */,
|
||||
E1C92618288756BD002A7A66 /* DotHStack.swift */,
|
||||
@ -2430,7 +2431,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E1D8429429346C6400D1041A /* BasicStepper.swift */,
|
||||
E1A1528728FD229500600579 /* ChevronButton.swift */,
|
||||
E133328C2953AE4B00EE76AB /* CircularProgressView.swift */,
|
||||
E1A3E4CE2BB7E02B005C59F8 /* DelayedProgressView.swift */,
|
||||
E18E01A7288746AF0022598C /* DotHStack.swift */,
|
||||
@ -3594,6 +3594,8 @@
|
||||
E104DC952B9E7E29008F506D /* AssertionFailureView.swift */,
|
||||
E18E0203288749200022598C /* BlurView.swift */,
|
||||
E145EB212BDCCA43003BF6F3 /* BulletedList.swift */,
|
||||
4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */,
|
||||
E1A1528728FD229500600579 /* ChevronButton.swift */,
|
||||
E1153DCB2BBB633B00424D36 /* FastSVGView.swift */,
|
||||
531AC8BE26750DE20091C7EB /* ImageView.swift */,
|
||||
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */,
|
||||
@ -4338,6 +4340,7 @@
|
||||
E1575E74293E77B5001665B1 /* PanDirectionGestureRecognizer.swift in Sources */,
|
||||
E1575E85293E7A00001665B1 /* DarkAppIcon.swift in Sources */,
|
||||
E1763A762BF3FF01004DF6AB /* AppLoadingView.swift in Sources */,
|
||||
E18121062CBE428000682985 /* ChevronButton.swift in Sources */,
|
||||
E102315A2BCF8AF8009D71FC /* ProgramButtonContent.swift in Sources */,
|
||||
E17639F82BF2E25B004DF6AB /* Keychain.swift in Sources */,
|
||||
C45C36552A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift in Sources */,
|
||||
@ -4381,7 +4384,6 @@
|
||||
4E9A24E92C82B79D0023DA83 /* EditCustomDeviceProfileCoordinator.swift in Sources */,
|
||||
E1C9261C288756BD002A7A66 /* PosterHStack.swift in Sources */,
|
||||
E1722DB229491C3900CC0239 /* ImageBlurHashes.swift in Sources */,
|
||||
E12E30F329638B140022FAC9 /* ChevronButton.swift in Sources */,
|
||||
4E9A24ED2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift in Sources */,
|
||||
E12CC1BC28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */,
|
||||
E1575E9E293E7B1E001665B1 /* Equatable.swift in Sources */,
|
||||
@ -4468,6 +4470,7 @@
|
||||
E1763A722BF3F67C004DF6AB /* SwiftfinStore+Mappings.swift in Sources */,
|
||||
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
||||
E18A17F0298C68B700C22F62 /* Overlay.swift in Sources */,
|
||||
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */,
|
||||
E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */,
|
||||
4E2AC4CF2C6C4A0600DD600D /* PlaybackQualitySettingsCoordinator.swift in Sources */,
|
||||
E1D37F492B9C648E00343D2B /* MaxHeightText.swift in Sources */,
|
||||
@ -5044,6 +5047,7 @@
|
||||
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||
E1ED7FD62CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift in Sources */,
|
||||
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
||||
E1545BD82BDC55C300D9578F /* ResetUserPasswordView.swift in Sources */,
|
||||
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
||||
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
||||
|
@ -1,85 +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 ChevronButton: View {
|
||||
|
||||
private let isExternal: Bool
|
||||
private let title: Text
|
||||
private let subtitle: Text?
|
||||
private var leadingView: () -> any View
|
||||
private var onSelect: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
onSelect()
|
||||
} label: {
|
||||
HStack {
|
||||
|
||||
leadingView()
|
||||
.eraseToAnyView()
|
||||
|
||||
title
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
if let subtitle {
|
||||
subtitle
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Image(systemName: isExternal ? "arrow.up.forward" : "chevron.right")
|
||||
.font(.body.weight(.regular))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChevronButton {
|
||||
|
||||
init(
|
||||
_ title: String,
|
||||
subtitle: String? = nil,
|
||||
external: Bool = false
|
||||
) {
|
||||
self.init(
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: {
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}(),
|
||||
leadingView: { EmptyView() },
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
init(_ title: String, external: Bool = false, subtitle: @autoclosure () -> Text) {
|
||||
self.init(
|
||||
isExternal: external,
|
||||
title: Text(title),
|
||||
subtitle: subtitle(),
|
||||
leadingView: { EmptyView() },
|
||||
onSelect: {}
|
||||
)
|
||||
}
|
||||
|
||||
func leadingView(@ViewBuilder _ content: @escaping () -> any View) -> Self {
|
||||
copy(modifying: \.leadingView, with: content)
|
||||
}
|
||||
|
||||
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||
copy(modifying: \.onSelect, with: action)
|
||||
}
|
||||
}
|
@ -38,44 +38,34 @@ struct AboutAppView: View {
|
||||
trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))"
|
||||
)
|
||||
|
||||
ChevronButton(L10n.sourceCode, external: true)
|
||||
.leadingView {
|
||||
Image(.logoGithub)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.onSelect {
|
||||
UIApplication.shared.open(.swiftfinGithub)
|
||||
}
|
||||
ChevronButton(
|
||||
L10n.sourceCode,
|
||||
image: Image(.logoGithub),
|
||||
external: true
|
||||
)
|
||||
.onSelect {
|
||||
UIApplication.shared.open(.swiftfinGithub)
|
||||
}
|
||||
|
||||
ChevronButton(L10n.bugsAndFeatures, external: true)
|
||||
.leadingView {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.resizable()
|
||||
.backport
|
||||
.fontWeight(.bold)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.onSelect {
|
||||
UIApplication.shared.open(.swiftfinGithubIssues)
|
||||
}
|
||||
ChevronButton(
|
||||
L10n.bugsAndFeatures,
|
||||
systemName: "plus.circle.fill",
|
||||
external: true
|
||||
)
|
||||
.onSelect {
|
||||
UIApplication.shared.open(.swiftfinGithubIssues)
|
||||
}
|
||||
.symbolRenderingMode(.monochrome)
|
||||
|
||||
ChevronButton(L10n.settings, external: true)
|
||||
.leadingView {
|
||||
Image(systemName: "gearshape.fill")
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.onSelect {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
ChevronButton(
|
||||
L10n.settings,
|
||||
systemName: "gearshape.fill",
|
||||
external: true
|
||||
)
|
||||
.onSelect {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
extension CustomizeViewsSettings {
|
||||
|
||||
struct HomeSection: View {
|
||||
|
||||
@Default(.Customization.Home.showRecentlyAdded)
|
||||
@ -19,9 +20,6 @@ extension CustomizeViewsSettings {
|
||||
@Default(.Customization.Home.resumeNextUp)
|
||||
private var resumeNextUp
|
||||
|
||||
@State
|
||||
private var isPresentingNextUpDays = false
|
||||
|
||||
var body: some View {
|
||||
Section(L10n.home) {
|
||||
|
||||
@ -29,33 +27,23 @@ extension CustomizeViewsSettings {
|
||||
|
||||
Toggle(L10n.nextUpRewatch, isOn: $resumeNextUp)
|
||||
|
||||
ChevronButton(
|
||||
ChevronAlertButton(
|
||||
L10n.nextUpDays,
|
||||
subtitle: {
|
||||
if maxNextUp > 0 {
|
||||
return Text(
|
||||
Date.now.addingTimeInterval(-maxNextUp) ..< Date.now,
|
||||
format: .components(style: .narrow, fields: [.year, .month, .week, .day])
|
||||
)
|
||||
return Text(maxNextUp, format: .interval(style: .narrow, fields: [.day]))
|
||||
} else {
|
||||
return Text(L10n.disabled)
|
||||
}
|
||||
}()
|
||||
)
|
||||
.onSelect {
|
||||
isPresentingNextUpDays = true
|
||||
}
|
||||
.alert(L10n.nextUpDays, isPresented: $isPresentingNextUpDays) {
|
||||
|
||||
}(),
|
||||
description: L10n.nextUpDaysDescription
|
||||
) {
|
||||
TextField(
|
||||
L10n.nextUpDays,
|
||||
L10n.days,
|
||||
value: $maxNextUp,
|
||||
format: .dayInterval(range: 0 ... 1000)
|
||||
)
|
||||
.keyboardType(.numberPad)
|
||||
|
||||
} message: {
|
||||
L10n.nextUpDaysDescription.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user