[iOS] Admin Dashboard - Add/Delete Task Triggers (#1276)
* All Working. TODO: Figure out why TimeInterval crashes Swiftfin if I select 'Cancel' * Cleanup. Kind of a typeAlias but not really? Fixed the minute crash, I was make a recursive calc. All good now. Make sure temp values default to existing value at startup * Manual Run action from Edit View * Issues resolved. * Labels / soft merge with Main * Utilize events to print a success/failure message for when there is an attempted change with a TaskTrigger. * Fix label wrong value & remove TODO for completed item. * Fix all the merge issues. * wip * wip * localize --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
a04f97e1ba
commit
c46ee13dbc
|
@ -49,15 +49,21 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
var videoPlayerSettings = makeVideoPlayerSettings
|
var videoPlayerSettings = makeVideoPlayerSettings
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var customDeviceProfileSettings = makeCustomDeviceProfileSettings
|
var customDeviceProfileSettings = makeCustomDeviceProfileSettings
|
||||||
|
@Route(.modal)
|
||||||
|
var itemOverviewView = makeItemOverviewView
|
||||||
|
|
||||||
|
@Route(.modal)
|
||||||
|
var editCustomDeviceProfile = makeEditCustomDeviceProfile
|
||||||
|
@Route(.modal)
|
||||||
|
var createCustomDeviceProfile = makeCreateCustomDeviceProfile
|
||||||
|
|
||||||
|
// TODO: Move AdminDashboard items to its own coordinator ->
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var userDashboard = makeUserDashboard
|
var userDashboard = makeUserDashboard
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var activeSessions = makeActiveSessions
|
var activeSessions = makeActiveSessions
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var activeDeviceDetails = makeActiveDeviceDetails
|
var activeDeviceDetails = makeActiveDeviceDetails
|
||||||
@Route(.modal)
|
|
||||||
var itemOverviewView = makeItemOverviewView
|
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var tasks = makeTasks
|
var tasks = makeTasks
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
@ -65,14 +71,12 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var deviceDetails = makeDeviceDetails
|
var deviceDetails = makeDeviceDetails
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var editScheduledTask = makeEditScheduledTask
|
var editServerTask = makeEditServerTask
|
||||||
|
@Route(.modal)
|
||||||
|
var addServerTaskTrigger = makeAddServerTaskTrigger
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var serverLogs = makeServerLogs
|
var serverLogs = makeServerLogs
|
||||||
|
// <- End of AdminDashboard Items
|
||||||
@Route(.modal)
|
|
||||||
var editCustomDeviceProfile = makeEditCustomDeviceProfile
|
|
||||||
@Route(.modal)
|
|
||||||
var createCustomDeviceProfile = makeCreateCustomDeviceProfile
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
@ -164,6 +168,22 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
EditServerView(server: server)
|
EditServerView(server: server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeItemOverviewView(item: BaseItemDto) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
||||||
|
NavigationViewCoordinator {
|
||||||
|
ItemOverviewView(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeItemFilterDrawerSelector(selection: Binding<[ItemFilterType]>) -> some View {
|
||||||
|
OrderedSectionSelectorView(selection: selection, sources: ItemFilterType.allCases)
|
||||||
|
.navigationTitle(L10n.filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeVideoPlayerSettings() -> VideoPlayerSettingsCoordinator {
|
||||||
|
VideoPlayerSettingsCoordinator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move AdminDashboard items to its own coordinator ->
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeUserDashboard() -> some View {
|
func makeUserDashboard() -> some View {
|
||||||
UserDashboardView()
|
UserDashboardView()
|
||||||
|
@ -179,15 +199,9 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
ActiveSessionDetailView(box: box)
|
ActiveSessionDetailView(box: box)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeItemOverviewView(item: BaseItemDto) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
|
||||||
NavigationViewCoordinator {
|
|
||||||
ItemOverviewView(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeTasks() -> some View {
|
func makeTasks() -> some View {
|
||||||
ScheduledTasksView()
|
ServerTasksView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -201,8 +215,14 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeEditScheduledTask(observer: ServerTaskObserver) -> some View {
|
func makeEditServerTask(observer: ServerTaskObserver) -> some View {
|
||||||
EditScheduledTaskView(observer: observer)
|
EditServerTaskView(observer: observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAddServerTaskTrigger(observer: ServerTaskObserver) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
|
||||||
|
NavigationViewCoordinator {
|
||||||
|
AddTaskTriggerView(observer: observer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -210,14 +230,7 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
ServerLogsView()
|
ServerLogsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeItemFilterDrawerSelector(selection: Binding<[ItemFilterType]>) -> some View {
|
// <- End of AdminDashboard Items
|
||||||
OrderedSectionSelectorView(selection: selection, sources: ItemFilterType.allCases)
|
|
||||||
.navigationTitle(L10n.filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeVideoPlayerSettings() -> VideoPlayerSettingsCoordinator {
|
|
||||||
VideoPlayerSettingsCoordinator()
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -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 Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
extension DayOfWeek {
|
||||||
|
|
||||||
|
var displayTitle: String? {
|
||||||
|
let newLineRemoved = rawValue.replacingOccurrences(of: "\n", with: "")
|
||||||
|
|
||||||
|
guard let index = DateFormatter().weekdaySymbols.firstIndex(of: newLineRemoved) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calendar.current
|
||||||
|
.weekdaySymbols[index]
|
||||||
|
.localizedCapitalized
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// TODO: remove and have sdk use strong types instead
|
||||||
|
|
||||||
|
typealias ServerTicks = Int
|
||||||
|
|
||||||
|
extension ServerTicks {
|
||||||
|
|
||||||
|
// MARK: - Conversion Constants
|
||||||
|
|
||||||
|
private static let ticksPerSecond = 10_000_000
|
||||||
|
private static let ticksPerMinute = 600_000_000
|
||||||
|
private static let ticksPerHour = 36_000_000_000
|
||||||
|
private static let ticksPerDay = 864_000_000_000
|
||||||
|
|
||||||
|
// MARK: - Initializers
|
||||||
|
|
||||||
|
init(_ ticks: Int? = nil) {
|
||||||
|
self = ticks ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
init(seconds: Int? = nil) {
|
||||||
|
self = (seconds ?? 0) * ServerTicks.ticksPerSecond
|
||||||
|
}
|
||||||
|
|
||||||
|
init(minutes: Int? = nil) {
|
||||||
|
self = (minutes ?? 0) * ServerTicks.ticksPerMinute
|
||||||
|
}
|
||||||
|
|
||||||
|
init(hours: Int? = nil) {
|
||||||
|
self = (hours ?? 0) * ServerTicks.ticksPerHour
|
||||||
|
}
|
||||||
|
|
||||||
|
init(days: Int? = nil) {
|
||||||
|
self = (days ?? 0) * ServerTicks.ticksPerDay
|
||||||
|
}
|
||||||
|
|
||||||
|
init(timeInterval: TimeInterval? = nil) {
|
||||||
|
self = Int((timeInterval ?? 0) * Double(ServerTicks.ticksPerSecond))
|
||||||
|
}
|
||||||
|
|
||||||
|
init(date: Date) {
|
||||||
|
let components = Calendar.current.dateComponents([.hour, .minute], from: date)
|
||||||
|
let totalSeconds = TimeInterval((components.hour ?? 0) * 3600 + (components.minute ?? 0) * 60)
|
||||||
|
self = Int(totalSeconds * 10_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
|
var ticks: Int {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
var seconds: TimeInterval {
|
||||||
|
TimeInterval(self) / Double(ServerTicks.ticksPerSecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
var minutes: TimeInterval {
|
||||||
|
TimeInterval(self) / Double(ServerTicks.ticksPerMinute)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hours: TimeInterval {
|
||||||
|
TimeInterval(self) / Double(ServerTicks.ticksPerHour)
|
||||||
|
}
|
||||||
|
|
||||||
|
var days: TimeInterval {
|
||||||
|
TimeInterval(self) / Double(ServerTicks.ticksPerDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
var date: Date {
|
||||||
|
let totalSeconds = TimeInterval(self) / 10_000_000
|
||||||
|
let hours = Int(totalSeconds) / 3600
|
||||||
|
let minutes = (Int(totalSeconds) % 3600) / 60
|
||||||
|
var components = DateComponents()
|
||||||
|
components.hour = hours
|
||||||
|
components.minute = minutes
|
||||||
|
return Calendar.current.date(from: components) ?? Date()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// 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 JellyfinAPI
|
||||||
|
|
||||||
|
extension TaskState: Displayable {
|
||||||
|
|
||||||
|
var displayTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .cancelling:
|
||||||
|
return L10n.cancelling
|
||||||
|
case .idle:
|
||||||
|
return L10n.idle
|
||||||
|
case .running:
|
||||||
|
return L10n.running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// TODO: move to SDK as patch file
|
||||||
|
|
||||||
|
enum TaskTriggerType: String, Codable, CaseIterable, Displayable, SystemImageable {
|
||||||
|
|
||||||
|
case daily = "DailyTrigger"
|
||||||
|
case weekly = "WeeklyTrigger"
|
||||||
|
case interval = "IntervalTrigger"
|
||||||
|
case startup = "StartupTrigger"
|
||||||
|
|
||||||
|
var displayTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .daily:
|
||||||
|
return L10n.daily
|
||||||
|
case .weekly:
|
||||||
|
return L10n.weekly
|
||||||
|
case .interval:
|
||||||
|
return L10n.interval
|
||||||
|
case .startup:
|
||||||
|
return L10n.onApplicationStartup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemImage: String {
|
||||||
|
switch self {
|
||||||
|
case .daily:
|
||||||
|
return "clock"
|
||||||
|
case .weekly:
|
||||||
|
return "calendar"
|
||||||
|
case .interval:
|
||||||
|
return "timer"
|
||||||
|
case .startup:
|
||||||
|
return "power"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,12 @@ internal enum L10n {
|
||||||
internal static let accessibility = L10n.tr("Localizable", "accessibility", fallback: "Accessibility")
|
internal static let accessibility = L10n.tr("Localizable", "accessibility", fallback: "Accessibility")
|
||||||
/// ActiveSessionsView Header
|
/// ActiveSessionsView Header
|
||||||
internal static let activeDevices = L10n.tr("Localizable", "activeDevices", fallback: "Active Devices")
|
internal static let activeDevices = L10n.tr("Localizable", "activeDevices", fallback: "Active Devices")
|
||||||
|
/// Add
|
||||||
|
internal static let add = L10n.tr("Localizable", "add", fallback: "Add")
|
||||||
/// Select Server View - Add Server
|
/// Select Server View - 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
|
||||||
|
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")
|
||||||
/// Administration Dashboard Section
|
/// Administration Dashboard Section
|
||||||
|
@ -150,6 +154,8 @@ internal enum L10n {
|
||||||
internal static let category = L10n.tr("Localizable", "category", fallback: "Category")
|
internal static let category = L10n.tr("Localizable", "category", fallback: "Category")
|
||||||
/// Change Server
|
/// Change Server
|
||||||
internal static let changeServer = L10n.tr("Localizable", "changeServer", fallback: "Change Server")
|
internal static let changeServer = L10n.tr("Localizable", "changeServer", fallback: "Change Server")
|
||||||
|
/// Changes not saved
|
||||||
|
internal static let changesNotSaved = L10n.tr("Localizable", "changesNotSaved", fallback: "Changes not saved")
|
||||||
/// Channels
|
/// Channels
|
||||||
internal static let channels = L10n.tr("Localizable", "channels", fallback: "Channels")
|
internal static let channels = L10n.tr("Localizable", "channels", fallback: "Channels")
|
||||||
/// Chapters
|
/// Chapters
|
||||||
|
@ -234,14 +240,18 @@ internal enum L10n {
|
||||||
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
internal static let customize = L10n.tr("Localizable", "customize", fallback: "Customize")
|
||||||
/// Section Header for a Custom Device Profile
|
/// Section Header for a Custom Device Profile
|
||||||
internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom Profile")
|
internal static let customProfile = L10n.tr("Localizable", "customProfile", fallback: "Custom Profile")
|
||||||
|
/// Daily
|
||||||
|
internal static let daily = L10n.tr("Localizable", "daily", fallback: "Daily")
|
||||||
/// Represents the dark theme setting
|
/// Represents the dark theme setting
|
||||||
internal static let dark = L10n.tr("Localizable", "dark", fallback: "Dark")
|
internal static let dark = L10n.tr("Localizable", "dark", fallback: "Dark")
|
||||||
/// UserDashboardView Header
|
/// UserDashboardView Header
|
||||||
internal static let dashboard = L10n.tr("Localizable", "dashboard", fallback: "Dashboard")
|
internal static let dashboard = L10n.tr("Localizable", "dashboard", fallback: "Dashboard")
|
||||||
/// Description for the dashboard section
|
/// Description for the dashboard section
|
||||||
internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.")
|
internal static let dashboardDescription = L10n.tr("Localizable", "dashboardDescription", fallback: "Perform administrative tasks for your Jellyfin server.")
|
||||||
|
/// Day of Week
|
||||||
|
internal static let dayOfWeek = L10n.tr("Localizable", "dayOfWeek", fallback: "Day of Week")
|
||||||
/// Time Interval Help Text - Days
|
/// Time Interval Help Text - Days
|
||||||
internal static let days = L10n.tr("Localizable", "days", fallback: "days")
|
internal static let days = L10n.tr("Localizable", "days", fallback: "Days")
|
||||||
/// Default Scheme
|
/// Default Scheme
|
||||||
internal static let defaultScheme = L10n.tr("Localizable", "defaultScheme", fallback: "Default Scheme")
|
internal static let defaultScheme = L10n.tr("Localizable", "defaultScheme", fallback: "Default Scheme")
|
||||||
/// Server Detail View - Delete
|
/// Server Detail View - Delete
|
||||||
|
@ -262,8 +272,14 @@ internal enum L10n {
|
||||||
internal static let deleteSelectionDevicesWarning = L10n.tr("Localizable", "deleteSelectionDevicesWarning", fallback: "Are you sure you wish to delete all selected devices? All selected sessions will be logged out.")
|
internal static let deleteSelectionDevicesWarning = L10n.tr("Localizable", "deleteSelectionDevicesWarning", fallback: "Are you sure you wish to delete all selected devices? All selected sessions will be logged out.")
|
||||||
/// Server Detail View - Delete Server
|
/// Server Detail View - Delete Server
|
||||||
internal static let deleteServer = L10n.tr("Localizable", "deleteServer", fallback: "Delete Server")
|
internal static let deleteServer = L10n.tr("Localizable", "deleteServer", fallback: "Delete Server")
|
||||||
|
/// Delete Trigger
|
||||||
|
internal static let deleteTrigger = L10n.tr("Localizable", "deleteTrigger", fallback: "Delete Trigger")
|
||||||
|
/// Are you sure you want to delete this trigger? This action cannot be undone.
|
||||||
|
internal static let deleteTriggerConfirmationMessage = L10n.tr("Localizable", "deleteTriggerConfirmationMessage", fallback: "Are you sure you want to delete this trigger? This action cannot be undone.")
|
||||||
/// Delivery
|
/// Delivery
|
||||||
internal static let delivery = L10n.tr("Localizable", "delivery", fallback: "Delivery")
|
internal static let delivery = L10n.tr("Localizable", "delivery", fallback: "Delivery")
|
||||||
|
/// Details
|
||||||
|
internal static let details = L10n.tr("Localizable", "details", fallback: "Details")
|
||||||
/// Session Device Section Label
|
/// Session Device Section Label
|
||||||
internal static let device = L10n.tr("Localizable", "device", fallback: "Device")
|
internal static let device = L10n.tr("Localizable", "device", fallback: "Device")
|
||||||
/// Section Header for Device Profiles
|
/// Section Header for Device Profiles
|
||||||
|
@ -282,6 +298,8 @@ internal enum L10n {
|
||||||
internal static let directStream = L10n.tr("Localizable", "directStream", fallback: "Direct Stream")
|
internal static let directStream = L10n.tr("Localizable", "directStream", fallback: "Direct Stream")
|
||||||
/// Disabled
|
/// Disabled
|
||||||
internal static let disabled = L10n.tr("Localizable", "disabled", fallback: "Disabled")
|
internal static let disabled = L10n.tr("Localizable", "disabled", fallback: "Disabled")
|
||||||
|
/// Discard Changes
|
||||||
|
internal static let discardChanges = L10n.tr("Localizable", "discardChanges", fallback: "Discard Changes")
|
||||||
/// Discovered Servers
|
/// Discovered Servers
|
||||||
internal static let discoveredServers = L10n.tr("Localizable", "discoveredServers", fallback: "Discovered Servers")
|
internal static let discoveredServers = L10n.tr("Localizable", "discoveredServers", fallback: "Discovered Servers")
|
||||||
/// Dismiss
|
/// Dismiss
|
||||||
|
@ -312,6 +330,16 @@ internal enum L10n {
|
||||||
internal static let episodes = L10n.tr("Localizable", "episodes", fallback: "Episodes")
|
internal static let episodes = L10n.tr("Localizable", "episodes", fallback: "Episodes")
|
||||||
/// Error
|
/// Error
|
||||||
internal static let error = L10n.tr("Localizable", "error", fallback: "Error")
|
internal static let error = L10n.tr("Localizable", "error", fallback: "Error")
|
||||||
|
/// Error Details
|
||||||
|
internal static let errorDetails = L10n.tr("Localizable", "errorDetails", fallback: "Error Details")
|
||||||
|
/// Every
|
||||||
|
internal static let every = L10n.tr("Localizable", "every", fallback: "Every")
|
||||||
|
/// Every %1$@
|
||||||
|
internal static func everyInterval(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "everyInterval", String(describing: p1), fallback: "Every %1$@")
|
||||||
|
}
|
||||||
|
/// Executed
|
||||||
|
internal static let executed = L10n.tr("Localizable", "executed", fallback: "Executed")
|
||||||
/// Existing Server
|
/// Existing Server
|
||||||
internal static let existingServer = L10n.tr("Localizable", "existingServer", fallback: "Existing Server")
|
internal static let existingServer = L10n.tr("Localizable", "existingServer", fallback: "Existing Server")
|
||||||
/// Existing User
|
/// Existing User
|
||||||
|
@ -344,16 +372,26 @@ internal enum L10n {
|
||||||
internal static let hapticFeedback = L10n.tr("Localizable", "hapticFeedback", fallback: "Haptic Feedback")
|
internal static let hapticFeedback = L10n.tr("Localizable", "hapticFeedback", fallback: "Haptic Feedback")
|
||||||
/// Home
|
/// Home
|
||||||
internal static let home = L10n.tr("Localizable", "home", fallback: "Home")
|
internal static let home = L10n.tr("Localizable", "home", fallback: "Home")
|
||||||
|
/// Hours
|
||||||
|
internal static let hours = L10n.tr("Localizable", "hours", fallback: "Hours")
|
||||||
|
/// Idle
|
||||||
|
internal static let idle = L10n.tr("Localizable", "idle", fallback: "Idle")
|
||||||
/// Customize Server View - Indicators
|
/// Customize Server View - Indicators
|
||||||
internal static let indicators = L10n.tr("Localizable", "indicators", fallback: "Indicators")
|
internal static let indicators = L10n.tr("Localizable", "indicators", fallback: "Indicators")
|
||||||
/// Information
|
/// Information
|
||||||
internal static let information = L10n.tr("Localizable", "information", fallback: "Information")
|
internal static let information = L10n.tr("Localizable", "information", fallback: "Information")
|
||||||
/// TranscodeReason - Interlaced Video Not Supported
|
/// TranscodeReason - Interlaced Video Not Supported
|
||||||
internal static let interlacedVideoNotSupported = L10n.tr("Localizable", "interlacedVideoNotSupported", fallback: "Interlaced video is not supported")
|
internal static let interlacedVideoNotSupported = L10n.tr("Localizable", "interlacedVideoNotSupported", fallback: "Interlaced video is not supported")
|
||||||
|
/// Interval
|
||||||
|
internal static let interval = L10n.tr("Localizable", "interval", fallback: "Interval")
|
||||||
/// Inverted Dark
|
/// Inverted Dark
|
||||||
internal static let invertedDark = L10n.tr("Localizable", "invertedDark", fallback: "Inverted Dark")
|
internal static let invertedDark = L10n.tr("Localizable", "invertedDark", fallback: "Inverted Dark")
|
||||||
/// Inverted Light
|
/// Inverted Light
|
||||||
internal static let invertedLight = L10n.tr("Localizable", "invertedLight", fallback: "Inverted Light")
|
internal static let invertedLight = L10n.tr("Localizable", "invertedLight", fallback: "Inverted Light")
|
||||||
|
/// %1$@ at %2$@
|
||||||
|
internal static func itemAtItem(_ p1: Any, _ p2: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "itemAtItem", String(describing: p1), String(describing: p2), fallback: "%1$@ at %2$@")
|
||||||
|
}
|
||||||
/// SessionPlaybackMethod Remaining Time
|
/// SessionPlaybackMethod Remaining Time
|
||||||
internal static func itemOverItem(_ p1: Any, _ p2: Any) -> String {
|
internal static func itemOverItem(_ p1: Any, _ p2: Any) -> String {
|
||||||
return L10n.tr("Localizable", "itemOverItem", String(describing: p1), String(describing: p2), fallback: "%1$@ / %2$@")
|
return L10n.tr("Localizable", "itemOverItem", String(describing: p1), String(describing: p2), fallback: "%1$@ / %2$@")
|
||||||
|
@ -420,6 +458,8 @@ internal enum L10n {
|
||||||
}
|
}
|
||||||
/// Settings View - Logs
|
/// Settings View - Logs
|
||||||
internal static let logs = L10n.tr("Localizable", "logs", fallback: "Logs")
|
internal static let logs = L10n.tr("Localizable", "logs", fallback: "Logs")
|
||||||
|
/// Access the Jellyfin server logs for troubleshooting and monitoring purposes.
|
||||||
|
internal static let logsDescription = L10n.tr("Localizable", "logsDescription", fallback: "Access the Jellyfin server logs for troubleshooting and monitoring purposes.")
|
||||||
/// Option to set the maximum bitrate for playback
|
/// Option to set the maximum bitrate for playback
|
||||||
internal static let maximumBitrate = L10n.tr("Localizable", "maximumBitrate", fallback: "Maximum Bitrate")
|
internal static let maximumBitrate = L10n.tr("Localizable", "maximumBitrate", fallback: "Maximum Bitrate")
|
||||||
/// Playback May Fail
|
/// Playback May Fail
|
||||||
|
@ -430,6 +470,8 @@ internal enum L10n {
|
||||||
internal static let menuButtons = L10n.tr("Localizable", "menuButtons", fallback: "Menu Buttons")
|
internal static let menuButtons = L10n.tr("Localizable", "menuButtons", fallback: "Menu Buttons")
|
||||||
/// The play method (e.g., Direct Play, Transcoding)
|
/// The play method (e.g., Direct Play, Transcoding)
|
||||||
internal static let method = L10n.tr("Localizable", "method", fallback: "Method")
|
internal static let method = L10n.tr("Localizable", "method", fallback: "Method")
|
||||||
|
/// Minutes
|
||||||
|
internal static let minutes = L10n.tr("Localizable", "minutes", fallback: "Minutes")
|
||||||
/// Missing
|
/// Missing
|
||||||
internal static let missing = L10n.tr("Localizable", "missing", fallback: "Missing")
|
internal static let missing = L10n.tr("Localizable", "missing", fallback: "Missing")
|
||||||
/// Missing Items
|
/// Missing Items
|
||||||
|
@ -488,6 +530,8 @@ internal enum L10n {
|
||||||
internal static let noResults = L10n.tr("Localizable", "noResults", fallback: "No results.")
|
internal static let noResults = L10n.tr("Localizable", "noResults", fallback: "No results.")
|
||||||
/// Normal
|
/// Normal
|
||||||
internal static let normal = L10n.tr("Localizable", "normal", fallback: "Normal")
|
internal static let normal = L10n.tr("Localizable", "normal", fallback: "Normal")
|
||||||
|
/// No runtime limit
|
||||||
|
internal static let noRuntimeLimit = L10n.tr("Localizable", "noRuntimeLimit", fallback: "No runtime limit")
|
||||||
/// No active session available
|
/// No active session available
|
||||||
internal static let noSession = L10n.tr("Localizable", "noSession", fallback: "No session")
|
internal static let noSession = L10n.tr("Localizable", "noSession", fallback: "No session")
|
||||||
/// N/A
|
/// N/A
|
||||||
|
@ -502,6 +546,8 @@ internal enum L10n {
|
||||||
internal static let offset = L10n.tr("Localizable", "offset", fallback: "Offset")
|
internal static let offset = L10n.tr("Localizable", "offset", fallback: "Offset")
|
||||||
/// Ok
|
/// Ok
|
||||||
internal static let ok = L10n.tr("Localizable", "ok", fallback: "Ok")
|
internal static let ok = L10n.tr("Localizable", "ok", fallback: "Ok")
|
||||||
|
/// On application startup
|
||||||
|
internal static let onApplicationStartup = L10n.tr("Localizable", "onApplicationStartup", fallback: "On application startup")
|
||||||
/// 1 user
|
/// 1 user
|
||||||
internal static let oneUser = L10n.tr("Localizable", "oneUser", fallback: "1 user")
|
internal static let oneUser = L10n.tr("Localizable", "oneUser", fallback: "1 user")
|
||||||
/// Indicates that something is Online
|
/// Indicates that something is Online
|
||||||
|
@ -674,7 +720,7 @@ internal enum L10n {
|
||||||
internal static let running = L10n.tr("Localizable", "running", fallback: "Running...")
|
internal static let running = L10n.tr("Localizable", "running", fallback: "Running...")
|
||||||
/// Runtime
|
/// Runtime
|
||||||
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
|
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
|
||||||
/// Save - Completed, end, or save
|
/// Save
|
||||||
internal static let save = L10n.tr("Localizable", "save", fallback: "Save")
|
internal static let save = L10n.tr("Localizable", "save", fallback: "Save")
|
||||||
/// Administration Dashboard Scan All Libraries Button
|
/// Administration Dashboard Scan All Libraries Button
|
||||||
internal static let scanAllLibraries = L10n.tr("Localizable", "scanAllLibraries", fallback: "Scan All Libraries")
|
internal static let scanAllLibraries = L10n.tr("Localizable", "scanAllLibraries", fallback: "Scan All Libraries")
|
||||||
|
@ -730,6 +776,14 @@ internal enum L10n {
|
||||||
internal static let serverLogs = L10n.tr("Localizable", "serverLogs", fallback: "Server Logs")
|
internal static let serverLogs = L10n.tr("Localizable", "serverLogs", fallback: "Server Logs")
|
||||||
/// Select Server View
|
/// Select Server View
|
||||||
internal static let servers = L10n.tr("Localizable", "servers", fallback: "Servers")
|
internal static let servers = L10n.tr("Localizable", "servers", fallback: "Servers")
|
||||||
|
/// A new trigger was created for '%1$@'.
|
||||||
|
internal static func serverTriggerCreated(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "serverTriggerCreated", String(describing: p1), fallback: "A new trigger was created for '%1$@'.")
|
||||||
|
}
|
||||||
|
/// The selected trigger was deleted from '%1$@'.
|
||||||
|
internal static func serverTriggerDeleted(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "serverTriggerDeleted", String(describing: p1), fallback: "The selected trigger was deleted from '%1$@'.")
|
||||||
|
}
|
||||||
/// Server URL
|
/// Server URL
|
||||||
internal static let serverURL = L10n.tr("Localizable", "serverURL", fallback: "Server URL")
|
internal static let serverURL = L10n.tr("Localizable", "serverURL", fallback: "Server URL")
|
||||||
/// The title for the session view
|
/// The title for the session view
|
||||||
|
@ -796,6 +850,8 @@ internal enum L10n {
|
||||||
internal static let specialFeatures = L10n.tr("Localizable", "specialFeatures", fallback: "Special Features")
|
internal static let specialFeatures = L10n.tr("Localizable", "specialFeatures", fallback: "Special Features")
|
||||||
/// Sports
|
/// Sports
|
||||||
internal static let sports = L10n.tr("Localizable", "sports", fallback: "Sports")
|
internal static let sports = L10n.tr("Localizable", "sports", fallback: "Sports")
|
||||||
|
/// Status
|
||||||
|
internal static let status = L10n.tr("Localizable", "status", fallback: "Status")
|
||||||
/// Button label to stop a task
|
/// Button label to stop a task
|
||||||
internal static let stop = L10n.tr("Localizable", "stop", fallback: "Stop")
|
internal static let stop = L10n.tr("Localizable", "stop", fallback: "Stop")
|
||||||
/// Session Streaming Clients
|
/// Session Streaming Clients
|
||||||
|
@ -854,8 +910,24 @@ internal enum L10n {
|
||||||
internal static let tasks = L10n.tr("Localizable", "tasks", fallback: "Tasks")
|
internal static let tasks = L10n.tr("Localizable", "tasks", fallback: "Tasks")
|
||||||
/// Description for the tasks section
|
/// Description for the tasks section
|
||||||
internal static let tasksDescription = L10n.tr("Localizable", "tasksDescription", fallback: "Tasks are operations that are scheduled to run periodically or can be triggered manually.")
|
internal static let tasksDescription = L10n.tr("Localizable", "tasksDescription", fallback: "Tasks are operations that are scheduled to run periodically or can be triggered manually.")
|
||||||
|
/// Sets the duration (in minutes) in between task triggers.
|
||||||
|
internal static let taskTriggerInterval = L10n.tr("Localizable", "taskTriggerInterval", fallback: "Sets the duration (in minutes) in between task triggers.")
|
||||||
|
/// Sets the maximum runtime (in hours) for this task trigger.
|
||||||
|
internal static let taskTriggerTimeLimit = L10n.tr("Localizable", "taskTriggerTimeLimit", fallback: "Sets the maximum runtime (in hours) for this task trigger.")
|
||||||
/// Option to set the test size for bitrate testing
|
/// Option to set the test size for bitrate testing
|
||||||
internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size")
|
internal static let testSize = L10n.tr("Localizable", "testSize", fallback: "Test Size")
|
||||||
|
/// Time
|
||||||
|
internal static let time = L10n.tr("Localizable", "time", fallback: "Time")
|
||||||
|
/// Time Limit
|
||||||
|
internal static let timeLimit = L10n.tr("Localizable", "timeLimit", fallback: "Time Limit")
|
||||||
|
/// Time limit: %1$@
|
||||||
|
internal static func timeLimitLabelWithValue(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "timeLimitLabelWithValue", String(describing: p1), fallback: "Time limit: %1$@")
|
||||||
|
}
|
||||||
|
/// Time Limit (%@)
|
||||||
|
internal static func timeLimitWithUnit(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "timeLimitWithUnit", String(describing: p1), fallback: "Time Limit (%@)")
|
||||||
|
}
|
||||||
/// Timestamp
|
/// Timestamp
|
||||||
internal static let timestamp = L10n.tr("Localizable", "timestamp", fallback: "Timestamp")
|
internal static let timestamp = L10n.tr("Localizable", "timestamp", fallback: "Timestamp")
|
||||||
/// Timestamp Type
|
/// Timestamp Type
|
||||||
|
@ -870,10 +942,16 @@ internal enum L10n {
|
||||||
internal static let transcodeReasons = L10n.tr("Localizable", "transcodeReasons", fallback: "Transcode Reason(s)")
|
internal static let transcodeReasons = L10n.tr("Localizable", "transcodeReasons", fallback: "Transcode Reason(s)")
|
||||||
/// Transition
|
/// Transition
|
||||||
internal static let transition = L10n.tr("Localizable", "transition", fallback: "Transition")
|
internal static let transition = L10n.tr("Localizable", "transition", fallback: "Transition")
|
||||||
|
/// Trigger already exists
|
||||||
|
internal static let triggerAlreadyExists = L10n.tr("Localizable", "triggerAlreadyExists", fallback: "Trigger already exists")
|
||||||
|
/// Triggers
|
||||||
|
internal static let triggers = L10n.tr("Localizable", "triggers", fallback: "Triggers")
|
||||||
/// Try again
|
/// Try again
|
||||||
internal static let tryAgain = L10n.tr("Localizable", "tryAgain", fallback: "Try again")
|
internal static let tryAgain = L10n.tr("Localizable", "tryAgain", fallback: "Try again")
|
||||||
/// TV Shows
|
/// TV Shows
|
||||||
internal static let tvShows = L10n.tr("Localizable", "tvShows", fallback: "TV Shows")
|
internal static let tvShows = L10n.tr("Localizable", "tvShows", fallback: "TV Shows")
|
||||||
|
/// Indicate a type
|
||||||
|
internal static let type = L10n.tr("Localizable", "type", fallback: "Type")
|
||||||
/// Unable to connect to server
|
/// Unable to connect to server
|
||||||
internal static let unableToConnectServer = L10n.tr("Localizable", "unableToConnectServer", fallback: "Unable to connect to server")
|
internal static let unableToConnectServer = L10n.tr("Localizable", "unableToConnectServer", fallback: "Unable to connect to server")
|
||||||
/// Unable to find host
|
/// Unable to find host
|
||||||
|
@ -894,6 +972,8 @@ internal enum L10n {
|
||||||
internal static let unknownVideoStreamInfo = L10n.tr("Localizable", "unknownVideoStreamInfo", fallback: "The video stream information is unknown")
|
internal static let unknownVideoStreamInfo = L10n.tr("Localizable", "unknownVideoStreamInfo", fallback: "The video stream information is unknown")
|
||||||
/// Unplayed
|
/// Unplayed
|
||||||
internal static let unplayed = L10n.tr("Localizable", "unplayed", fallback: "Unplayed")
|
internal static let unplayed = L10n.tr("Localizable", "unplayed", fallback: "Unplayed")
|
||||||
|
/// You have unsaved changes. Are you sure you want to discard them?
|
||||||
|
internal static let unsavedChangesMessage = L10n.tr("Localizable", "unsavedChangesMessage", fallback: "You have unsaved changes. Are you sure you want to discard them?")
|
||||||
/// URL
|
/// URL
|
||||||
internal static let url = L10n.tr("Localizable", "url", fallback: "URL")
|
internal static let url = L10n.tr("Localizable", "url", fallback: "URL")
|
||||||
/// Override Transcoding Profile
|
/// Override Transcoding Profile
|
||||||
|
@ -934,6 +1014,8 @@ internal enum L10n {
|
||||||
internal static let videoRangeTypeNotSupported = L10n.tr("Localizable", "videoRangeTypeNotSupported", fallback: "The video range type is not supported")
|
internal static let videoRangeTypeNotSupported = L10n.tr("Localizable", "videoRangeTypeNotSupported", fallback: "The video range type is not supported")
|
||||||
/// TranscodeReason - Video Resolution Not Supported
|
/// TranscodeReason - Video Resolution Not Supported
|
||||||
internal static let videoResolutionNotSupported = L10n.tr("Localizable", "videoResolutionNotSupported", fallback: "The video resolution is not supported")
|
internal static let videoResolutionNotSupported = L10n.tr("Localizable", "videoResolutionNotSupported", fallback: "The video resolution is not supported")
|
||||||
|
/// Weekly
|
||||||
|
internal static let weekly = L10n.tr("Localizable", "weekly", fallback: "Weekly")
|
||||||
/// Who's watching?
|
/// Who's watching?
|
||||||
internal static let whosWatching = L10n.tr("Localizable", "WhosWatching", fallback: "Who's watching?")
|
internal static let whosWatching = L10n.tr("Localizable", "WhosWatching", fallback: "Who's watching?")
|
||||||
/// WIP
|
/// WIP
|
||||||
|
|
|
@ -100,7 +100,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
|
||||||
}
|
}
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self?.backgroundStates.remove(.gettingDevices)
|
let _ = self?.backgroundStates.remove(.gettingDevices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.asAnyCancellable()
|
.asAnyCancellable()
|
||||||
|
@ -129,7 +129,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
|
||||||
}
|
}
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self?.backgroundStates.remove(.settingCustomName)
|
let _ = self?.backgroundStates.remove(.settingCustomName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.asAnyCancellable()
|
.asAnyCancellable()
|
||||||
|
@ -157,7 +157,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
|
||||||
}
|
}
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self?.backgroundStates.remove(.deletingDevices)
|
let _ = self?.backgroundStates.remove(.deletingDevices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.asAnyCancellable()
|
.asAnyCancellable()
|
||||||
|
@ -203,7 +203,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
|
||||||
let request = Paths.updateDeviceOptions(id: id, DeviceOptionsDto(customName: newName))
|
let request = Paths.updateDeviceOptions(id: id, DeviceOptionsDto(customName: newName))
|
||||||
try await userSession.client.send(request)
|
try await userSession.client.send(request)
|
||||||
|
|
||||||
if let device = self.devices[id]?.value {
|
if let _ = devices[id]?.value {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.devices[id]?.value?.name = newName
|
self.devices[id]?.value?.name = newName
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ final class DevicesViewModel: ViewModel, Eventful, Stateful {
|
||||||
try await userSession.client.send(request)
|
try await userSession.client.send(request)
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.devices.removeValue(forKey: id)
|
let _ = self.devices.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import SwiftUI
|
||||||
// TODO: do something for errors from restart/shutdown
|
// TODO: do something for errors from restart/shutdown
|
||||||
// - toast?
|
// - toast?
|
||||||
|
|
||||||
final class ScheduledTasksViewModel: ViewModel, Stateful {
|
final class ServerTasksViewModel: ViewModel, Stateful {
|
||||||
|
|
||||||
// MARK: - Action
|
// MARK: - Action
|
||||||
|
|
||||||
|
|
|
@ -9,38 +9,76 @@
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
import OrderedCollections
|
||||||
|
|
||||||
// TODO: refactor with socket implementation
|
// TODO: refactor with socket implementation
|
||||||
// TODO: edit triggers
|
// TODO: for trigger updating, could temp set new triggers
|
||||||
|
// and set back on failure
|
||||||
|
|
||||||
final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
final class ServerTaskObserver: ViewModel, Stateful, Eventful, Identifiable {
|
||||||
|
|
||||||
|
// MARK: Event
|
||||||
|
|
||||||
|
enum Event {
|
||||||
|
case error(JellyfinAPIError)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BackgroundState {
|
||||||
|
case updatingTriggers
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Action
|
||||||
|
|
||||||
enum Action: Equatable {
|
enum Action: Equatable {
|
||||||
case start
|
case start
|
||||||
case stop
|
case stop
|
||||||
case stopObserving
|
case stopObserving
|
||||||
|
case addTrigger(TaskTriggerInfo)
|
||||||
|
case removeTrigger(TaskTriggerInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: State
|
||||||
|
|
||||||
enum State: Hashable {
|
enum State: Hashable {
|
||||||
case error(JellyfinAPIError)
|
case error(JellyfinAPIError)
|
||||||
case initial
|
case initial
|
||||||
case running
|
case running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Published Values
|
||||||
|
|
||||||
|
@Published
|
||||||
|
final var backgroundStates: OrderedSet<BackgroundState> = []
|
||||||
@Published
|
@Published
|
||||||
final var state: State = .initial
|
final var state: State = .initial
|
||||||
@Published
|
@Published
|
||||||
private(set) var task: TaskInfo
|
private(set) var task: TaskInfo
|
||||||
|
|
||||||
|
// MARK: Cancellable Tasks
|
||||||
|
|
||||||
private var progressCancellable: AnyCancellable?
|
private var progressCancellable: AnyCancellable?
|
||||||
private var cancelCancellable: AnyCancellable?
|
private var cancelCancellable: AnyCancellable?
|
||||||
|
|
||||||
|
// MARK: Initialize from TaskId
|
||||||
|
|
||||||
var id: String? { task.id }
|
var id: String? { task.id }
|
||||||
|
|
||||||
init(task: TaskInfo) {
|
init(task: TaskInfo) {
|
||||||
self.task = task
|
self.task = task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Event Variables
|
||||||
|
|
||||||
|
private var eventSubject: PassthroughSubject<Event, Never> = .init()
|
||||||
|
|
||||||
|
var events: AnyPublisher<Event, Never> {
|
||||||
|
eventSubject
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Respond to Action
|
||||||
|
|
||||||
func respond(to action: Action) -> State {
|
func respond(to action: Action) -> State {
|
||||||
switch action {
|
switch action {
|
||||||
case .start:
|
case .start:
|
||||||
|
@ -58,6 +96,7 @@ final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.state = .error(.init(error.localizedDescription))
|
self.state = .error(.init(error.localizedDescription))
|
||||||
|
self.eventSubject.send(.error(.init(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +117,7 @@ final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
self.state = .error(.init(error.localizedDescription))
|
self.state = .error(.init(error.localizedDescription))
|
||||||
|
self.eventSubject.send(.error(.init(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,9 +129,65 @@ final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
||||||
cancelCancellable?.cancel()
|
cancelCancellable?.cancel()
|
||||||
|
|
||||||
return .initial
|
return .initial
|
||||||
|
case let .addTrigger(trigger):
|
||||||
|
progressCancellable?.cancel()
|
||||||
|
cancelCancellable?.cancel()
|
||||||
|
|
||||||
|
cancelCancellable = Task {
|
||||||
|
let updatedTriggers = (task.triggers ?? [])
|
||||||
|
.appending(trigger)
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.append(.updatingTriggers)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await updateTriggers(updatedTriggers)
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
self.eventSubject.send(.error(.init(error.localizedDescription)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.remove(.updatingTriggers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asAnyCancellable()
|
||||||
|
|
||||||
|
return .running
|
||||||
|
case let .removeTrigger(trigger):
|
||||||
|
progressCancellable?.cancel()
|
||||||
|
cancelCancellable?.cancel()
|
||||||
|
|
||||||
|
cancelCancellable = Task {
|
||||||
|
var updatedTriggers = (task.triggers ?? [])
|
||||||
|
updatedTriggers.removeAll { $0 == trigger }
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.append(.updatingTriggers)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await updateTriggers(updatedTriggers)
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
self.eventSubject.send(.error(.init(error.localizedDescription)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.remove(.updatingTriggers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asAnyCancellable()
|
||||||
|
|
||||||
|
return .running
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Start Task
|
||||||
|
|
||||||
private func start() async throws {
|
private func start() async throws {
|
||||||
guard let id = task.id else { return }
|
guard let id = task.id else { return }
|
||||||
|
|
||||||
|
@ -101,6 +197,8 @@ final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
||||||
try await pollTaskProgress(id: id)
|
try await pollTaskProgress(id: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Poll Task Progress
|
||||||
|
|
||||||
private func pollTaskProgress(id: String) async throws {
|
private func pollTaskProgress(id: String) async throws {
|
||||||
while true {
|
while true {
|
||||||
let request = Paths.getTask(taskID: id)
|
let request = Paths.getTask(taskID: id)
|
||||||
|
@ -118,10 +216,26 @@ final class ServerTaskObserver: ViewModel, Stateful, Identifiable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Stop Task
|
||||||
|
|
||||||
private func stop() async throws {
|
private func stop() async throws {
|
||||||
guard let id = task.id else { return }
|
guard let id = task.id else { return }
|
||||||
|
|
||||||
let request = Paths.stopTask(taskID: id)
|
let request = Paths.stopTask(taskID: id)
|
||||||
try await userSession.client.send(request)
|
try await userSession.client.send(request)
|
||||||
|
|
||||||
|
try await pollTaskProgress(id: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Update Triggers
|
||||||
|
|
||||||
|
private func updateTriggers(_ updatedTriggers: [TaskTriggerInfo]) async throws {
|
||||||
|
guard let id = task.id else { return }
|
||||||
|
let updateRequest = Paths.updateTask(taskID: id, updatedTriggers)
|
||||||
|
try await userSession.client.send(updateRequest)
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
self.task.triggers = updatedTriggers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
|
4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
|
||||||
4E17498E2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
4E17498E2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
||||||
4E17498F2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
4E17498F2CC00A3100DD07D1 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */; };
|
||||||
4E182C9C2C94993200FBEFD5 /* ScheduledTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9B2C94993200FBEFD5 /* ScheduledTasksView.swift */; };
|
4E182C9C2C94993200FBEFD5 /* ServerTasksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */; };
|
||||||
4E182C9F2C94A1E000FBEFD5 /* ScheduledTaskButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9E2C94A1E000FBEFD5 /* ScheduledTaskButton.swift */; };
|
4E182C9F2C94A1E000FBEFD5 /* ServerTaskRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */; };
|
||||||
4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */; };
|
4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */; };
|
||||||
4E2182E52CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
|
4E2182E52CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
|
||||||
4E2182E62CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
|
4E2182E62CAF67F50094806B /* PlayMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2182E42CAF67EF0094806B /* PlayMethod.swift */; };
|
||||||
|
@ -43,6 +43,19 @@
|
||||||
4E2AC4D42C6C4C1200DD600D /* OrderedSectionSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */; };
|
4E2AC4D42C6C4C1200DD600D /* OrderedSectionSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */; };
|
||||||
4E2AC4D62C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D52C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift */; };
|
4E2AC4D62C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D52C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift */; };
|
||||||
4E2AC4D92C6C4D9400DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D72C6C4D8D00DD600D /* PlaybackQualitySettingsView.swift */; };
|
4E2AC4D92C6C4D9400DD600D /* PlaybackQualitySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2AC4D72C6C4D8D00DD600D /* PlaybackQualitySettingsView.swift */; };
|
||||||
|
4E35CE5C2CBED3F300DBD886 /* TimeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE562CBED3F300DBD886 /* TimeRow.swift */; };
|
||||||
|
4E35CE5D2CBED3F300DBD886 /* TriggerTypeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE572CBED3F300DBD886 /* TriggerTypeRow.swift */; };
|
||||||
|
4E35CE5E2CBED3F300DBD886 /* AddTaskTriggerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE5A2CBED3F300DBD886 /* AddTaskTriggerView.swift */; };
|
||||||
|
4E35CE5F2CBED3F300DBD886 /* IntervalRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE542CBED3F300DBD886 /* IntervalRow.swift */; };
|
||||||
|
4E35CE602CBED3F300DBD886 /* DayOfWeekRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE532CBED3F300DBD886 /* DayOfWeekRow.swift */; };
|
||||||
|
4E35CE612CBED3F300DBD886 /* TimeLimitSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE552CBED3F300DBD886 /* TimeLimitSection.swift */; };
|
||||||
|
4E35CE642CBED69600DBD886 /* TaskTriggerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE632CBED69600DBD886 /* TaskTriggerType.swift */; };
|
||||||
|
4E35CE662CBED8B600DBD886 /* ServerTicks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE652CBED8B300DBD886 /* ServerTicks.swift */; };
|
||||||
|
4E35CE672CBED8B600DBD886 /* ServerTicks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE652CBED8B300DBD886 /* ServerTicks.swift */; };
|
||||||
|
4E35CE692CBED95F00DBD886 /* DayOfWeek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE682CBED95F00DBD886 /* DayOfWeek.swift */; };
|
||||||
|
4E35CE6A2CBED95F00DBD886 /* DayOfWeek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE682CBED95F00DBD886 /* DayOfWeek.swift */; };
|
||||||
|
4E35CE6C2CBEDB7600DBD886 /* TaskState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE6B2CBEDB7300DBD886 /* TaskState.swift */; };
|
||||||
|
4E35CE6D2CBEDB7600DBD886 /* TaskState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E35CE6B2CBEDB7300DBD886 /* TaskState.swift */; };
|
||||||
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
|
4E4A53222CBE0A1C003BD24D /* ChevronAlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */; };
|
||||||
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
|
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
|
||||||
4E63B9FA2C8A5BEF00C25378 /* UserDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */; };
|
4E63B9FA2C8A5BEF00C25378 /* UserDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */; };
|
||||||
|
@ -57,6 +70,12 @@
|
||||||
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */; };
|
4E762AAF2C3A1A95004D1579 /* PlaybackBitrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.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 */; };
|
||||||
|
4E90F7642CC72B1F00417C31 /* LastRunSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F75B2CC72B1F00417C31 /* LastRunSection.swift */; };
|
||||||
|
4E90F7652CC72B1F00417C31 /* EditServerTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */; };
|
||||||
|
4E90F7662CC72B1F00417C31 /* LastErrorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F75A2CC72B1F00417C31 /* LastErrorSection.swift */; };
|
||||||
|
4E90F7672CC72B1F00417C31 /* TriggerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F75F2CC72B1F00417C31 /* TriggerRow.swift */; };
|
||||||
|
4E90F7682CC72B1F00417C31 /* TriggersSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F75D2CC72B1F00417C31 /* TriggersSection.swift */; };
|
||||||
|
4E90F76A2CC72B1F00417C31 /* DetailsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E90F7592CC72B1F00417C31 /* DetailsSection.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 */; };
|
||||||
|
@ -64,7 +83,7 @@
|
||||||
4E9A24ED2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24EC2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift */; };
|
4E9A24ED2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9A24EC2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift */; };
|
||||||
4EB1404C2C8E45B1008691F3 /* StreamSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1404B2C8E45B1008691F3 /* StreamSection.swift */; };
|
4EB1404C2C8E45B1008691F3 /* StreamSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1404B2C8E45B1008691F3 /* StreamSection.swift */; };
|
||||||
4EB1A8CA2C9A766200F43898 /* ActiveSessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8C92C9A765800F43898 /* ActiveSessionsView.swift */; };
|
4EB1A8CA2C9A766200F43898 /* ActiveSessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8C92C9A765800F43898 /* ActiveSessionsView.swift */; };
|
||||||
4EB1A8CC2C9B1BA200F43898 /* ServerTaskButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CB2C9B1B9700F43898 /* ServerTaskButton.swift */; };
|
4EB1A8CC2C9B1BA200F43898 /* DestructiveServerTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CB2C9B1B9700F43898 /* DestructiveServerTask.swift */; };
|
||||||
4EB1A8CE2C9B2D0800F43898 /* ActiveSessionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */; };
|
4EB1A8CE2C9B2D0800F43898 /* ActiveSessionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */; };
|
||||||
4EB4ECE32CBEFC4D002FF2FC /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */; };
|
4EB4ECE32CBEFC4D002FF2FC /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */; };
|
||||||
4EB4ECE42CBEFC4D002FF2FC /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */; };
|
4EB4ECE42CBEFC4D002FF2FC /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */; };
|
||||||
|
@ -87,7 +106,7 @@
|
||||||
4ECDAA9F2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; };
|
4ECDAA9F2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; };
|
||||||
4EDBDCD12CBDD6590033D347 /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */; };
|
4EDBDCD12CBDD6590033D347 /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */; };
|
||||||
4EDBDCD22CBDD6590033D347 /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */; };
|
4EDBDCD22CBDD6590033D347 /* SessionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */; };
|
||||||
4EE141692C8BABDF0045B661 /* ProgressSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE141682C8BABDF0045B661 /* ProgressSection.swift */; };
|
4EE141692C8BABDF0045B661 /* ActiveSessionProgressSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE141682C8BABDF0045B661 /* ActiveSessionProgressSection.swift */; };
|
||||||
4EED874A2CBF824B002354D2 /* DeviceRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED87462CBF824B002354D2 /* DeviceRow.swift */; };
|
4EED874A2CBF824B002354D2 /* DeviceRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED87462CBF824B002354D2 /* DeviceRow.swift */; };
|
||||||
4EED874B2CBF824B002354D2 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED87482CBF824B002354D2 /* DevicesView.swift */; };
|
4EED874B2CBF824B002354D2 /* DevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED87482CBF824B002354D2 /* DevicesView.swift */; };
|
||||||
4EED87502CBF84AD002354D2 /* DevicesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */; };
|
4EED87502CBF84AD002354D2 /* DevicesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */; };
|
||||||
|
@ -359,6 +378,7 @@
|
||||||
E11895B42893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */; };
|
E11895B42893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */; };
|
||||||
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1194F4D2BEABA9100888DB6 /* NavigationBarCloseButton.swift */; };
|
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1194F4D2BEABA9100888DB6 /* NavigationBarCloseButton.swift */; };
|
||||||
E1194F502BEB1E3000888DB6 /* StoredValues+Temp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1194F4F2BEB1E3000888DB6 /* StoredValues+Temp.swift */; };
|
E1194F502BEB1E3000888DB6 /* StoredValues+Temp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1194F4F2BEB1E3000888DB6 /* StoredValues+Temp.swift */; };
|
||||||
|
E119696A2CC99EA9001A58BE /* ServerTaskProgressSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11969692CC99EA9001A58BE /* ServerTaskProgressSection.swift */; };
|
||||||
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||||
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||||
E11BDF772B8513B40045C54A /* ItemGenre.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11BDF762B8513B40045C54A /* ItemGenre.swift */; };
|
E11BDF772B8513B40045C54A /* ItemGenre.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11BDF762B8513B40045C54A /* ItemGenre.swift */; };
|
||||||
|
@ -979,7 +999,6 @@
|
||||||
E1EA9F6B28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */; };
|
E1EA9F6B28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */; };
|
||||||
E1EBCB42278BD174009FE6E9 /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */; };
|
E1EBCB42278BD174009FE6E9 /* TruncatedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */; };
|
||||||
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; };
|
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */; };
|
||||||
E1ED7FD62CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FD52CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift */; };
|
|
||||||
E1ED7FD82CA8AF7400ACB6E3 /* ServerTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */; };
|
E1ED7FD82CA8AF7400ACB6E3 /* ServerTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */; };
|
||||||
E1ED7FD92CA8AF7400ACB6E3 /* ServerTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */; };
|
E1ED7FD92CA8AF7400ACB6E3 /* ServerTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */; };
|
||||||
E1ED7FDB2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */; };
|
E1ED7FDB2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */; };
|
||||||
|
@ -1048,8 +1067,8 @@
|
||||||
4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = "<group>"; };
|
4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = "<group>"; };
|
4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = "<group>"; };
|
||||||
4E182C9B2C94993200FBEFD5 /* ScheduledTasksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduledTasksView.swift; sourceTree = "<group>"; };
|
4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksView.swift; sourceTree = "<group>"; };
|
||||||
4E182C9E2C94A1E000FBEFD5 /* ScheduledTaskButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduledTaskButton.swift; sourceTree = "<group>"; };
|
4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskRow.swift; sourceTree = "<group>"; };
|
||||||
4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeSettingsCoordinator.swift; sourceTree = "<group>"; };
|
4E204E582C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeSettingsCoordinator.swift; sourceTree = "<group>"; };
|
||||||
4E2182E42CAF67EF0094806B /* PlayMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMethod.swift; sourceTree = "<group>"; };
|
4E2182E42CAF67EF0094806B /* PlayMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayMethod.swift; sourceTree = "<group>"; };
|
||||||
4E2AC4BD2C6C48D200DD600D /* CustomDeviceProfileAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileAction.swift; sourceTree = "<group>"; };
|
4E2AC4BD2C6C48D200DD600D /* CustomDeviceProfileAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDeviceProfileAction.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1061,6 +1080,16 @@
|
||||||
4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSectionSelectorView.swift; sourceTree = "<group>"; };
|
4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedSectionSelectorView.swift; sourceTree = "<group>"; };
|
||||||
4E2AC4D52C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackQualitySettingsView.swift; sourceTree = "<group>"; };
|
4E2AC4D52C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackQualitySettingsView.swift; sourceTree = "<group>"; };
|
||||||
4E2AC4D72C6C4D8D00DD600D /* PlaybackQualitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackQualitySettingsView.swift; sourceTree = "<group>"; };
|
4E2AC4D72C6C4D8D00DD600D /* PlaybackQualitySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackQualitySettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE532CBED3F300DBD886 /* DayOfWeekRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayOfWeekRow.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE542CBED3F300DBD886 /* IntervalRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalRow.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE552CBED3F300DBD886 /* TimeLimitSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeLimitSection.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE562CBED3F300DBD886 /* TimeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeRow.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE572CBED3F300DBD886 /* TriggerTypeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggerTypeRow.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE5A2CBED3F300DBD886 /* AddTaskTriggerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTaskTriggerView.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE632CBED69600DBD886 /* TaskTriggerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTriggerType.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE652CBED8B300DBD886 /* ServerTicks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTicks.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE682CBED95F00DBD886 /* DayOfWeek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayOfWeek.swift; sourceTree = "<group>"; };
|
||||||
|
4E35CE6B2CBEDB7300DBD886 /* TaskState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskState.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>"; };
|
||||||
4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDashboardView.swift; sourceTree = "<group>"; };
|
4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDashboardView.swift; sourceTree = "<group>"; };
|
||||||
4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionsViewModel.swift; sourceTree = "<group>"; };
|
4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionsViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1071,13 +1100,19 @@
|
||||||
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
|
4E73E2A52C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBitrateTestSize.swift; sourceTree = "<group>"; };
|
||||||
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.swift; sourceTree = "<group>"; };
|
4E762AAD2C3A1A95004D1579 /* PlaybackBitrate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaybackBitrate.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>"; };
|
||||||
|
4E90F7592CC72B1F00417C31 /* DetailsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsSection.swift; sourceTree = "<group>"; };
|
||||||
|
4E90F75A2CC72B1F00417C31 /* LastErrorSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastErrorSection.swift; sourceTree = "<group>"; };
|
||||||
|
4E90F75B2CC72B1F00417C31 /* LastRunSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastRunSection.swift; sourceTree = "<group>"; };
|
||||||
|
4E90F75D2CC72B1F00417C31 /* TriggersSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggersSection.swift; sourceTree = "<group>"; };
|
||||||
|
4E90F75F2CC72B1F00417C31 /* TriggerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggerRow.swift; sourceTree = "<group>"; };
|
||||||
|
4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditServerTaskView.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>"; };
|
||||||
4E9A24EC2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDeviceProfileView.swift; sourceTree = "<group>"; };
|
4E9A24EC2C82BAFB0023DA83 /* EditCustomDeviceProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCustomDeviceProfileView.swift; sourceTree = "<group>"; };
|
||||||
4EB1404B2C8E45B1008691F3 /* StreamSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamSection.swift; sourceTree = "<group>"; };
|
4EB1404B2C8E45B1008691F3 /* StreamSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamSection.swift; sourceTree = "<group>"; };
|
||||||
4EB1A8C92C9A765800F43898 /* ActiveSessionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsView.swift; sourceTree = "<group>"; };
|
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>"; };
|
4EB1A8CB2C9B1B9700F43898 /* DestructiveServerTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveServerTask.swift; sourceTree = "<group>"; };
|
||||||
4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionRow.swift; sourceTree = "<group>"; };
|
4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionRow.swift; sourceTree = "<group>"; };
|
||||||
4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInfo.swift; sourceTree = "<group>"; };
|
4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInfo.swift; sourceTree = "<group>"; };
|
||||||
4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronAlertButton.swift; sourceTree = "<group>"; };
|
4EB7B33A2CBDE63F004A342E /* ChevronAlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronAlertButton.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1092,7 +1127,7 @@
|
||||||
4EC6C16A2C92999800FC904B /* TranscodeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeSection.swift; sourceTree = "<group>"; };
|
4EC6C16A2C92999800FC904B /* TranscodeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeSection.swift; sourceTree = "<group>"; };
|
||||||
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeReason.swift; sourceTree = "<group>"; };
|
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeReason.swift; sourceTree = "<group>"; };
|
||||||
4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInfo.swift; sourceTree = "<group>"; };
|
4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInfo.swift; sourceTree = "<group>"; };
|
||||||
4EE141682C8BABDF0045B661 /* ProgressSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressSection.swift; sourceTree = "<group>"; };
|
4EE141682C8BABDF0045B661 /* ActiveSessionProgressSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionProgressSection.swift; sourceTree = "<group>"; };
|
||||||
4EED87462CBF824B002354D2 /* DeviceRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRow.swift; sourceTree = "<group>"; };
|
4EED87462CBF824B002354D2 /* DeviceRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRow.swift; sourceTree = "<group>"; };
|
||||||
4EED87482CBF824B002354D2 /* DevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesView.swift; sourceTree = "<group>"; };
|
4EED87482CBF824B002354D2 /* DevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesView.swift; sourceTree = "<group>"; };
|
||||||
4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesViewModel.swift; sourceTree = "<group>"; };
|
4EED874F2CBF84AD002354D2 /* DevicesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevicesViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1308,6 +1343,7 @@
|
||||||
E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundParallaxHeaderModifier.swift; sourceTree = "<group>"; };
|
E11895B22893844A0042947B /* BackgroundParallaxHeaderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundParallaxHeaderModifier.swift; sourceTree = "<group>"; };
|
||||||
E1194F4D2BEABA9100888DB6 /* NavigationBarCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarCloseButton.swift; sourceTree = "<group>"; };
|
E1194F4D2BEABA9100888DB6 /* NavigationBarCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarCloseButton.swift; sourceTree = "<group>"; };
|
||||||
E1194F4F2BEB1E3000888DB6 /* StoredValues+Temp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoredValues+Temp.swift"; sourceTree = "<group>"; };
|
E1194F4F2BEB1E3000888DB6 /* StoredValues+Temp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoredValues+Temp.swift"; sourceTree = "<group>"; };
|
||||||
|
E11969692CC99EA9001A58BE /* ServerTaskProgressSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskProgressSection.swift; sourceTree = "<group>"; };
|
||||||
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
|
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
|
||||||
E11BDF762B8513B40045C54A /* ItemGenre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemGenre.swift; sourceTree = "<group>"; };
|
E11BDF762B8513B40045C54A /* ItemGenre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemGenre.swift; sourceTree = "<group>"; };
|
||||||
E11BDF792B85529D0045C54A /* SupportedCaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedCaseIterable.swift; sourceTree = "<group>"; };
|
E11BDF792B85529D0045C54A /* SupportedCaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedCaseIterable.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1697,7 +1733,6 @@
|
||||||
E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerManager.swift; sourceTree = "<group>"; };
|
E1EA9F6928F8A79E00BEC442 /* VideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerManager.swift; sourceTree = "<group>"; };
|
||||||
E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedText.swift; sourceTree = "<group>"; };
|
E1EBCB41278BD174009FE6E9 /* TruncatedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedText.swift; sourceTree = "<group>"; };
|
||||||
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = "<group>"; };
|
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemOverviewView.swift; sourceTree = "<group>"; };
|
||||||
E1ED7FD52CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditScheduledTaskView.swift; sourceTree = "<group>"; };
|
|
||||||
E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskObserver.swift; sourceTree = "<group>"; };
|
E1ED7FD72CA8AF7400ACB6E3 /* ServerTaskObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTaskObserver.swift; sourceTree = "<group>"; };
|
||||||
E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStateInfo.swift; sourceTree = "<group>"; };
|
E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStateInfo.swift; sourceTree = "<group>"; };
|
||||||
E1ED7FDD2CAA641F00ACB6E3 /* ListTitleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTitleSection.swift; sourceTree = "<group>"; };
|
E1ED7FDD2CAA641F00ACB6E3 /* ListTitleSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTitleSection.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1881,20 +1916,20 @@
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
4E182C9A2C94991800FBEFD5 /* ScheduledTasksView */ = {
|
4E182C9A2C94991800FBEFD5 /* ServerTasksView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4E182C9D2C94A01600FBEFD5 /* Components */,
|
4E182C9D2C94A01600FBEFD5 /* Components */,
|
||||||
4E182C9B2C94993200FBEFD5 /* ScheduledTasksView.swift */,
|
4E182C9B2C94993200FBEFD5 /* ServerTasksView.swift */,
|
||||||
);
|
);
|
||||||
path = ScheduledTasksView;
|
path = ServerTasksView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
4E182C9D2C94A01600FBEFD5 /* Components */ = {
|
4E182C9D2C94A01600FBEFD5 /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4E182C9E2C94A1E000FBEFD5 /* ScheduledTaskButton.swift */,
|
4EB1A8CB2C9B1B9700F43898 /* DestructiveServerTask.swift */,
|
||||||
4EB1A8CB2C9B1B9700F43898 /* ServerTaskButton.swift */,
|
4E182C9E2C94A1E000FBEFD5 /* ServerTaskRow.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1910,6 +1945,35 @@
|
||||||
path = MediaComponents;
|
path = MediaComponents;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4E35CE592CBED3F300DBD886 /* Components */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4E35CE532CBED3F300DBD886 /* DayOfWeekRow.swift */,
|
||||||
|
4E35CE542CBED3F300DBD886 /* IntervalRow.swift */,
|
||||||
|
4E35CE552CBED3F300DBD886 /* TimeLimitSection.swift */,
|
||||||
|
4E35CE562CBED3F300DBD886 /* TimeRow.swift */,
|
||||||
|
4E35CE572CBED3F300DBD886 /* TriggerTypeRow.swift */,
|
||||||
|
);
|
||||||
|
path = Components;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4E35CE5B2CBED3F300DBD886 /* AddTaskTriggerView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4E35CE592CBED3F300DBD886 /* Components */,
|
||||||
|
4E35CE5A2CBED3F300DBD886 /* AddTaskTriggerView.swift */,
|
||||||
|
);
|
||||||
|
path = AddTaskTriggerView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4E35CE622CBED3FF00DBD886 /* ServerLogsView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E1ED7FDF2CAA685900ACB6E3 /* ServerLogsView.swift */,
|
||||||
|
);
|
||||||
|
path = ServerLogsView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4E3A785D2C3B87A400D33C11 /* PlaybackBitrate */ = {
|
4E3A785D2C3B87A400D33C11 /* PlaybackBitrate */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1922,14 +1986,15 @@
|
||||||
4E63B9F52C8A5BEF00C25378 /* UserDashboardView */ = {
|
4E63B9F52C8A5BEF00C25378 /* UserDashboardView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E1DE64902CC6F06C00E423B6 /* Components */,
|
|
||||||
4E6C27062C8BD09200FD2185 /* ActiveSessionDetailView */,
|
4E6C27062C8BD09200FD2185 /* ActiveSessionDetailView */,
|
||||||
4EB1A8CF2C9B2FA200F43898 /* ActiveSessionsView */,
|
4EB1A8CF2C9B2FA200F43898 /* ActiveSessionsView */,
|
||||||
|
4E35CE5B2CBED3F300DBD886 /* AddTaskTriggerView */,
|
||||||
|
E1DE64902CC6F06C00E423B6 /* Components */,
|
||||||
4E10C80F2CC030B20012CC9F /* DeviceDetailsView */,
|
4E10C80F2CC030B20012CC9F /* DeviceDetailsView */,
|
||||||
4EED87492CBF824B002354D2 /* DevicesView */,
|
4EED87492CBF824B002354D2 /* DevicesView */,
|
||||||
E1ED7FD52CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift */,
|
4E90F7622CC72B1F00417C31 /* EditServerTaskView */,
|
||||||
4E182C9A2C94991800FBEFD5 /* ScheduledTasksView */,
|
4E182C9A2C94991800FBEFD5 /* ServerTasksView */,
|
||||||
E1ED7FDF2CAA685900ACB6E3 /* ServerLogsView.swift */,
|
4E35CE622CBED3FF00DBD886 /* ServerLogsView */,
|
||||||
4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */,
|
4E63B9F42C8A5BEF00C25378 /* UserDashboardView.swift */,
|
||||||
);
|
);
|
||||||
path = UserDashboardView;
|
path = UserDashboardView;
|
||||||
|
@ -1995,6 +2060,36 @@
|
||||||
path = ActiveSessionDetailView;
|
path = ActiveSessionDetailView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4E90F75E2CC72B1F00417C31 /* Sections */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4E90F7592CC72B1F00417C31 /* DetailsSection.swift */,
|
||||||
|
4E90F75A2CC72B1F00417C31 /* LastErrorSection.swift */,
|
||||||
|
4E90F75B2CC72B1F00417C31 /* LastRunSection.swift */,
|
||||||
|
E11969692CC99EA9001A58BE /* ServerTaskProgressSection.swift */,
|
||||||
|
4E90F75D2CC72B1F00417C31 /* TriggersSection.swift */,
|
||||||
|
);
|
||||||
|
path = Sections;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4E90F7602CC72B1F00417C31 /* Components */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4E90F75E2CC72B1F00417C31 /* Sections */,
|
||||||
|
4E90F75F2CC72B1F00417C31 /* TriggerRow.swift */,
|
||||||
|
);
|
||||||
|
path = Components;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4E90F7622CC72B1F00417C31 /* EditServerTaskView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4E90F7602CC72B1F00417C31 /* Components */,
|
||||||
|
4E90F7612CC72B1F00417C31 /* EditServerTaskView.swift */,
|
||||||
|
);
|
||||||
|
path = EditServerTaskView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4E9A24E32C82B4700023DA83 /* CustomDeviceProfileSettingsView */ = {
|
4E9A24E32C82B4700023DA83 /* CustomDeviceProfileSettingsView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2025,8 +2120,8 @@
|
||||||
4EB1A8D02C9B2FB600F43898 /* Components */ = {
|
4EB1A8D02C9B2FB600F43898 /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4EE141682C8BABDF0045B661 /* ActiveSessionProgressSection.swift */,
|
||||||
4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */,
|
4EB1A8CD2C9B2D0100F43898 /* ActiveSessionRow.swift */,
|
||||||
4EE141682C8BABDF0045B661 /* ProgressSection.swift */,
|
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3640,13 +3735,12 @@
|
||||||
E1D37F5B2B9CF02600343D2B /* BaseItemDto */,
|
E1D37F5B2B9CF02600343D2B /* BaseItemDto */,
|
||||||
E1D37F5A2B9CF01F00343D2B /* BaseItemPerson */,
|
E1D37F5A2B9CF01F00343D2B /* BaseItemPerson */,
|
||||||
E1002B632793CEE700E47059 /* ChapterInfo.swift */,
|
E1002B632793CEE700E47059 /* ChapterInfo.swift */,
|
||||||
E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */,
|
|
||||||
E1CB758A2C80F9EC00217C76 /* CodecProfile.swift */,
|
E1CB758A2C80F9EC00217C76 /* CodecProfile.swift */,
|
||||||
|
4E35CE682CBED95F00DBD886 /* DayOfWeek.swift */,
|
||||||
4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */,
|
4E17498D2CC00A2E00DD07D1 /* DeviceInfo.swift */,
|
||||||
4EBE06502C7ED0E1004A6C03 /* DeviceProfile.swift */,
|
4EBE06502C7ED0E1004A6C03 /* DeviceProfile.swift */,
|
||||||
4E12F9152CBE9615006C217E /* DeviceType.swift */,
|
4E12F9152CBE9615006C217E /* DeviceType.swift */,
|
||||||
4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */,
|
4EB4ECE22CBEFC49002FF2FC /* SessionInfo.swift */,
|
||||||
4E0A8FFA2CAF74CD0014B047 /* TaskCompletionStatus.swift */,
|
|
||||||
E1CB75712C80E71800217C76 /* DirectPlayProfile.swift */,
|
E1CB75712C80E71800217C76 /* DirectPlayProfile.swift */,
|
||||||
E1722DB029491C3900CC0239 /* ImageBlurHashes.swift */,
|
E1722DB029491C3900CC0239 /* ImageBlurHashes.swift */,
|
||||||
E1D842902933F87500D1041A /* ItemFields.swift */,
|
E1D842902933F87500D1041A /* ItemFields.swift */,
|
||||||
|
@ -3656,11 +3750,16 @@
|
||||||
E1F5F9B12BA0200500BA5014 /* MediaSourceInfo */,
|
E1F5F9B12BA0200500BA5014 /* MediaSourceInfo */,
|
||||||
E122A9122788EAAD0060FA63 /* MediaStream.swift */,
|
E122A9122788EAAD0060FA63 /* MediaStream.swift */,
|
||||||
E1AD105E26D9ADDD003E4A08 /* NameGuidPair.swift */,
|
E1AD105E26D9ADDD003E4A08 /* NameGuidPair.swift */,
|
||||||
|
E1ED7FDA2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift */,
|
||||||
4E2182E42CAF67EF0094806B /* PlayMethod.swift */,
|
4E2182E42CAF67EF0094806B /* PlayMethod.swift */,
|
||||||
|
4E35CE652CBED8B300DBD886 /* ServerTicks.swift */,
|
||||||
4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */,
|
4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */,
|
||||||
E148128428C15472003B8787 /* SortOrder+ItemSortOrder.swift */,
|
E148128428C15472003B8787 /* SortOrder+ItemSortOrder.swift */,
|
||||||
E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */,
|
E1DA654B28E69B0500592A73 /* SpecialFeatureType.swift */,
|
||||||
E1CB757E2C80F28F00217C76 /* SubtitleProfile.swift */,
|
E1CB757E2C80F28F00217C76 /* SubtitleProfile.swift */,
|
||||||
|
4E0A8FFA2CAF74CD0014B047 /* TaskCompletionStatus.swift */,
|
||||||
|
4E35CE6B2CBEDB7300DBD886 /* TaskState.swift */,
|
||||||
|
4E35CE632CBED69600DBD886 /* TaskTriggerType.swift */,
|
||||||
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */,
|
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */,
|
||||||
E1CB757B2C80F00D00217C76 /* TranscodingProfile.swift */,
|
E1CB757B2C80F00D00217C76 /* TranscodingProfile.swift */,
|
||||||
E18CE0B128A229E70092E7F1 /* UserDto.swift */,
|
E18CE0B128A229E70092E7F1 /* UserDto.swift */,
|
||||||
|
@ -4428,6 +4527,7 @@
|
||||||
E1A42E4F28CBD3E100A14DCB /* HomeErrorView.swift in Sources */,
|
E1A42E4F28CBD3E100A14DCB /* HomeErrorView.swift in Sources */,
|
||||||
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
|
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
|
||||||
E122A9142788EAAD0060FA63 /* MediaStream.swift in Sources */,
|
E122A9142788EAAD0060FA63 /* MediaStream.swift in Sources */,
|
||||||
|
4E35CE6D2CBEDB7600DBD886 /* TaskState.swift in Sources */,
|
||||||
4E2AC4D62C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift in Sources */,
|
4E2AC4D62C6C4CDC00DD600D /* PlaybackQualitySettingsView.swift in Sources */,
|
||||||
E102314E2BCF8A7E009D71FC /* AlternateLayoutView.swift in Sources */,
|
E102314E2BCF8A7E009D71FC /* AlternateLayoutView.swift in Sources */,
|
||||||
E1575E74293E77B5001665B1 /* PanDirectionGestureRecognizer.swift in Sources */,
|
E1575E74293E77B5001665B1 /* PanDirectionGestureRecognizer.swift in Sources */,
|
||||||
|
@ -4628,6 +4728,7 @@
|
||||||
E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */,
|
E18A17F2298C68BB00C22F62 /* MainOverlay.swift in Sources */,
|
||||||
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */,
|
E1763A6A2BF3D177004DF6AB /* PublicUserRow.swift in Sources */,
|
||||||
E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */,
|
E1E6C44B29AED2B70064123F /* HorizontalAlignment.swift in Sources */,
|
||||||
|
4E35CE672CBED8B600DBD886 /* ServerTicks.swift in Sources */,
|
||||||
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */,
|
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */,
|
||||||
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
||||||
E148128628C15475003B8787 /* SortOrder+ItemSortOrder.swift in Sources */,
|
E148128628C15475003B8787 /* SortOrder+ItemSortOrder.swift in Sources */,
|
||||||
|
@ -4686,6 +4787,7 @@
|
||||||
E1153D962BBA3E2F00424D36 /* EpisodeHStack.swift in Sources */,
|
E1153D962BBA3E2F00424D36 /* EpisodeHStack.swift in Sources */,
|
||||||
E193D5512719432400900D82 /* ServerConnectionViewModel.swift in Sources */,
|
E193D5512719432400900D82 /* ServerConnectionViewModel.swift in Sources */,
|
||||||
E1B5861329E32EEF00E45D6E /* Sequence.swift in Sources */,
|
E1B5861329E32EEF00E45D6E /* Sequence.swift in Sources */,
|
||||||
|
4E35CE6A2CBED95F00DBD886 /* DayOfWeek.swift in Sources */,
|
||||||
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
|
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
|
||||||
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
|
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
|
||||||
E113133B28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
E113133B28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
||||||
|
@ -4924,6 +5026,7 @@
|
||||||
E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */,
|
E1BDF31729525F0400CC0294 /* AdvancedActionButton.swift in Sources */,
|
||||||
E1ED91152B95897500802036 /* LatestInLibraryViewModel.swift in Sources */,
|
E1ED91152B95897500802036 /* LatestInLibraryViewModel.swift in Sources */,
|
||||||
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */,
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */,
|
||||||
|
E119696A2CC99EA9001A58BE /* ServerTaskProgressSection.swift in Sources */,
|
||||||
E1BAFE102BE921270069C4D7 /* SwiftfinApp+ValueObservation.swift in Sources */,
|
E1BAFE102BE921270069C4D7 /* SwiftfinApp+ValueObservation.swift in Sources */,
|
||||||
E1ED7FDE2CAA641F00ACB6E3 /* ListTitleSection.swift in Sources */,
|
E1ED7FDE2CAA641F00ACB6E3 /* ListTitleSection.swift in Sources */,
|
||||||
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
|
@ -4932,6 +5035,7 @@
|
||||||
4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */,
|
4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */,
|
||||||
E1DD55372B6EE533007501C0 /* Task.swift in Sources */,
|
E1DD55372B6EE533007501C0 /* Task.swift in Sources */,
|
||||||
E1ED7FE02CAA685900ACB6E3 /* ServerLogsView.swift in Sources */,
|
E1ED7FE02CAA685900ACB6E3 /* ServerLogsView.swift in Sources */,
|
||||||
|
4E35CE6C2CBEDB7600DBD886 /* TaskState.swift in Sources */,
|
||||||
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */,
|
E1194F4E2BEABA9100888DB6 /* NavigationBarCloseButton.swift in Sources */,
|
||||||
E113133428BE988200930F75 /* NavigationBarFilterDrawer.swift in Sources */,
|
E113133428BE988200930F75 /* NavigationBarFilterDrawer.swift in Sources */,
|
||||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
||||||
|
@ -4939,6 +5043,12 @@
|
||||||
E11E0E8C2BF7E76F007676DD /* DataCache.swift in Sources */,
|
E11E0E8C2BF7E76F007676DD /* DataCache.swift in Sources */,
|
||||||
E10231482BCF8A6D009D71FC /* ChannelLibraryViewModel.swift in Sources */,
|
E10231482BCF8A6D009D71FC /* ChannelLibraryViewModel.swift in Sources */,
|
||||||
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
||||||
|
4E90F7642CC72B1F00417C31 /* LastRunSection.swift in Sources */,
|
||||||
|
4E90F7652CC72B1F00417C31 /* EditServerTaskView.swift in Sources */,
|
||||||
|
4E90F7662CC72B1F00417C31 /* LastErrorSection.swift in Sources */,
|
||||||
|
4E90F7672CC72B1F00417C31 /* TriggerRow.swift in Sources */,
|
||||||
|
4E90F7682CC72B1F00417C31 /* TriggersSection.swift in Sources */,
|
||||||
|
4E90F76A2CC72B1F00417C31 /* DetailsSection.swift in Sources */,
|
||||||
E129428828F0831F00796AC6 /* SplitTimestamp.swift in Sources */,
|
E129428828F0831F00796AC6 /* SplitTimestamp.swift in Sources */,
|
||||||
C46DD8E72A8FA77F0046A504 /* LiveBottomBarView.swift in Sources */,
|
C46DD8E72A8FA77F0046A504 /* LiveBottomBarView.swift in Sources */,
|
||||||
E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */,
|
E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */,
|
||||||
|
@ -4998,7 +5108,7 @@
|
||||||
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
|
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
|
||||||
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
||||||
E1549666296CA2EF00C4EF88 /* SwiftfinNotifications.swift in Sources */,
|
E1549666296CA2EF00C4EF88 /* SwiftfinNotifications.swift in Sources */,
|
||||||
4EE141692C8BABDF0045B661 /* ProgressSection.swift in Sources */,
|
4EE141692C8BABDF0045B661 /* ActiveSessionProgressSection.swift in Sources */,
|
||||||
E1A1528528FD191A00600579 /* TextPair.swift in Sources */,
|
E1A1528528FD191A00600579 /* TextPair.swift in Sources */,
|
||||||
6334175D287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift in Sources */,
|
6334175D287DE0D0000603CE /* QuickConnectAuthorizeViewModel.swift in Sources */,
|
||||||
4EED874A2CBF824B002354D2 /* DeviceRow.swift in Sources */,
|
4EED874A2CBF824B002354D2 /* DeviceRow.swift in Sources */,
|
||||||
|
@ -5044,9 +5154,15 @@
|
||||||
E18A8E8028D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift in Sources */,
|
E18A8E8028D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift in Sources */,
|
||||||
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */,
|
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */,
|
||||||
E18E01E2288747230022598C /* EpisodeItemView.swift in Sources */,
|
E18E01E2288747230022598C /* EpisodeItemView.swift in Sources */,
|
||||||
|
4E35CE5C2CBED3F300DBD886 /* TimeRow.swift in Sources */,
|
||||||
|
4E35CE5D2CBED3F300DBD886 /* TriggerTypeRow.swift in Sources */,
|
||||||
|
4E35CE5E2CBED3F300DBD886 /* AddTaskTriggerView.swift in Sources */,
|
||||||
|
4E35CE5F2CBED3F300DBD886 /* IntervalRow.swift in Sources */,
|
||||||
|
4E35CE602CBED3F300DBD886 /* DayOfWeekRow.swift in Sources */,
|
||||||
|
4E35CE612CBED3F300DBD886 /* TimeLimitSection.swift in Sources */,
|
||||||
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
||||||
E14EA1602BF6FF8900DE757A /* UserProfileImagePicker.swift in Sources */,
|
E14EA1602BF6FF8900DE757A /* UserProfileImagePicker.swift in Sources */,
|
||||||
4E182C9C2C94993200FBEFD5 /* ScheduledTasksView.swift in Sources */,
|
4E182C9C2C94993200FBEFD5 /* ServerTasksView.swift in Sources */,
|
||||||
E1D4BF812719D22800A11E64 /* AppAppearance.swift in Sources */,
|
E1D4BF812719D22800A11E64 /* AppAppearance.swift in Sources */,
|
||||||
E1BDF2EF29522A5900CC0294 /* AudioActionButton.swift in Sources */,
|
E1BDF2EF29522A5900CC0294 /* AudioActionButton.swift in Sources */,
|
||||||
E174120F29AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */,
|
E174120F29AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */,
|
||||||
|
@ -5057,6 +5173,7 @@
|
||||||
E10B1ECA2BD9AF8200A92EAF /* SwiftfinStore+V1.swift in Sources */,
|
E10B1ECA2BD9AF8200A92EAF /* SwiftfinStore+V1.swift in Sources */,
|
||||||
E1AA331D2782541500F6439C /* PrimaryButton.swift in Sources */,
|
E1AA331D2782541500F6439C /* PrimaryButton.swift in Sources */,
|
||||||
4E2AC4D92C6C4D9400DD600D /* PlaybackQualitySettingsView.swift in Sources */,
|
4E2AC4D92C6C4D9400DD600D /* PlaybackQualitySettingsView.swift in Sources */,
|
||||||
|
4E35CE692CBED95F00DBD886 /* DayOfWeek.swift in Sources */,
|
||||||
E18E01E3288747230022598C /* CompactPortraitScrollView.swift in Sources */,
|
E18E01E3288747230022598C /* CompactPortraitScrollView.swift in Sources */,
|
||||||
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */,
|
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */,
|
||||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
||||||
|
@ -5076,6 +5193,7 @@
|
||||||
E113133A28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
E113133A28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
||||||
E1E6C44C29AED2BE0064123F /* HorizontalAlignment.swift in Sources */,
|
E1E6C44C29AED2BE0064123F /* HorizontalAlignment.swift in Sources */,
|
||||||
E1A1528D28FD23AC00600579 /* VideoPlayerSettingsCoordinator.swift in Sources */,
|
E1A1528D28FD23AC00600579 /* VideoPlayerSettingsCoordinator.swift in Sources */,
|
||||||
|
4E35CE642CBED69600DBD886 /* TaskTriggerType.swift in Sources */,
|
||||||
E18E01EE288747230022598C /* AboutView.swift in Sources */,
|
E18E01EE288747230022598C /* AboutView.swift in Sources */,
|
||||||
62E632E0267D30CA0063E547 /* ItemLibraryViewModel.swift in Sources */,
|
62E632E0267D30CA0063E547 /* ItemLibraryViewModel.swift in Sources */,
|
||||||
E1B33EB028EA890D0073B0FD /* Equatable.swift in Sources */,
|
E1B33EB028EA890D0073B0FD /* Equatable.swift in Sources */,
|
||||||
|
@ -5135,7 +5253,7 @@
|
||||||
E1401CA72938140300E8B599 /* PrimaryAppIcon.swift in Sources */,
|
E1401CA72938140300E8B599 /* PrimaryAppIcon.swift in Sources */,
|
||||||
E1937A3E288F0D3D00CB80AA /* UIScreen.swift in Sources */,
|
E1937A3E288F0D3D00CB80AA /* UIScreen.swift in Sources */,
|
||||||
E10B1EBE2BD9AD5C00A92EAF /* V1ServerModel.swift in Sources */,
|
E10B1EBE2BD9AD5C00A92EAF /* V1ServerModel.swift in Sources */,
|
||||||
4EB1A8CC2C9B1BA200F43898 /* ServerTaskButton.swift in Sources */,
|
4EB1A8CC2C9B1BA200F43898 /* DestructiveServerTask.swift in Sources */,
|
||||||
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
|
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
|
||||||
E10B1EB62BD98C6600A92EAF /* AddUserRow.swift in Sources */,
|
E10B1EB62BD98C6600A92EAF /* AddUserRow.swift in Sources */,
|
||||||
E1CB75802C80F28F00217C76 /* SubtitleProfile.swift in Sources */,
|
E1CB75802C80F28F00217C76 /* SubtitleProfile.swift in Sources */,
|
||||||
|
@ -5154,7 +5272,6 @@
|
||||||
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
||||||
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
||||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */,
|
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||||
E1ED7FD62CA8A7FD00ACB6E3 /* EditScheduledTaskView.swift in Sources */,
|
|
||||||
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
||||||
E1545BD82BDC55C300D9578F /* ResetUserPasswordView.swift in Sources */,
|
E1545BD82BDC55C300D9578F /* ResetUserPasswordView.swift in Sources */,
|
||||||
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
||||||
|
@ -5175,7 +5292,7 @@
|
||||||
E18E01F1288747230022598C /* PlayButton.swift in Sources */,
|
E18E01F1288747230022598C /* PlayButton.swift in Sources */,
|
||||||
E129429028F0BDC300796AC6 /* TimeStampType.swift in Sources */,
|
E129429028F0BDC300796AC6 /* TimeStampType.swift in Sources */,
|
||||||
E1F5CF092CB0A04500607465 /* Text.swift in Sources */,
|
E1F5CF092CB0A04500607465 /* Text.swift in Sources */,
|
||||||
4E182C9F2C94A1E000FBEFD5 /* ScheduledTaskButton.swift in Sources */,
|
4E182C9F2C94A1E000FBEFD5 /* ServerTaskRow.swift in Sources */,
|
||||||
E1B490442967E26300D3EDCE /* PersistentLogHandler.swift in Sources */,
|
E1B490442967E26300D3EDCE /* PersistentLogHandler.swift in Sources */,
|
||||||
E1CB756F2C80E66700217C76 /* CommaStringBuilder.swift in Sources */,
|
E1CB756F2C80E66700217C76 /* CommaStringBuilder.swift in Sources */,
|
||||||
E19D41AC2BF288110082B8B2 /* ServerCheckView.swift in Sources */,
|
E19D41AC2BF288110082B8B2 /* ServerCheckView.swift in Sources */,
|
||||||
|
@ -5221,6 +5338,7 @@
|
||||||
E1A3E4C72BB74E50005C59F8 /* EpisodeCard.swift in Sources */,
|
E1A3E4C72BB74E50005C59F8 /* EpisodeCard.swift in Sources */,
|
||||||
E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */,
|
E1153DB42BBA80FB00424D36 /* EmptyCard.swift in Sources */,
|
||||||
4E63B9FA2C8A5BEF00C25378 /* UserDashboardView.swift in Sources */,
|
4E63B9FA2C8A5BEF00C25378 /* UserDashboardView.swift in Sources */,
|
||||||
|
4E35CE662CBED8B600DBD886 /* ServerTicks.swift in Sources */,
|
||||||
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
|
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
|
||||||
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
|
4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */,
|
||||||
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
|
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
struct AddTaskTriggerView: View {
|
||||||
|
|
||||||
|
@Environment(\.dismiss)
|
||||||
|
private var dismiss
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var observer: ServerTaskObserver
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isPresentingNotSaved = false
|
||||||
|
@State
|
||||||
|
private var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
static let defaultTimeOfDayTicks = 0
|
||||||
|
static let defaultDayOfWeek: DayOfWeek = .sunday
|
||||||
|
static let defaultIntervalTicks = 36_000_000_000
|
||||||
|
private let emptyTaskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
private var hasUnsavedChanges: Bool {
|
||||||
|
taskTriggerInfo != emptyTaskTriggerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isDuplicate: Bool {
|
||||||
|
observer.task.triggers?.contains(where: { $0 == taskTriggerInfo }) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(observer: ServerTaskObserver) {
|
||||||
|
self.observer = observer
|
||||||
|
|
||||||
|
let newTrigger = TaskTriggerInfo(
|
||||||
|
dayOfWeek: nil,
|
||||||
|
intervalTicks: nil,
|
||||||
|
maxRuntimeTicks: nil,
|
||||||
|
timeOfDayTicks: nil,
|
||||||
|
type: TaskTriggerType.startup.rawValue
|
||||||
|
)
|
||||||
|
|
||||||
|
_taskTriggerInfo = State(initialValue: newTrigger)
|
||||||
|
self.emptyTaskTriggerInfo = newTrigger
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View for TaskTriggerType.daily
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var dailyView: some View {
|
||||||
|
TimeRow(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View for TaskTriggerType.weekly
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var weeklyView: some View {
|
||||||
|
DayOfWeekRow(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
TimeRow(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View for TaskTriggerType.interval
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var intervalView: some View {
|
||||||
|
IntervalRow(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
TriggerTypeRow(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
|
||||||
|
if let taskType = taskTriggerInfo.type {
|
||||||
|
if taskType == TaskTriggerType.daily.rawValue {
|
||||||
|
dailyView
|
||||||
|
} else if taskType == TaskTriggerType.weekly.rawValue {
|
||||||
|
weeklyView
|
||||||
|
} else if taskType == TaskTriggerType.interval.rawValue {
|
||||||
|
intervalView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} footer: {
|
||||||
|
if isDuplicate {
|
||||||
|
Label(L10n.triggerAlreadyExists, systemImage: "exclamationmark.circle.fill")
|
||||||
|
.labelStyle(.sectionFooterWithImage(imageStyle: .orange))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeLimitSection(taskTriggerInfo: $taskTriggerInfo)
|
||||||
|
}
|
||||||
|
.animation(.linear(duration: 0.2), value: isDuplicate)
|
||||||
|
.animation(.linear(duration: 0.2), value: taskTriggerInfo.type)
|
||||||
|
.interactiveDismissDisabled(true)
|
||||||
|
.navigationTitle(L10n.addTrigger)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navigationBarCloseButton {
|
||||||
|
if hasUnsavedChanges {
|
||||||
|
isPresentingNotSaved = true
|
||||||
|
} else {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.topBarTrailing {
|
||||||
|
Button(L10n.save) {
|
||||||
|
|
||||||
|
UIDevice.impact(.light)
|
||||||
|
|
||||||
|
observer.send(.addTrigger(taskTriggerInfo))
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
.buttonStyle(.toolbarPill)
|
||||||
|
.disabled(isDuplicate)
|
||||||
|
}
|
||||||
|
.alert(L10n.unsavedChangesMessage, isPresented: $isPresentingNotSaved) {
|
||||||
|
Button(L10n.close, role: .destructive) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
Button(L10n.cancel, role: .cancel) {
|
||||||
|
isPresentingNotSaved = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// 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 AddTaskTriggerView {
|
||||||
|
|
||||||
|
struct DayOfWeekRow: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Picker(
|
||||||
|
L10n.dayOfWeek,
|
||||||
|
selection: Binding(
|
||||||
|
get: { taskTriggerInfo.dayOfWeek ?? defaultDayOfWeek },
|
||||||
|
set: { taskTriggerInfo.dayOfWeek = $0 }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ForEach(DayOfWeek.allCases, id: \.self) { day in
|
||||||
|
Text(day.displayTitle ?? L10n.unknown)
|
||||||
|
.tag(day)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// 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 AddTaskTriggerView {
|
||||||
|
|
||||||
|
struct IntervalRow: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var tempInterval: Int?
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(taskTriggerInfo: Binding<TaskTriggerInfo>) {
|
||||||
|
self._taskTriggerInfo = taskTriggerInfo
|
||||||
|
_tempInterval = State(initialValue: Int(ServerTicks(taskTriggerInfo.wrappedValue.intervalTicks).minutes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ChevronAlertButton(
|
||||||
|
L10n.every,
|
||||||
|
subtitle: ServerTicks(
|
||||||
|
taskTriggerInfo.intervalTicks
|
||||||
|
).seconds.formatted(.hourMinute),
|
||||||
|
description: L10n.taskTriggerInterval
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
L10n.minutes,
|
||||||
|
value: $tempInterval,
|
||||||
|
format: .number
|
||||||
|
)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
} onSave: {
|
||||||
|
if tempInterval != nil && tempInterval != 0 {
|
||||||
|
taskTriggerInfo.intervalTicks = ServerTicks(minutes: tempInterval).ticks
|
||||||
|
} else {
|
||||||
|
taskTriggerInfo.intervalTicks = nil
|
||||||
|
}
|
||||||
|
} onCancel: {
|
||||||
|
if let intervalTicks = taskTriggerInfo.intervalTicks {
|
||||||
|
tempInterval = Int(ServerTicks(intervalTicks).minutes)
|
||||||
|
} else {
|
||||||
|
tempInterval = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// 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 AddTaskTriggerView {
|
||||||
|
|
||||||
|
struct TimeLimitSection: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var tempTimeLimit: Int?
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(taskTriggerInfo: Binding<TaskTriggerInfo>) {
|
||||||
|
self._taskTriggerInfo = taskTriggerInfo
|
||||||
|
_tempTimeLimit = State(initialValue: Int(ServerTicks(taskTriggerInfo.wrappedValue.maxRuntimeTicks).hours))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section {
|
||||||
|
ChevronAlertButton(
|
||||||
|
L10n.timeLimit,
|
||||||
|
subtitle: subtitleString,
|
||||||
|
description: L10n.taskTriggerTimeLimit
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
L10n.hours,
|
||||||
|
value: $tempTimeLimit,
|
||||||
|
format: .number
|
||||||
|
)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
} onSave: {
|
||||||
|
if tempTimeLimit != nil && tempTimeLimit != 0 {
|
||||||
|
taskTriggerInfo.maxRuntimeTicks = ServerTicks(hours: tempTimeLimit).ticks
|
||||||
|
} else {
|
||||||
|
taskTriggerInfo.maxRuntimeTicks = nil
|
||||||
|
}
|
||||||
|
} onCancel: {
|
||||||
|
if let maxRuntimeTicks = taskTriggerInfo.maxRuntimeTicks {
|
||||||
|
tempTimeLimit = Int(ServerTicks(maxRuntimeTicks).hours)
|
||||||
|
} else {
|
||||||
|
tempTimeLimit = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Create Subtitle String
|
||||||
|
|
||||||
|
private var subtitleString: String {
|
||||||
|
if let maxRuntimeTicks = taskTriggerInfo.maxRuntimeTicks {
|
||||||
|
ServerTicks(maxRuntimeTicks).seconds.formatted(.hourMinute)
|
||||||
|
} else {
|
||||||
|
L10n.none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// 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 AddTaskTriggerView {
|
||||||
|
|
||||||
|
struct TimeRow: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
DatePicker(
|
||||||
|
L10n.time,
|
||||||
|
selection: Binding<Date>(
|
||||||
|
get: {
|
||||||
|
ServerTicks(
|
||||||
|
taskTriggerInfo.timeOfDayTicks ?? defaultTimeOfDayTicks
|
||||||
|
).date
|
||||||
|
},
|
||||||
|
set: { date in
|
||||||
|
taskTriggerInfo.timeOfDayTicks = ServerTicks(date: date).ticks
|
||||||
|
}
|
||||||
|
),
|
||||||
|
displayedComponents: .hourAndMinute
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
//
|
||||||
|
// 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 AddTaskTriggerView {
|
||||||
|
|
||||||
|
struct TriggerTypeRow: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Picker(
|
||||||
|
L10n.type,
|
||||||
|
selection: Binding<TaskTriggerType?>(
|
||||||
|
get: {
|
||||||
|
if let t = taskTriggerInfo.type {
|
||||||
|
return TaskTriggerType(rawValue: t)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: { newValue in
|
||||||
|
if taskTriggerInfo.type != newValue?.rawValue {
|
||||||
|
resetValuesForNewType(newType: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ForEach(TaskTriggerType.allCases, id: \.self) { type in
|
||||||
|
Text(type.displayTitle)
|
||||||
|
.tag(type as TaskTriggerType?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetValuesForNewType(newType: TaskTriggerType?) {
|
||||||
|
taskTriggerInfo.type = newType?.rawValue
|
||||||
|
let maxRuntimeTicks = taskTriggerInfo.maxRuntimeTicks
|
||||||
|
|
||||||
|
switch newType {
|
||||||
|
case .daily:
|
||||||
|
taskTriggerInfo.timeOfDayTicks = defaultTimeOfDayTicks
|
||||||
|
taskTriggerInfo.dayOfWeek = nil
|
||||||
|
taskTriggerInfo.intervalTicks = nil
|
||||||
|
case .weekly:
|
||||||
|
taskTriggerInfo.timeOfDayTicks = defaultTimeOfDayTicks
|
||||||
|
taskTriggerInfo.dayOfWeek = defaultDayOfWeek
|
||||||
|
taskTriggerInfo.intervalTicks = nil
|
||||||
|
case .interval:
|
||||||
|
taskTriggerInfo.intervalTicks = defaultIntervalTicks
|
||||||
|
taskTriggerInfo.timeOfDayTicks = nil
|
||||||
|
taskTriggerInfo.dayOfWeek = nil
|
||||||
|
case .startup:
|
||||||
|
taskTriggerInfo.timeOfDayTicks = nil
|
||||||
|
taskTriggerInfo.dayOfWeek = nil
|
||||||
|
taskTriggerInfo.intervalTicks = nil
|
||||||
|
default:
|
||||||
|
taskTriggerInfo.timeOfDayTicks = nil
|
||||||
|
taskTriggerInfo.dayOfWeek = nil
|
||||||
|
taskTriggerInfo.intervalTicks = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
taskTriggerInfo.maxRuntimeTicks = maxRuntimeTicks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,95 +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: last run details
|
|
||||||
// - result, show error if available
|
|
||||||
// TODO: observe running status
|
|
||||||
// - stop
|
|
||||||
// - run
|
|
||||||
// - progress
|
|
||||||
// TODO: triggers
|
|
||||||
|
|
||||||
struct EditScheduledTaskView: View {
|
|
||||||
|
|
||||||
@CurrentDate
|
|
||||||
private var currentDate: Date
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var observer: ServerTaskObserver
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List {
|
|
||||||
|
|
||||||
ListTitleSection(
|
|
||||||
observer.task.name ?? L10n.unknown,
|
|
||||||
description: observer.task.description
|
|
||||||
)
|
|
||||||
|
|
||||||
if let category = observer.task.category {
|
|
||||||
TextPairView(
|
|
||||||
leading: L10n.category,
|
|
||||||
trailing: category
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lastEndTime = observer.task.lastExecutionResult?.endTimeUtc {
|
|
||||||
TextPairView(
|
|
||||||
L10n.lastRun,
|
|
||||||
value: Text("\(lastEndTime, format: .relative(presentation: .numeric, unitsStyle: .narrow))")
|
|
||||||
)
|
|
||||||
.id(currentDate)
|
|
||||||
.monospacedDigit()
|
|
||||||
|
|
||||||
if let lastStartTime = observer.task.lastExecutionResult?.startTimeUtc {
|
|
||||||
TextPairView(
|
|
||||||
L10n.runtime,
|
|
||||||
value: Text(
|
|
||||||
"\(lastStartTime ..< lastEndTime, format: .components(style: .narrow))"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle(L10n.task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove after view done
|
|
||||||
#Preview {
|
|
||||||
NavigationView {
|
|
||||||
EditScheduledTaskView(
|
|
||||||
observer: .init(
|
|
||||||
task: TaskInfo(
|
|
||||||
category: "test",
|
|
||||||
currentProgressPercentage: nil,
|
|
||||||
description: "A test task",
|
|
||||||
id: "123",
|
|
||||||
isHidden: false,
|
|
||||||
key: "123",
|
|
||||||
lastExecutionResult: TaskResult(
|
|
||||||
endTimeUtc: Date(timeIntervalSinceNow: -10),
|
|
||||||
errorMessage: nil,
|
|
||||||
id: nil,
|
|
||||||
key: nil,
|
|
||||||
longErrorMessage: nil,
|
|
||||||
name: nil,
|
|
||||||
startTimeUtc: Date(timeIntervalSinceNow: -30),
|
|
||||||
status: .completed
|
|
||||||
),
|
|
||||||
name: "Test",
|
|
||||||
state: .running,
|
|
||||||
triggers: nil
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension EditServerTaskView {
|
||||||
|
|
||||||
|
struct DetailsSection: View {
|
||||||
|
|
||||||
|
let category: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section(L10n.details) {
|
||||||
|
TextPairView(leading: L10n.category, trailing: category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// 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 EditServerTaskView {
|
||||||
|
|
||||||
|
struct LastErrorSection: View {
|
||||||
|
|
||||||
|
let message: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section(L10n.errorDetails) {
|
||||||
|
Text(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// 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 EditServerTaskView {
|
||||||
|
|
||||||
|
struct LastRunSection: View {
|
||||||
|
|
||||||
|
@CurrentDate
|
||||||
|
private var currentDate: Date
|
||||||
|
|
||||||
|
let status: TaskCompletionStatus
|
||||||
|
let endTime: Date
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section(L10n.lastRun) {
|
||||||
|
|
||||||
|
TextPairView(
|
||||||
|
leading: L10n.status,
|
||||||
|
trailing: status.displayTitle
|
||||||
|
)
|
||||||
|
|
||||||
|
TextPairView(
|
||||||
|
L10n.executed,
|
||||||
|
value: Text("\(endTime, format: .relative(presentation: .numeric, unitsStyle: .narrow))")
|
||||||
|
)
|
||||||
|
.id(currentDate)
|
||||||
|
.monospacedDigit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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 EditServerTaskView {
|
||||||
|
|
||||||
|
struct ProgressSection: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var observer: ServerTaskObserver
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if observer.task.state == .running || observer.task.state == .cancelling {
|
||||||
|
Section(L10n.progress) {
|
||||||
|
if let status = observer.task.state {
|
||||||
|
TextPairView(
|
||||||
|
leading: L10n.status,
|
||||||
|
trailing: status.displayTitle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentProgressPercentage = observer.task.currentProgressPercentage {
|
||||||
|
TextPairView(
|
||||||
|
L10n.progress,
|
||||||
|
value: Text("\(currentProgressPercentage / 100, format: .percent.precision(.fractionLength(1)))")
|
||||||
|
)
|
||||||
|
.monospacedDigit()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
observer.send(.stop)
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Text(L10n.stop)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "stop.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(L10n.run) {
|
||||||
|
observer.send(.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// 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 EditServerTaskView {
|
||||||
|
|
||||||
|
struct TriggersSection: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: SettingsCoordinator.Router
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var observer: ServerTaskObserver
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isPresentingDeleteConfirmation: Bool = false
|
||||||
|
@State
|
||||||
|
private var selectedTrigger: TaskTriggerInfo?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Section(L10n.triggers) {
|
||||||
|
if let triggers = observer.task.triggers, triggers.isNotEmpty {
|
||||||
|
ForEach(triggers, id: \.self) { trigger in
|
||||||
|
TriggerRow(taskTriggerInfo: trigger)
|
||||||
|
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||||
|
Button {
|
||||||
|
selectedTrigger = trigger
|
||||||
|
isPresentingDeleteConfirmation = true
|
||||||
|
} label: {
|
||||||
|
Label(L10n.delete, systemImage: "trash")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(L10n.addTrigger) {
|
||||||
|
router.route(to: \.addServerTaskTrigger, observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.confirmationDialog(
|
||||||
|
L10n.deleteTrigger,
|
||||||
|
isPresented: $isPresentingDeleteConfirmation,
|
||||||
|
titleVisibility: .visible
|
||||||
|
) {
|
||||||
|
Button(L10n.cancel, role: .cancel) {}
|
||||||
|
|
||||||
|
Button(L10n.delete, role: .destructive) {
|
||||||
|
if let selectedTrigger {
|
||||||
|
observer.send(.removeTrigger(selectedTrigger))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text(L10n.deleteTriggerConfirmationMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
//
|
||||||
|
// 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 Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension EditServerTaskView {
|
||||||
|
|
||||||
|
struct TriggerRow: View {
|
||||||
|
|
||||||
|
let taskTriggerInfo: TaskTriggerInfo
|
||||||
|
|
||||||
|
// TODO: remove after `TaskTriggerType` is provided by SDK
|
||||||
|
|
||||||
|
private var taskTriggerType: TaskTriggerType {
|
||||||
|
if let t = taskTriggerInfo.type, let type = TaskTriggerType(rawValue: t) {
|
||||||
|
return type
|
||||||
|
} else {
|
||||||
|
return .startup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
|
Text(triggerDisplayText)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if let maxRuntimeTicks = taskTriggerInfo.maxRuntimeTicks {
|
||||||
|
Text(
|
||||||
|
L10n.timeLimitLabelWithValue(
|
||||||
|
ServerTicks(maxRuntimeTicks)
|
||||||
|
.seconds.formatted(.hourMinute)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(L10n.noRuntimeLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
Image(systemName: taskTriggerType.systemImage)
|
||||||
|
.backport
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Trigger Display Text
|
||||||
|
|
||||||
|
private var triggerDisplayText: String {
|
||||||
|
switch taskTriggerType {
|
||||||
|
case .daily:
|
||||||
|
if let timeOfDayTicks = taskTriggerInfo.timeOfDayTicks {
|
||||||
|
return L10n.itemAtItem(
|
||||||
|
taskTriggerType.displayTitle,
|
||||||
|
ServerTicks(timeOfDayTicks)
|
||||||
|
.date.formatted(date: .omitted, time: .shortened)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case .weekly:
|
||||||
|
if let dayOfWeek = taskTriggerInfo.dayOfWeek,
|
||||||
|
let timeOfDayTicks = taskTriggerInfo.timeOfDayTicks
|
||||||
|
{
|
||||||
|
return L10n.itemAtItem(
|
||||||
|
dayOfWeek.rawValue.capitalized,
|
||||||
|
ServerTicks(timeOfDayTicks)
|
||||||
|
.date.formatted(date: .omitted, time: .shortened)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case .interval:
|
||||||
|
if let intervalTicks = taskTriggerInfo.intervalTicks {
|
||||||
|
return L10n.everyInterval(
|
||||||
|
ServerTicks(intervalTicks)
|
||||||
|
.seconds.formatted(.hourMinute)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case .startup:
|
||||||
|
return taskTriggerType.displayTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
return L10n.unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
//
|
||||||
|
// 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 JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EditServerTaskView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: SettingsCoordinator.Router
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var observer: ServerTaskObserver
|
||||||
|
|
||||||
|
// MARK: - State Variables
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isPresentingDeleteConfirmation = false
|
||||||
|
@State
|
||||||
|
private var isPresentingEventAlert = false
|
||||||
|
@State
|
||||||
|
private var error: JellyfinAPIError?
|
||||||
|
@State
|
||||||
|
private var selectedTrigger: TaskTriggerInfo?
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
ListTitleSection(
|
||||||
|
observer.task.name ?? L10n.unknown,
|
||||||
|
description: observer.task.description
|
||||||
|
)
|
||||||
|
|
||||||
|
ProgressSection(observer: observer)
|
||||||
|
|
||||||
|
if let category = observer.task.category {
|
||||||
|
DetailsSection(category: category)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lastExecutionResult = observer.task.lastExecutionResult {
|
||||||
|
if let status = lastExecutionResult.status, let endTime = lastExecutionResult.endTimeUtc {
|
||||||
|
LastRunSection(status: status, endTime: endTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let errorMessage = lastExecutionResult.errorMessage {
|
||||||
|
LastErrorSection(message: errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggersSection(observer: observer)
|
||||||
|
}
|
||||||
|
.animation(.linear(duration: 0.2), value: observer.state)
|
||||||
|
.animation(.linear(duration: 0.1), value: observer.task.state)
|
||||||
|
.animation(.linear(duration: 0.1), value: observer.task.triggers)
|
||||||
|
.navigationTitle(L10n.task)
|
||||||
|
.topBarTrailing {
|
||||||
|
|
||||||
|
if observer.backgroundStates.contains(.updatingTriggers) {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let triggers = observer.task.triggers, triggers.isNotEmpty {
|
||||||
|
Button(L10n.add) {
|
||||||
|
UIDevice.impact(.light)
|
||||||
|
router.route(to: \.addServerTaskTrigger, observer)
|
||||||
|
}
|
||||||
|
.buttonStyle(.toolbarPill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onReceive(observer.events) { event in
|
||||||
|
switch event {
|
||||||
|
case let .error(eventError):
|
||||||
|
error = eventError
|
||||||
|
isPresentingEventAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.alert(
|
||||||
|
L10n.error,
|
||||||
|
isPresented: $isPresentingEventAlert,
|
||||||
|
presenting: error
|
||||||
|
) { _ in
|
||||||
|
|
||||||
|
} message: { error in
|
||||||
|
Text(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,12 @@ struct ServerLogsView: View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var contentView: some View {
|
private var contentView: some View {
|
||||||
List {
|
List {
|
||||||
|
ListTitleSection(
|
||||||
|
L10n.logs,
|
||||||
|
description: L10n.logsDescription
|
||||||
|
) {
|
||||||
|
UIApplication.shared.open(URL(string: "https://jellyfin.org/docs/general/administration/troubleshooting")!)
|
||||||
|
}
|
||||||
ForEach(viewModel.logs, id: \.self) { log in
|
ForEach(viewModel.logs, id: \.self) { log in
|
||||||
Button {
|
Button {
|
||||||
let request = Paths.getLogFile(name: log.name!)
|
let request = Paths.getLogFile(name: log.name!)
|
|
@ -10,38 +10,43 @@ import Defaults
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ScheduledTasksView {
|
extension ServerTasksView {
|
||||||
|
|
||||||
struct ServerTaskButton: View {
|
struct DestructiveServerTask: View {
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isPresented: Bool = false
|
||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
let systemImage: String
|
let systemName: String
|
||||||
let warningMessage: String
|
let message: String
|
||||||
let isPresented: Binding<Bool>
|
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
isPresented.wrappedValue = true
|
isPresented = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(title)
|
Text(title)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image(systemName: systemImage)
|
Image(systemName: systemName)
|
||||||
|
.backport
|
||||||
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
title,
|
title,
|
||||||
isPresented: isPresented,
|
isPresented: $isPresented,
|
||||||
titleVisibility: .hidden
|
titleVisibility: .visible
|
||||||
) {
|
) {
|
||||||
Button(title, role: .destructive, action: action)
|
Button(title, role: .destructive, action: action)
|
||||||
} message: {
|
} message: {
|
||||||
Text(warningMessage)
|
Text(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,9 +10,9 @@ import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ScheduledTasksView {
|
extension ServerTasksView {
|
||||||
|
|
||||||
struct ScheduledTaskButton: View {
|
struct ServerTaskRow: View {
|
||||||
|
|
||||||
@CurrentDate
|
@CurrentDate
|
||||||
private var currentDate: Date
|
private var currentDate: Date
|
||||||
|
@ -41,22 +41,6 @@ extension ScheduledTasksView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Task Status Section
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var statusView: some View {
|
|
||||||
switch observer.state {
|
|
||||||
case .running:
|
|
||||||
ProgressView(value: (observer.task.currentProgressPercentage ?? 0) / 100)
|
|
||||||
.progressViewStyle(.gauge(systemImage: "stop.fill"))
|
|
||||||
.transition(.opacity.combined(with: .scale).animation(.bouncy))
|
|
||||||
default:
|
|
||||||
Image(systemName: "play.fill")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.transition(.opacity.combined(with: .scale).animation(.bouncy))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Task Status View
|
// MARK: - Task Status View
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -97,8 +81,16 @@ extension ScheduledTasksView {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
statusView
|
if observer.state == .running {
|
||||||
.frame(width: 25, height: 25)
|
ProgressView(value: (observer.task.currentProgressPercentage ?? 0) / 100)
|
||||||
|
.progressViewStyle(.gauge)
|
||||||
|
.transition(.opacity.combined(with: .scale).animation(.bouncy))
|
||||||
|
.frame(width: 25, height: 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.font(.body.weight(.regular))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.1), value: observer.state)
|
.animation(.linear(duration: 0.1), value: observer.state)
|
||||||
|
@ -122,7 +114,7 @@ extension ScheduledTasksView {
|
||||||
.disabled(observer.task.state == .cancelling)
|
.disabled(observer.task.state == .cancelling)
|
||||||
|
|
||||||
Button(L10n.edit) {
|
Button(L10n.edit) {
|
||||||
router.route(to: \.editScheduledTask, observer)
|
router.route(to: \.editServerTask, observer)
|
||||||
}
|
}
|
||||||
} message: {
|
} message: {
|
||||||
if let description = observer.task.description {
|
if let description = observer.task.description {
|
|
@ -12,18 +12,13 @@ import SwiftUI
|
||||||
|
|
||||||
// TODO: refactor after socket implementation
|
// TODO: refactor after socket implementation
|
||||||
|
|
||||||
struct ScheduledTasksView: View {
|
struct ServerTasksView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: SettingsCoordinator.Router
|
private var router: SettingsCoordinator.Router
|
||||||
|
|
||||||
@State
|
|
||||||
private var isPresentingRestartConfirmation = false
|
|
||||||
@State
|
|
||||||
private var isPresentingShutdownConfirmation = false
|
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
private var viewModel = ScheduledTasksViewModel()
|
private var viewModel = ServerTasksViewModel()
|
||||||
|
|
||||||
private let timer = Timer.publish(every: 5, on: .main, in: .common)
|
private let timer = Timer.publish(every: 5, on: .main, in: .common)
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
|
@ -32,20 +27,18 @@ struct ScheduledTasksView: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var serverFunctions: some View {
|
private var serverFunctions: some View {
|
||||||
ServerTaskButton(
|
DestructiveServerTask(
|
||||||
title: L10n.restartServer,
|
title: L10n.restartServer,
|
||||||
systemImage: "arrow.clockwise",
|
systemName: "arrow.clockwise",
|
||||||
warningMessage: L10n.restartWarning,
|
message: L10n.restartWarning
|
||||||
isPresented: $isPresentingRestartConfirmation
|
|
||||||
) {
|
) {
|
||||||
viewModel.send(.restartApplication)
|
viewModel.send(.restartApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerTaskButton(
|
DestructiveServerTask(
|
||||||
title: L10n.shutdownServer,
|
title: L10n.shutdownServer,
|
||||||
systemImage: "power",
|
systemName: "power",
|
||||||
warningMessage: L10n.shutdownWarning,
|
message: L10n.shutdownWarning
|
||||||
isPresented: $isPresentingShutdownConfirmation
|
|
||||||
) {
|
) {
|
||||||
viewModel.send(.shutdownApplication)
|
viewModel.send(.shutdownApplication)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +64,7 @@ struct ScheduledTasksView: View {
|
||||||
ForEach(viewModel.tasks.keys, id: \.self) { category in
|
ForEach(viewModel.tasks.keys, id: \.self) { category in
|
||||||
Section(category) {
|
Section(category) {
|
||||||
ForEach(viewModel.tasks[category] ?? []) { task in
|
ForEach(viewModel.tasks[category] ?? []) { task in
|
||||||
ScheduledTaskButton(observer: task)
|
ServerTaskRow(observer: task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -734,7 +734,7 @@
|
||||||
"save" = "Save";
|
"save" = "Save";
|
||||||
|
|
||||||
/* Time Interval Help Text - Days */
|
/* Time Interval Help Text - Days */
|
||||||
"days" = "days";
|
"days" = "Days";
|
||||||
|
|
||||||
/* Section Title for Column Configuration */
|
/* Section Title for Column Configuration */
|
||||||
"columns" = "Columns";
|
"columns" = "Columns";
|
||||||
|
@ -843,3 +843,183 @@
|
||||||
// Selects all available devices
|
// Selects all available devices
|
||||||
// Used to select all items in selection mode
|
// Used to select all items in selection mode
|
||||||
"selectAll" = "Select All";
|
"selectAll" = "Select All";
|
||||||
|
|
||||||
|
// Logs Description - View
|
||||||
|
// Access the Jellyfin server logs for troubleshooting and monitoring purposes
|
||||||
|
// Describes the logs view in settings
|
||||||
|
"logsDescription" = "Access the Jellyfin server logs for troubleshooting and monitoring purposes.";
|
||||||
|
|
||||||
|
/* Indicate a type */
|
||||||
|
"type" = "Type";
|
||||||
|
|
||||||
|
// Day of Week - Section Label
|
||||||
|
// Specifies the day of the week for the trigger
|
||||||
|
// Label for the day of week section
|
||||||
|
"dayOfWeek" = "Day of Week";
|
||||||
|
|
||||||
|
// Time - Section Label
|
||||||
|
// Specifies the time for the trigger
|
||||||
|
// Label for the time section
|
||||||
|
"time" = "Time";
|
||||||
|
|
||||||
|
// Daily - Description
|
||||||
|
// Recurring trigger that runs daily
|
||||||
|
// Describes the daily trigger type
|
||||||
|
"daily" = "Daily";
|
||||||
|
|
||||||
|
// Interval - Description
|
||||||
|
// Recurring trigger based on time intervals
|
||||||
|
// Describes the interval trigger type
|
||||||
|
"interval" = "Interval";
|
||||||
|
|
||||||
|
// Weekly - Description
|
||||||
|
// Recurring trigger that runs weekly
|
||||||
|
// Describes the weekly trigger type
|
||||||
|
"weekly" = "Weekly";
|
||||||
|
|
||||||
|
// On Application Startup - Description
|
||||||
|
// Trigger that runs when the application starts
|
||||||
|
// Describes the startup trigger type
|
||||||
|
"onApplicationStartup" = "On application startup";
|
||||||
|
|
||||||
|
// Task Trigger Time Limit - Section Description
|
||||||
|
// Sets the maximum runtime (in hours) for this task trigger
|
||||||
|
// Description for the task trigger time limit section
|
||||||
|
"taskTriggerTimeLimit" = "Sets the maximum runtime (in hours) for this task trigger.";
|
||||||
|
|
||||||
|
// Task Trigger Interval - Section Description
|
||||||
|
// Sets the duration (in minutes) in between task triggers
|
||||||
|
// Description for the task trigger interval section
|
||||||
|
"taskTriggerInterval" = "Sets the duration (in minutes) in between task triggers.";
|
||||||
|
|
||||||
|
// Every - Label
|
||||||
|
// Used to select interval frequency
|
||||||
|
// Label for selecting interval frequency
|
||||||
|
"every" = "Every";
|
||||||
|
|
||||||
|
// Time Limit with Unit - Label
|
||||||
|
// Specifies time limit along with the unit
|
||||||
|
// Time limit label with descriptive unit
|
||||||
|
"timeLimitWithUnit" = "Time Limit (%@)";
|
||||||
|
|
||||||
|
// Time Limit - Section Label
|
||||||
|
// Specifies the time limit for the task
|
||||||
|
// Label for the time limit section
|
||||||
|
"timeLimit" = "Time Limit";
|
||||||
|
|
||||||
|
// Hours - Input Field Placeholder
|
||||||
|
// Placeholder for inputting hours
|
||||||
|
// Input field placeholder for hours
|
||||||
|
"hours" = "Hours";
|
||||||
|
|
||||||
|
// Minutes - Input Field Placeholder
|
||||||
|
// Placeholder for inputting minutes
|
||||||
|
// Input field placeholder for minutes
|
||||||
|
"minutes" = "Minutes";
|
||||||
|
|
||||||
|
// Add Trigger - Title
|
||||||
|
// Title for adding a new task trigger
|
||||||
|
// Title for adding a new task trigger
|
||||||
|
"addTrigger" = "Add trigger";
|
||||||
|
|
||||||
|
// Save - Button Label
|
||||||
|
// Button to save the current task trigger
|
||||||
|
// Save button label
|
||||||
|
"save" = "Save";
|
||||||
|
|
||||||
|
// Changes Not Saved - Alert Title
|
||||||
|
// Title for unsaved changes alert
|
||||||
|
// Title for the unsaved changes alert
|
||||||
|
"changesNotSaved" = "Changes not saved";
|
||||||
|
|
||||||
|
// Discard Changes - Button Label
|
||||||
|
// Button to discard unsaved changes
|
||||||
|
// Button label for discarding unsaved changes
|
||||||
|
"discardChanges" = "Discard Changes";
|
||||||
|
|
||||||
|
// Unsaved Changes Message - Alert
|
||||||
|
// Message for unsaved changes alert
|
||||||
|
// Alert message for unsaved changes
|
||||||
|
"unsavedChangesMessage" = "You have unsaved changes. Are you sure you want to discard them?";
|
||||||
|
|
||||||
|
// Delete Trigger - Confirmation Dialog Title
|
||||||
|
// Title for the delete trigger confirmation dialog
|
||||||
|
// Confirmation dialog title for deleting a trigger
|
||||||
|
"deleteTrigger" = "Delete Trigger";
|
||||||
|
|
||||||
|
// Delete Trigger Confirmation - Message
|
||||||
|
// Message for deleting a trigger confirmation dialog
|
||||||
|
// Confirmation dialog message for deleting a trigger
|
||||||
|
"deleteTriggerConfirmationMessage" = "Are you sure you want to delete this trigger? This action cannot be undone.";
|
||||||
|
|
||||||
|
// Item At Item - Label
|
||||||
|
// Used to describe an item at another item
|
||||||
|
// Label for something at something else
|
||||||
|
"itemAtItem" = "%1$@ at %2$@";
|
||||||
|
|
||||||
|
// Every Interval - Label
|
||||||
|
// Describes an interval trigger with recurring time
|
||||||
|
// Label for interval trigger with recurring time
|
||||||
|
"everyInterval" = "Every %1$@";
|
||||||
|
|
||||||
|
// Time Limit Label with Value - Label
|
||||||
|
// Describes time limit with a value
|
||||||
|
// Label for time limit with value
|
||||||
|
"timeLimitLabelWithValue" = "Time limit: %1$@";
|
||||||
|
|
||||||
|
// Add - Button Label
|
||||||
|
// Button to add a new item
|
||||||
|
// Button label for adding a new item
|
||||||
|
"add" = "Add";
|
||||||
|
|
||||||
|
// Idle - Task State
|
||||||
|
// Describes the task state as idle
|
||||||
|
// Localized text for task state 'Idle'
|
||||||
|
"idle" = "Idle";
|
||||||
|
|
||||||
|
// Status - Section Title
|
||||||
|
// Title for the status section
|
||||||
|
// Section title for a status section
|
||||||
|
"status" = "Status";
|
||||||
|
|
||||||
|
// Error Details - Section Title
|
||||||
|
// Title for the error details section
|
||||||
|
// Section title for a task error details
|
||||||
|
"errorDetails" = "Error Details";
|
||||||
|
|
||||||
|
// Details - Section Title
|
||||||
|
// Title for the details section
|
||||||
|
// Section title for any details section
|
||||||
|
"details" = "Details";
|
||||||
|
|
||||||
|
// Triggers - Section Header
|
||||||
|
// Header for the scheduled task triggers section
|
||||||
|
// Section header for scheduled task triggers
|
||||||
|
"triggers" = "Triggers";
|
||||||
|
|
||||||
|
// Executed - Section Title
|
||||||
|
// Title for the task execution date section
|
||||||
|
// Section title for a task execution date
|
||||||
|
"executed" = "Executed";
|
||||||
|
|
||||||
|
// No Runtime Limit - Label
|
||||||
|
// Describes a task with no runtime limit
|
||||||
|
// No task trigger runtime limit set
|
||||||
|
"noRuntimeLimit" = "No runtime limit";
|
||||||
|
|
||||||
|
// API Key Created - Success Message
|
||||||
|
// A new Access Token was successfully created for the specified application
|
||||||
|
// Appears in success alert when a new API key is created
|
||||||
|
"serverTriggerCreated" = "A new trigger was created for '%1$@'.";
|
||||||
|
|
||||||
|
// API Key Deleted - Success Message
|
||||||
|
// The Access Token was successfully deleted for the specified application
|
||||||
|
// Appears in success alert when an API key is deleted
|
||||||
|
"serverTriggerDeleted" = "The selected trigger was deleted from '%1$@'.";
|
||||||
|
|
||||||
|
// Save - Button
|
||||||
|
// Confirms that something completed successfully or without error
|
||||||
|
// Appears in the views with eventful to indicate a task did not fail
|
||||||
|
"success" = "Success";
|
||||||
|
|
||||||
|
"triggerAlreadyExists" = "Trigger already exists";
|
||||||
|
|
Loading…
Reference in New Issue