From 1772728cb8163d536618e54391a4d1cdf580c5b3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 11 Jun 2021 00:50:10 +0200 Subject: [PATCH] Popular videos, playing from mp4 --- .gitignore | 2 + .swiftlint.yml | 14 ++ Apple TV/Info.plist | 8 + Model/DataProvider.swift | 14 ++ Model/PopularVideosProvider.swift | 18 +++ Model/Video.swift | 39 +++++ Model/VideoDetailsProvider.swift | 25 ++++ Pearvidious.xcodeproj/project.pbxproj | 141 +++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 34 +++++ .../xcschemes/xcschememanagement.plist | 21 +++ Shared/ContentView.swift | 34 +++-- Shared/PearvidiousApp.swift | 12 +- Shared/PlayerView.swift | 61 ++++++++ Shared/VideoThumbnailView.swift | 48 ++++++ Tests Apple TV/PearvidiousUITests.swift | 1 - Tests iOS/Tests_iOS.swift | 1 - Tests macOS/Tests_macOS.swift | 1 - 17 files changed, 454 insertions(+), 20 deletions(-) create mode 100644 .gitignore create mode 100644 .swiftlint.yml create mode 100644 Apple TV/Info.plist create mode 100644 Model/DataProvider.swift create mode 100644 Model/PopularVideosProvider.swift create mode 100644 Model/Video.swift create mode 100644 Model/VideoDetailsProvider.swift create mode 100644 Pearvidious.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Shared/PlayerView.swift create mode 100644 Shared/VideoThumbnailView.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3e6af795 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# SwiftLint Remote Config Cache +.swiftlint/RemoteConfigCache \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..416af209 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,14 @@ +parent_config: https://raw.githubusercontent.com/sindresorhus/swiftlint-config/main/.swiftlint.yml + +excluded: + - .build + +identifier_name: + excluded: + - db + - id + - vm + +type_name: + excluded: + - VM diff --git a/Apple TV/Info.plist b/Apple TV/Info.plist new file mode 100644 index 00000000..cc25efe1 --- /dev/null +++ b/Apple TV/Info.plist @@ -0,0 +1,8 @@ + + + + + LSRequiresIPhoneOS + + + diff --git a/Model/DataProvider.swift b/Model/DataProvider.swift new file mode 100644 index 00000000..9c33cede --- /dev/null +++ b/Model/DataProvider.swift @@ -0,0 +1,14 @@ +import Alamofire +import Foundation + +class DataProvider: ObservableObject { + static let instance = "https://invidious.home.arekf.net" + + static func request(_ path: String) -> DataRequest { + return AF.request(apiURLString(path)) + } + + static func apiURLString(_ path: String) -> String { + "\(instance)/api/v1/\(path)" + } +} diff --git a/Model/PopularVideosProvider.swift b/Model/PopularVideosProvider.swift new file mode 100644 index 00000000..56c42a8f --- /dev/null +++ b/Model/PopularVideosProvider.swift @@ -0,0 +1,18 @@ +import Alamofire +import Foundation +import SwiftyJSON + +final class PopluarVideosProvider: DataProvider { + @Published var videos = [Video]() + + func load() { + DataProvider.request("popular").responseJSON { response in + switch response.result { + case let .success(value): + self.videos = JSON(value).arrayValue.map { Video($0) } + case let .failure(error): + print(error) + } + } + } +} diff --git a/Model/Video.swift b/Model/Video.swift new file mode 100644 index 00000000..d7b8590c --- /dev/null +++ b/Model/Video.swift @@ -0,0 +1,39 @@ +import Alamofire +import Foundation +import SwiftyJSON + +class Video: Identifiable, ObservableObject { + let id: String + var title: String + var thumbnailURL: URL + var author: String + + @Published var url: URL? + @Published var error: Bool = false + + init(id: String, title: String, thumbnailURL: URL, author: String) { + self.id = id + self.title = title + self.thumbnailURL = thumbnailURL + self.author = author + } + + init(_ json: JSON) { + id = json["videoId"].stringValue + title = json["title"].stringValue + thumbnailURL = json["videoThumbnails"][0]["url"].url! + author = json["author"].stringValue + url = formatStreamURL(from: json["formatStreams"].arrayValue) + } + + func formatStreamURL(from streams: [JSON]) -> URL? { + if streams.isEmpty { + error = true + return nil + } + + let stream = streams.last! + + return stream["url"].url + } +} diff --git a/Model/VideoDetailsProvider.swift b/Model/VideoDetailsProvider.swift new file mode 100644 index 00000000..a5c2aca6 --- /dev/null +++ b/Model/VideoDetailsProvider.swift @@ -0,0 +1,25 @@ +import Alamofire +import Foundation +import SwiftyJSON + +final class VideoDetailsProvider: DataProvider { + @Published var video: Video? + + var id: String + + init(_ id: String) { + self.id = id + super.init() + } + + func load() { + DataProvider.request("videos/\(id)").responseJSON { response in + switch response.result { + case let .success(value): + self.video = Video(JSON(value)) + case let .failure(error): + print(error) + } + } + } +} diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 99495299..32f853d8 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -19,6 +19,29 @@ 37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; }; 37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; }; 37D4B1812671653A00C925CA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* ContentView.swift */; }; + 37D4B1832671681B00C925CA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; }; + 37D4B1842671684E00C925CA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; }; + 37D4B1852671684F00C925CA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1822671681B00C925CA /* PlayerView.swift */; }; + 37D4B1862671691600C925CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37D4B0C42671614800C925CA /* Assets.xcassets */; }; + 37D4B18C26717B3800C925CA /* VideoThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */; }; + 37D4B18D26717B3800C925CA /* VideoThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */; }; + 37D4B18E26717B3800C925CA /* VideoThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */; }; + 37D4B19126717C6900C925CA /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B19026717C6900C925CA /* Alamofire */; }; + 37D4B19326717CE100C925CA /* PopularVideosProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19226717CE100C925CA /* PopularVideosProvider.swift */; }; + 37D4B19426717CE100C925CA /* PopularVideosProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19226717CE100C925CA /* PopularVideosProvider.swift */; }; + 37D4B19526717CE100C925CA /* PopularVideosProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19226717CE100C925CA /* PopularVideosProvider.swift */; }; + 37D4B19726717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; }; + 37D4B19826717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; }; + 37D4B19926717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; }; + 37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B19C2671817900C925CA /* SwiftyJSON */; }; + 37D4B1AB2672580400C925CA /* URLImage in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B1AA2672580400C925CA /* URLImage */; }; + 37D4B1AD2672580400C925CA /* URLImageStore in Frameworks */ = {isa = PBXBuildFile; productRef = 37D4B1AC2672580400C925CA /* URLImageStore */; }; + 37D4B1B02672A01000C925CA /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1AF2672A01000C925CA /* DataProvider.swift */; }; + 37D4B1B12672A01000C925CA /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1AF2672A01000C925CA /* DataProvider.swift */; }; + 37D4B1B22672A01000C925CA /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1AF2672A01000C925CA /* DataProvider.swift */; }; + 37D4B1B42672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; }; + 37D4B1B52672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; }; + 37D4B1B62672A30700C925CA /* VideoDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -59,6 +82,13 @@ 37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37D4B171267164B000C925CA /* Tests Apple TV.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Apple TV.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 37D4B175267164B000C925CA /* PearvidiousUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearvidiousUITests.swift; sourceTree = ""; }; + 37D4B1822671681B00C925CA /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; + 37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoThumbnailView.swift; sourceTree = ""; }; + 37D4B19226717CE100C925CA /* PopularVideosProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularVideosProvider.swift; sourceTree = ""; }; + 37D4B19626717E1500C925CA /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = ""; }; + 37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 37D4B1AF2672A01000C925CA /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; + 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsProvider.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,6 +124,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 37D4B1AD2672580400C925CA /* URLImageStore in Frameworks */, + 37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */, + 37D4B1AB2672580400C925CA /* URLImage in Frameworks */, + 37D4B19126717C6900C925CA /* Alamofire in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -110,6 +144,7 @@ 37D4B0BC2671614700C925CA = { isa = PBXGroup; children = ( + 37D4B1B72672CFE300C925CA /* Model */, 37D4B0C12671614700C925CA /* Shared */, 37D4B159267164AE00C925CA /* Apple TV */, 37D4B174267164B000C925CA /* Tests Apple TV */, @@ -122,8 +157,10 @@ 37D4B0C12671614700C925CA /* Shared */ = { isa = PBXGroup; children = ( - 37D4B0C22671614700C925CA /* PearvidiousApp.swift */, 37D4B0C32671614700C925CA /* ContentView.swift */, + 37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */, + 37D4B1822671681B00C925CA /* PlayerView.swift */, + 37D4B0C22671614700C925CA /* PearvidiousApp.swift */, 37D4B0C42671614800C925CA /* Assets.xcassets */, ); path = Shared; @@ -161,6 +198,7 @@ 37D4B159267164AE00C925CA /* Apple TV */ = { isa = PBXGroup; children = ( + 37D4B1AE26729DEB00C925CA /* Info.plist */, 37D4B15E267164AF00C925CA /* Assets.xcassets */, ); path = "Apple TV"; @@ -174,6 +212,17 @@ path = "Tests Apple TV"; sourceTree = ""; }; + 37D4B1B72672CFE300C925CA /* Model */ = { + isa = PBXGroup; + children = ( + 37D4B19626717E1500C925CA /* Video.swift */, + 37D4B19226717CE100C925CA /* PopularVideosProvider.swift */, + 37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */, + 37D4B1AF2672A01000C925CA /* DataProvider.swift */, + ); + path = Model; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -260,6 +309,12 @@ dependencies = ( ); name = "Pearvidious (Apple TV)"; + packageProductDependencies = ( + 37D4B19026717C6900C925CA /* Alamofire */, + 37D4B19C2671817900C925CA /* SwiftyJSON */, + 37D4B1AA2672580400C925CA /* URLImage */, + 37D4B1AC2672580400C925CA /* URLImageStore */, + ); productName = Pearvidious; productReference = 37D4B158267164AE00C925CA /* Pearvidious (Apple TV).app */; productType = "com.apple.product-type.application"; @@ -324,6 +379,11 @@ Base, ); mainGroup = 37D4B0BC2671614700C925CA; + packageReferences = ( + 37D4B18F26717C6900C925CA /* XCRemoteSwiftPackageReference "Alamofire" */, + 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */, + 37D4B1A92672580400C925CA /* XCRemoteSwiftPackageReference "url-image" */, + ); productRefGroup = 37D4B0CA2671614900C925CA /* Products */; projectDirPath = ""; projectRoot = ""; @@ -373,6 +433,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37D4B1862671691600C925CA /* Assets.xcassets in Resources */, 37D4B15F267164AF00C925CA /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -391,7 +452,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37D4B19326717CE100C925CA /* PopularVideosProvider.swift in Sources */, + 37D4B1832671681B00C925CA /* PlayerView.swift in Sources */, 37D4B0E62671614900C925CA /* ContentView.swift in Sources */, + 37D4B1B02672A01000C925CA /* DataProvider.swift in Sources */, + 37D4B18C26717B3800C925CA /* VideoThumbnailView.swift in Sources */, + 37D4B1B42672A30700C925CA /* VideoDetailsProvider.swift in Sources */, + 37D4B19726717E1500C925CA /* Video.swift in Sources */, 37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -400,7 +467,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37D4B19426717CE100C925CA /* PopularVideosProvider.swift in Sources */, + 37D4B1852671684F00C925CA /* PlayerView.swift in Sources */, 37D4B0E72671614900C925CA /* ContentView.swift in Sources */, + 37D4B1B12672A01000C925CA /* DataProvider.swift in Sources */, + 37D4B18D26717B3800C925CA /* VideoThumbnailView.swift in Sources */, + 37D4B1B52672A30700C925CA /* VideoDetailsProvider.swift in Sources */, + 37D4B19826717E1500C925CA /* Video.swift in Sources */, 37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -425,7 +498,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37D4B19526717CE100C925CA /* PopularVideosProvider.swift in Sources */, + 37D4B1842671684E00C925CA /* PlayerView.swift in Sources */, 37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */, + 37D4B1B22672A01000C925CA /* DataProvider.swift in Sources */, + 37D4B18E26717B3800C925CA /* VideoThumbnailView.swift in Sources */, + 37D4B1B62672A30700C925CA /* VideoDetailsProvider.swift in Sources */, + 37D4B19926717E1500C925CA /* Video.swift in Sources */, 37D4B1812671653A00C925CA /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -801,6 +880,11 @@ DEVELOPMENT_TEAM = 78Z5H3M6RJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Apple TV/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Pearvidious; + INFOPLIST_KEY_CFBundleExecutable = "Pearvidious (Apple TV)"; + INFOPLIST_KEY_CFBundleName = "Pearvidious (Apple TV)"; + INFOPLIST_KEY_CFBundleVersion = 1; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; LD_RUNPATH_SEARCH_PATHS = ( @@ -829,6 +913,11 @@ DEVELOPMENT_TEAM = 78Z5H3M6RJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Apple TV/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Pearvidious; + INFOPLIST_KEY_CFBundleExecutable = "Pearvidious (Apple TV)"; + INFOPLIST_KEY_CFBundleName = "Pearvidious (Apple TV)"; + INFOPLIST_KEY_CFBundleVersion = 1; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; LD_RUNPATH_SEARCH_PATHS = ( @@ -965,6 +1054,56 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 37D4B18F26717C6900C925CA /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; + 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; + 37D4B1A92672580400C925CA /* XCRemoteSwiftPackageReference "url-image" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dmytro-anokhin/url-image"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 37D4B19026717C6900C925CA /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 37D4B18F26717C6900C925CA /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + 37D4B19C2671817900C925CA /* SwiftyJSON */ = { + isa = XCSwiftPackageProductDependency; + package = 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + productName = SwiftyJSON; + }; + 37D4B1AA2672580400C925CA /* URLImage */ = { + isa = XCSwiftPackageProductDependency; + package = 37D4B1A92672580400C925CA /* XCRemoteSwiftPackageReference "url-image" */; + productName = URLImage; + }; + 37D4B1AC2672580400C925CA /* URLImageStore */ = { + isa = XCSwiftPackageProductDependency; + package = 37D4B1A92672580400C925CA /* XCRemoteSwiftPackageReference "url-image" */; + productName = URLImageStore; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 37D4B0BD2671614700C925CA /* Project object */; } diff --git a/Pearvidious.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Pearvidious.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..d8ff2c95 --- /dev/null +++ b/Pearvidious.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "Alamofire", + "repositoryURL": "https://github.com/Alamofire/Alamofire.git", + "state": { + "branch": null, + "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", + "version": "5.4.3" + } + }, + { + "package": "SwiftyJSON", + "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git", + "state": { + "branch": null, + "revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", + "version": "5.0.1" + } + }, + { + "package": "url-image", + "repositoryURL": "https://github.com/dmytro-anokhin/url-image", + "state": { + "branch": null, + "revision": "a55b6597f6ce67dfbdc136ecfb8c8436b14ca41d", + "version": "3.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcschemes/xcschememanagement.plist b/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcschemes/xcschememanagement.plist index 85b3ad6c..65ca5537 100644 --- a/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Pearvidious.xcodeproj/xcuserdata/arek.xcuserdatad/xcschemes/xcschememanagement.plist @@ -19,6 +19,27 @@ orderHint 0 + Playground (Playground) 1.xcscheme + + isShown + + orderHint + 4 + + Playground (Playground) 2.xcscheme + + isShown + + orderHint + 5 + + Playground (Playground).xcscheme + + isShown + + orderHint + 3 + SuppressBuildableAutocreation diff --git a/Shared/ContentView.swift b/Shared/ContentView.swift index 7cb8af46..b35a9028 100644 --- a/Shared/ContentView.swift +++ b/Shared/ContentView.swift @@ -1,16 +1,32 @@ -// -// ContentView.swift -// Shared -// -// Created by Arkadiusz Fal on 09/06/2021. -// - import SwiftUI struct ContentView: View { + @ObservedObject var popular = PopluarVideosProvider() + + var items: [GridItem] { + Array(repeating: .init(.flexible()), count: 4) + } + var body: some View { - Text("Hello, world!") - .padding() + NavigationView { + TabView { + Group { + List { + ForEach(popular.videos) { video in + VideoThumbnailView(video: video) + .listRowInsets(EdgeInsets(top: .zero, leading: .zero, bottom: .zero, trailing: 30)) + } + } + .listStyle(GroupedListStyle()) + } + .tabItem { Text("Popular") } + } + } + .task { + async { + popular.load() + } + } } } diff --git a/Shared/PearvidiousApp.swift b/Shared/PearvidiousApp.swift index ca8fa416..2e6aec7d 100644 --- a/Shared/PearvidiousApp.swift +++ b/Shared/PearvidiousApp.swift @@ -1,17 +1,15 @@ -// -// PearvidiousApp.swift -// Shared -// -// Created by Arkadiusz Fal on 09/06/2021. -// - import SwiftUI +import URLImage +import URLImageStore @main struct PearvidiousApp: App { var body: some Scene { + let urlImageService = URLImageService(fileStore: URLImageFileStore(), + inMemoryStore: URLImageInMemoryStore()) WindowGroup { ContentView() + .environment(\.urlImageService, urlImageService) } } } diff --git a/Shared/PlayerView.swift b/Shared/PlayerView.swift new file mode 100644 index 00000000..300988b2 --- /dev/null +++ b/Shared/PlayerView.swift @@ -0,0 +1,61 @@ +import AVKit +import Foundation +import SwiftUI + +struct PlayerView: View { + @ObservedObject var provider: VideoDetailsProvider + + var body: some View { + ZStack { + if let video = provider.video { + if video.url != nil { + PlayerViewController(video) + .edgesIgnoringSafeArea(.all) + } + + if video.error { + Text("Video can not be loaded") + } + } + } + .task { + async { + provider.load() + } + } + } +} + +struct PlayerViewController: UIViewControllerRepresentable { + var video: Video + + init(_ video: Video) { + self.video = video + } + + private var player: AVPlayer { + let item = AVPlayerItem(url: video.url!) + item.externalMetadata = [makeMetadataItem(.commonIdentifierTitle, value: video.title)] + + return AVPlayer(playerItem: item) + } + + private func makeMetadataItem(_ identifier: AVMetadataIdentifier, value: Any) -> AVMetadataItem { + let item = AVMutableMetadataItem() + item.identifier = identifier + item.value = value as? NSCopying & NSObjectProtocol + item.extendedLanguageTag = "und" + return item.copy() as! AVMetadataItem + } + + func makeUIViewController(context _: Context) -> AVPlayerViewController { + let controller = AVPlayerViewController() + controller.modalPresentationStyle = .fullScreen + controller.player = player + controller.title = video.title + controller.player?.play() + return controller + } + + func updateUIViewController(_: AVPlayerViewController, context _: Context) {} +} diff --git a/Shared/VideoThumbnailView.swift b/Shared/VideoThumbnailView.swift new file mode 100644 index 00000000..717c1080 --- /dev/null +++ b/Shared/VideoThumbnailView.swift @@ -0,0 +1,48 @@ +import SwiftUI +import URLImage +import URLImageStore + +struct VideoThumbnailView: View { + @Environment(\.isFocused) var focused: Bool + + var video: Video + + var body: some View { + NavigationLink(destination: PlayerView(provider: VideoDetailsProvider(video.id))) { + HStack(alignment: .top, spacing: 2) { + // to replace with AsyncImage when it is fixed with lazy views + URLImage(video.thumbnailURL) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 320, height: 180) + } + .mask(RoundedRectangle(cornerRadius: 12)) + .frame(width: 320, height: 180) + + VStack(alignment: .leading) { + Text(video.title) + .foregroundColor(.primary) + .bold() + .lineLimit(1) + + Text(video.author) + .foregroundColor(.secondary) + .lineLimit(1) + + }.padding() + } + } + } +} + +struct VideoThumbnailView_Previews: PreviewProvider { + static var previews: some View { + VideoThumbnailView(video: Video( + id: "A", + title: "A very very long text which", + thumbnailURL: URL(string: "https://invidious.home.arekf.net/vi/yXohcxCKqvo/maxres.jpg")!, + author: "Bear" + )).frame(maxWidth: 350) + } +} diff --git a/Tests Apple TV/PearvidiousUITests.swift b/Tests Apple TV/PearvidiousUITests.swift index 4f023a8f..823a9ec7 100644 --- a/Tests Apple TV/PearvidiousUITests.swift +++ b/Tests Apple TV/PearvidiousUITests.swift @@ -8,7 +8,6 @@ import XCTest class PearvidiousUITests: XCTestCase { - override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/Tests iOS/Tests_iOS.swift b/Tests iOS/Tests_iOS.swift index 9f8a5bc0..bb3f119b 100644 --- a/Tests iOS/Tests_iOS.swift +++ b/Tests iOS/Tests_iOS.swift @@ -8,7 +8,6 @@ import XCTest class Tests_iOS: XCTestCase { - override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/Tests macOS/Tests_macOS.swift b/Tests macOS/Tests_macOS.swift index 8f41e69e..ebc75a43 100644 --- a/Tests macOS/Tests_macOS.swift +++ b/Tests macOS/Tests_macOS.swift @@ -8,7 +8,6 @@ import XCTest class Tests_macOS: XCTestCase { - override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class.