Popular videos, playing from mp4

This commit is contained in:
Arkadiusz Fal 2021-06-11 00:50:10 +02:00
parent d488ee6679
commit 1772728cb8
17 changed files with 454 additions and 20 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# SwiftLint Remote Config Cache
.swiftlint/RemoteConfigCache

14
.swiftlint.yml Normal file
View File

@ -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

8
Apple TV/Info.plist Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
</dict>
</plist>

14
Model/DataProvider.swift Normal file
View File

@ -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)"
}
}

View File

@ -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)
}
}
}
}

39
Model/Video.swift Normal file
View File

@ -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
}
}

View File

@ -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)
}
}
}
}

View File

@ -19,6 +19,29 @@
37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; }; 37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; };
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; }; 37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; };
37D4B1812671653A00C925CA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* ContentView.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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -59,6 +82,13 @@
37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
37D4B171267164B000C925CA /* Tests Apple TV.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests Apple TV.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 37D4B175267164B000C925CA /* PearvidiousUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearvidiousUITests.swift; sourceTree = "<group>"; };
37D4B1822671681B00C925CA /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoThumbnailView.swift; sourceTree = "<group>"; };
37D4B19226717CE100C925CA /* PopularVideosProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularVideosProvider.swift; sourceTree = "<group>"; };
37D4B19626717E1500C925CA /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = "<group>"; };
37D4B1AE26729DEB00C925CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
37D4B1AF2672A01000C925CA /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = "<group>"; };
37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsProvider.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -94,6 +124,10 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37D4B1AD2672580400C925CA /* URLImageStore in Frameworks */,
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
37D4B1AB2672580400C925CA /* URLImage in Frameworks */,
37D4B19126717C6900C925CA /* Alamofire in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -110,6 +144,7 @@
37D4B0BC2671614700C925CA = { 37D4B0BC2671614700C925CA = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37D4B1B72672CFE300C925CA /* Model */,
37D4B0C12671614700C925CA /* Shared */, 37D4B0C12671614700C925CA /* Shared */,
37D4B159267164AE00C925CA /* Apple TV */, 37D4B159267164AE00C925CA /* Apple TV */,
37D4B174267164B000C925CA /* Tests Apple TV */, 37D4B174267164B000C925CA /* Tests Apple TV */,
@ -122,8 +157,10 @@
37D4B0C12671614700C925CA /* Shared */ = { 37D4B0C12671614700C925CA /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37D4B0C22671614700C925CA /* PearvidiousApp.swift */,
37D4B0C32671614700C925CA /* ContentView.swift */, 37D4B0C32671614700C925CA /* ContentView.swift */,
37D4B18B26717B3800C925CA /* VideoThumbnailView.swift */,
37D4B1822671681B00C925CA /* PlayerView.swift */,
37D4B0C22671614700C925CA /* PearvidiousApp.swift */,
37D4B0C42671614800C925CA /* Assets.xcassets */, 37D4B0C42671614800C925CA /* Assets.xcassets */,
); );
path = Shared; path = Shared;
@ -161,6 +198,7 @@
37D4B159267164AE00C925CA /* Apple TV */ = { 37D4B159267164AE00C925CA /* Apple TV */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
37D4B1AE26729DEB00C925CA /* Info.plist */,
37D4B15E267164AF00C925CA /* Assets.xcassets */, 37D4B15E267164AF00C925CA /* Assets.xcassets */,
); );
path = "Apple TV"; path = "Apple TV";
@ -174,6 +212,17 @@
path = "Tests Apple TV"; path = "Tests Apple TV";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
37D4B1B72672CFE300C925CA /* Model */ = {
isa = PBXGroup;
children = (
37D4B19626717E1500C925CA /* Video.swift */,
37D4B19226717CE100C925CA /* PopularVideosProvider.swift */,
37D4B1B32672A30700C925CA /* VideoDetailsProvider.swift */,
37D4B1AF2672A01000C925CA /* DataProvider.swift */,
);
path = Model;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -260,6 +309,12 @@
dependencies = ( dependencies = (
); );
name = "Pearvidious (Apple TV)"; name = "Pearvidious (Apple TV)";
packageProductDependencies = (
37D4B19026717C6900C925CA /* Alamofire */,
37D4B19C2671817900C925CA /* SwiftyJSON */,
37D4B1AA2672580400C925CA /* URLImage */,
37D4B1AC2672580400C925CA /* URLImageStore */,
);
productName = Pearvidious; productName = Pearvidious;
productReference = 37D4B158267164AE00C925CA /* Pearvidious (Apple TV).app */; productReference = 37D4B158267164AE00C925CA /* Pearvidious (Apple TV).app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -324,6 +379,11 @@
Base, Base,
); );
mainGroup = 37D4B0BC2671614700C925CA; mainGroup = 37D4B0BC2671614700C925CA;
packageReferences = (
37D4B18F26717C6900C925CA /* XCRemoteSwiftPackageReference "Alamofire" */,
37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
37D4B1A92672580400C925CA /* XCRemoteSwiftPackageReference "url-image" */,
);
productRefGroup = 37D4B0CA2671614900C925CA /* Products */; productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -373,6 +433,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37D4B1862671691600C925CA /* Assets.xcassets in Resources */,
37D4B15F267164AF00C925CA /* Assets.xcassets in Resources */, 37D4B15F267164AF00C925CA /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -391,7 +452,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37D4B19326717CE100C925CA /* PopularVideosProvider.swift in Sources */,
37D4B1832671681B00C925CA /* PlayerView.swift in Sources */,
37D4B0E62671614900C925CA /* ContentView.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 */, 37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -400,7 +467,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37D4B19426717CE100C925CA /* PopularVideosProvider.swift in Sources */,
37D4B1852671684F00C925CA /* PlayerView.swift in Sources */,
37D4B0E72671614900C925CA /* ContentView.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 */, 37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -425,7 +498,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
37D4B19526717CE100C925CA /* PopularVideosProvider.swift in Sources */,
37D4B1842671684E00C925CA /* PlayerView.swift in Sources */,
37D4B1802671650A00C925CA /* PearvidiousApp.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 */, 37D4B1812671653A00C925CA /* ContentView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -801,6 +880,11 @@
DEVELOPMENT_TEAM = 78Z5H3M6RJ; DEVELOPMENT_TEAM = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = 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_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@ -829,6 +913,11 @@
DEVELOPMENT_TEAM = 78Z5H3M6RJ; DEVELOPMENT_TEAM = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = 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_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@ -965,6 +1054,56 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* 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 */; rootObject = 37D4B0BD2671614700C925CA /* Project object */;
} }

View File

@ -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
}

View File

@ -19,6 +19,27 @@
<key>orderHint</key> <key>orderHint</key>
<integer>0</integer> <integer>0</integer>
</dict> </dict>
<key>Playground (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>Playground (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>5</integer>
</dict>
<key>Playground (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>
<dict> <dict>

View File

@ -1,16 +1,32 @@
//
// ContentView.swift
// Shared
//
// Created by Arkadiusz Fal on 09/06/2021.
//
import SwiftUI import SwiftUI
struct ContentView: View { struct ContentView: View {
@ObservedObject var popular = PopluarVideosProvider()
var items: [GridItem] {
Array(repeating: .init(.flexible()), count: 4)
}
var body: some View { var body: some View {
Text("Hello, world!") NavigationView {
.padding() 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()
}
}
} }
} }

View File

@ -1,17 +1,15 @@
//
// PearvidiousApp.swift
// Shared
//
// Created by Arkadiusz Fal on 09/06/2021.
//
import SwiftUI import SwiftUI
import URLImage
import URLImageStore
@main @main
struct PearvidiousApp: App { struct PearvidiousApp: App {
var body: some Scene { var body: some Scene {
let urlImageService = URLImageService(fileStore: URLImageFileStore(),
inMemoryStore: URLImageInMemoryStore())
WindowGroup { WindowGroup {
ContentView() ContentView()
.environment(\.urlImageService, urlImageService)
} }
} }
} }

61
Shared/PlayerView.swift Normal file
View File

@ -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) {}
}

View File

@ -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)
}
}

View File

@ -8,7 +8,6 @@
import XCTest import XCTest
class PearvidiousUITests: XCTestCase { class PearvidiousUITests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.

View File

@ -8,7 +8,6 @@
import XCTest import XCTest
class Tests_iOS: XCTestCase { class Tests_iOS: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.

View File

@ -8,7 +8,6 @@
import XCTest import XCTest
class Tests_macOS: XCTestCase { class Tests_macOS: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.