diff --git a/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift b/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift index eb6b6a01..1da6bb5f 100644 --- a/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift +++ b/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift @@ -91,22 +91,18 @@ struct LandscapeItemElement: View { ) .shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0) .shadow(radius: focused ? 10.0 : 0, y: focused ? 10.0 : 0) - if focused { - if inSeasonView ?? false { - Text("\(item.getEpisodeLocator() ?? "") • \(item.name ?? "")") - .font(.callout) - .fontWeight(.semibold) - .lineLimit(1) - .frame(width: 445) - } else { - Text(item.type == "Episode" ? "\(item.seriesName ?? "") • \(item.getEpisodeLocator() ?? "")" : item.name ?? "") - .font(.callout) - .fontWeight(.semibold) - .lineLimit(1) - .frame(width: 445) - } + if inSeasonView ?? false { + Text("\(item.getEpisodeLocator() ?? "") • \(item.name ?? "")") + .font(.callout) + .fontWeight(.semibold) + .lineLimit(1) + .frame(width: 445) } else { - Spacer().frame(height: 25) + Text(item.type == "Episode" ? "\(item.seriesName ?? "") • \(item.getEpisodeLocator() ?? "")" : item.name ?? "") + .font(.callout) + .fontWeight(.semibold) + .lineLimit(1) + .frame(width: 445) } } .onChange(of: envFocused) { envFocus in diff --git a/JellyfinPlayer tvOS/Components/LiveTVChannelItemElement.swift b/JellyfinPlayer tvOS/Components/LiveTVChannelItemElement.swift deleted file mode 100644 index fe83123c..00000000 --- a/JellyfinPlayer tvOS/Components/LiveTVChannelItemElement.swift +++ /dev/null @@ -1,96 +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 2021 Aiden Vigue & Jellyfin Contributors - */ - - -import SwiftUI -import JellyfinAPI - -struct LiveTVChannelItemElement: View { - @Environment(\.isFocused) var envFocused: Bool - @State var focused: Bool = false - - var channel: BaseItemDto - var program: BaseItemDto? - - var dateFormatter: DateFormatter { - let df = DateFormatter() - df.dateFormat = "h:mm" - return df - } - - var body: some View { - VStack { - HStack { - Spacer() - Text(channel.number ?? "") - .font(.footnote) - .frame(alignment: .trailing) - }.frame(alignment: .top) - ImageView(src: channel.getPrimaryImage(maxWidth: 125)) - .frame(width: 125, alignment: .center) - .offset(x: 0, y: -32) - Text(channel.name ?? "?") - .font(.footnote) - .lineLimit(1) - .frame(alignment: .center) - if let currentProgram = program { - Text(currentProgram.name ?? "") - .font(.body) - .lineLimit(1) - .foregroundColor(.green) - } - - if let currentProgram = program, - let startDate = currentProgram.startDate, - let endDate = currentProgram.endDate { - let start = startDate.timeIntervalSinceReferenceDate - let end = endDate.timeIntervalSinceReferenceDate - let now = Date().timeIntervalSinceReferenceDate - let length = end - start - let progress = now - start - let progPercent = progress / length - VStack { - HStack { - Text(dateFormatter.string(from: startDate)) - .font(.footnote) - .lineLimit(1) - .frame(alignment: .leading) - - Spacer() - - Text(dateFormatter.string(from: endDate)) - .font(.footnote) - .lineLimit(1) - .frame(alignment: .trailing) - } - GeometryReader { gp in - ZStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 6) - .fill(Color.gray) - .opacity(0.4) - .frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12) - RoundedRectangle(cornerRadius: 6) - .fill(Color(red: 172/255, green: 92/255, blue: 195/255)) - .frame(width: CGFloat(progPercent * gp.size.width), height: 12) - } - } - } - } - } - .padding() - .background(Color.clear) - .border(focused ? Color.blue : Color.clear, width: 4) - .onChange(of: envFocused) { envFocus in - withAnimation(.linear(duration: 0.15)) { - self.focused = envFocus - } - } - .scaleEffect(focused ? 1.1 : 1) - } -} diff --git a/JellyfinPlayer tvOS/Views/LiveTVChannelsView.swift b/JellyfinPlayer tvOS/Views/LiveTVChannelsView.swift index d915543d..5599ddb5 100644 --- a/JellyfinPlayer tvOS/Views/LiveTVChannelsView.swift +++ b/JellyfinPlayer tvOS/Views/LiveTVChannelsView.swift @@ -8,6 +8,7 @@ */ import Foundation +import JellyfinAPI import SwiftUI import SwiftUICollection @@ -51,7 +52,13 @@ struct LiveTVChannelsView: View { Button { self.router.route(to: \.videoPlayer, channel) } label: { - LiveTVChannelItemElement(channel: channel, program: item.program) + LiveTVChannelItemElement( + channel: channel, + program: item.program, + startString: item.program?.getLiveStartTimeString(formatter: viewModel.timeFormatter) ?? " ", + endString: item.program?.getLiveEndTimeString(formatter: viewModel.timeFormatter) ?? " ", + progressPercent: item.program?.getLiveProgressPercentage() ?? 0 + ) } .buttonStyle(PlainNavigationLinkButtonStyle()) } diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index ec581680..a3852209 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -233,6 +233,9 @@ C40CD928271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */; }; C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */; }; C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; }; + C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */; }; + C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */; }; + C4AE2C3327498DBE00AE13CF /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; }; C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; }; C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; }; C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */; }; @@ -572,6 +575,8 @@ C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLibrariesCoordinator.swift; sourceTree = ""; }; C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesViewModel.swift; sourceTree = ""; }; C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesView.swift; sourceTree = ""; }; + C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVHomeView.swift; sourceTree = ""; }; + C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = ""; }; C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesCoordinator.swift; sourceTree = ""; }; C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesViewModel.swift; sourceTree = ""; }; C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesView.swift; sourceTree = ""; }; @@ -869,7 +874,6 @@ isa = PBXGroup; children = ( 531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */, - C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */, 53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */, 53116A18268B947A003024C9 /* PlainLinkButton.swift */, @@ -1256,6 +1260,8 @@ 625CB56E2678C23300530A6E /* HomeView.swift */, E14F7D0A26DB3714007C3AE6 /* ItemView */, 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */, + C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */, + C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */, 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */, 6213388F265F83A900A81A2A /* LibraryListView.swift */, 53EE24E5265060780068F029 /* LibrarySearchView.swift */, @@ -1351,6 +1357,7 @@ E1AD105326D96F5A003E4A08 /* Views */ = { isa = PBXGroup; children = ( + C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, 621338B22660A07800A81A2A /* LazyView.swift */, @@ -1962,6 +1969,7 @@ 53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */, E19169CE272514760085832A /* HTTPScheme.swift in Sources */, 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */, + C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, 0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */, @@ -1972,6 +1980,7 @@ 53892770263C25230035E14B /* NextUpView.swift in Sources */, 6264E88C273850380081A12A /* Strings.swift in Sources */, C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, + C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */, 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, @@ -1995,6 +2004,7 @@ 6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */, 62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */, 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */, + C4AE2C3327498DBE00AE13CF /* LiveTVChannelItemElement.swift in Sources */, E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */, E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */, C4BE07792726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */, @@ -2241,7 +2251,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JM7WWM3V8C; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; @@ -2271,7 +2281,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JM7WWM3V8C; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; diff --git a/JellyfinPlayer.xcodeproj/xcshareddata/xcschemes/JellyfinPlayer tvOS.xcscheme b/JellyfinPlayer.xcodeproj/xcshareddata/xcschemes/JellyfinPlayer tvOS.xcscheme new file mode 100644 index 00000000..490b03f0 --- /dev/null +++ b/JellyfinPlayer.xcodeproj/xcshareddata/xcschemes/JellyfinPlayer tvOS.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JellyfinPlayer/Views/LiveTVHomeView.swift b/JellyfinPlayer/Views/LiveTVHomeView.swift new file mode 100644 index 00000000..bc7ded6f --- /dev/null +++ b/JellyfinPlayer/Views/LiveTVHomeView.swift @@ -0,0 +1,15 @@ +/* JellyfinPlayer/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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Stinsen +import SwiftUI + +struct LiveTVHomeView: View { + var body: some View { + Text("Coming Soon") + } +} diff --git a/JellyfinPlayer/Views/LiveTVProgramsView.swift b/JellyfinPlayer/Views/LiveTVProgramsView.swift new file mode 100644 index 00000000..9cb25845 --- /dev/null +++ b/JellyfinPlayer/Views/LiveTVProgramsView.swift @@ -0,0 +1,15 @@ +/* JellyfinPlayer/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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Stinsen +import SwiftUI + +struct LiveTVProgramsView: View { + var body: some View { + Text("Coming Soon") + } +} diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 2f27801d..70937001 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -170,7 +170,34 @@ public extension BaseItemDto { return "\(String(progminutes))m" } } - + + func getLiveStartTimeString(formatter: DateFormatter) -> String { + if let startDate = self.startDate { + return formatter.string(from: startDate) + } + return " " + } + + func getLiveEndTimeString(formatter: DateFormatter) -> String { + if let endDate = self.endDate { + return formatter.string(from: endDate) + } + return " " + } + + func getLiveProgressPercentage() -> Double { + if let startDate = self.startDate, + let endDate = self.endDate { + let start = startDate.timeIntervalSinceReferenceDate + let end = endDate.timeIntervalSinceReferenceDate + let now = Date().timeIntervalSinceReferenceDate + let length = end - start + let progress = now - start + return progress / length + } + return 0 + } + // MARK: ItemType enum ItemType: String { diff --git a/Shared/ViewModels/LiveTVChannelsViewModel.swift b/Shared/ViewModels/LiveTVChannelsViewModel.swift index f90e573e..e20df2ee 100644 --- a/Shared/ViewModels/LiveTVChannelsViewModel.swift +++ b/Shared/ViewModels/LiveTVChannelsViewModel.swift @@ -37,9 +37,16 @@ final class LiveTVChannelsViewModel: ViewModel { } } @Published var rows = [LiveTVChannelRow]() + private var programs = [BaseItemDto]() private var channelProgramsList = [BaseItemDto: [BaseItemDto]]() + var timeFormatter: DateFormatter { + let df = DateFormatter() + df.dateFormat = "h:mm" + return df + } + override init() { super.init() @@ -91,9 +98,6 @@ final class LiveTVChannelsViewModel: ViewModel { let minEndDate = Date.now.addComponentsToDate(hours: -1) let maxStartDate = minEndDate.addComponentsToDate(hours: 6) - NSLog("*** maxStartDate: \(maxStartDate)") - NSLog("*** minEndDate: \(minEndDate)") - let getProgramsDto = GetProgramsDto( channelIds: channelIds, userId: SessionManager.main.currentLogin.user.id, @@ -123,11 +127,7 @@ final class LiveTVChannelsViewModel: ViewModel { private func processChannelPrograms() -> [LiveTVChannelProgram] { var channelPrograms = [LiveTVChannelProgram]() let now = Date() - let df = DateFormatter() - df.dateFormat = "MM/dd h:mm ZZZ" - NSLog("begin processing programs") for channel in self.channels { - NSLog("\n\(channel.name)") let prgs = self.programs.filter { item in item.channelId == channel.id } @@ -135,15 +135,6 @@ final class LiveTVChannelsViewModel: ViewModel { var currentPrg: BaseItemDto? for prg in prgs { - var startString = "" - var endString = "" - if let start = prg.startDate { - startString = df.string(from: start) - } - if let end = prg.endDate { - endString = df.string(from: end) - } - NSLog("\(prg.name) - \(startString) to \(endString)") if let startDate = prg.startDate, let endDate = prg.endDate, now.timeIntervalSinceReferenceDate > startDate.timeIntervalSinceReferenceDate && @@ -154,7 +145,6 @@ final class LiveTVChannelsViewModel: ViewModel { channelPrograms.append(LiveTVChannelProgram(channel: channel, program: currentPrg)) } - NSLog("finished processing programs") return channelPrograms } } diff --git a/Shared/Views/LiveTVChannelItemElement.swift b/Shared/Views/LiveTVChannelItemElement.swift new file mode 100644 index 00000000..2f4e2126 --- /dev/null +++ b/Shared/Views/LiveTVChannelItemElement.swift @@ -0,0 +1,80 @@ +// + /* + * 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 2021 Aiden Vigue & Jellyfin Contributors + */ + + +import SwiftUI +import JellyfinAPI + +struct LiveTVChannelItemElement: View { + @Environment(\.isFocused) var envFocused: Bool + @State var focused: Bool = false + + var channel: BaseItemDto + var program: BaseItemDto? + var startString = " " + var endString = " " + var progressPercent = Double(0) + + var body: some View { + VStack { + HStack { + Spacer() + Text(channel.number ?? "") + .font(.footnote) + .frame(alignment: .trailing) + }.frame(alignment: .top) + ImageView(src: channel.getPrimaryImage(maxWidth: 125)) + .frame(width: 125, alignment: .center) + .offset(x: 0, y: -32) + Text(channel.name ?? "?") + .font(.footnote) + .lineLimit(1) + .frame(alignment: .center) + Text(program?.name ?? "N/A") + .font(.body) + .lineLimit(1) + .foregroundColor(.green) + VStack { + HStack { + Text(startString) + .font(.footnote) + .lineLimit(1) + .frame(alignment: .leading) + + Spacer() + + Text(endString) + .font(.footnote) + .lineLimit(1) + .frame(alignment: .trailing) + } + GeometryReader { gp in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 6) + .fill(Color.gray) + .opacity(0.4) + .frame(minWidth: 100, maxWidth: .infinity, minHeight: 12, maxHeight: 12) + RoundedRectangle(cornerRadius: 6) + .fill(Color(red: 172/255, green: 92/255, blue: 195/255)) + .frame(width: CGFloat(progressPercent * gp.size.width), height: 12) + } + } + } + } + .padding() + .background(Color.clear) + .border(focused ? Color.blue : Color.clear, width: 4) + .onChange(of: envFocused) { envFocus in + withAnimation(.linear(duration: 0.15)) { + self.focused = envFocus + } + } + .scaleEffect(focused ? 1.1 : 1) + } +}