Implement trending view actions across platforms

This commit is contained in:
Arkadiusz Fal 2021-08-01 00:10:56 +02:00
parent 3a780b3d2c
commit 64ff1afa70
9 changed files with 231 additions and 371 deletions

View File

@ -1,27 +0,0 @@
import SwiftUI
struct TrendingCountrySelectionView: View {
@State private var query: String = ""
@ObservedObject private var store = Store<[Country]>()
@Binding var selectedCountry: Country
@Environment(\.dismiss) private var dismiss
var body: some View {
ScrollView(.vertical) {
ForEach(store.collection) { country in
Button(country.name) {
selectedCountry = country
dismiss()
}
}
.frame(width: 800)
}
.searchable(text: $query, prompt: Text("Country name or two letter code"))
.onChange(of: query) { newQuery in
store.replace(Country.search(newQuery))
}
.background(.thinMaterial)
}
}

View File

@ -3,7 +3,7 @@ import AVKit
import Foundation import Foundation
import SwiftyJSON import SwiftyJSON
struct Video: Identifiable { struct Video: Identifiable, Equatable {
let id: String let id: String
var title: String var title: String
var thumbnails: [Thumbnail] var thumbnails: [Thumbnail]
@ -161,12 +161,10 @@ struct Video: Identifiable {
} }
} }
static let options = [AVURLAssetPreferPreciseDurationAndTimingKey: false]
private static func extractFormatStreams(from streams: [JSON]) -> [Stream] { private static func extractFormatStreams(from streams: [JSON]) -> [Stream] {
streams.map { streams.map {
SingleAssetStream( SingleAssetStream(
avAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!, options: options), avAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!),
resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue)!, resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue)!,
kind: .stream, kind: .stream,
encoding: $0["encoding"].stringValue encoding: $0["encoding"].stringValue
@ -184,12 +182,16 @@ struct Video: Identifiable {
return videoAssetsURLs.map { return videoAssetsURLs.map {
Stream( Stream(
audioAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset(audioAssetURL!["url"].stringValue)!, options: options), audioAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset(audioAssetURL!["url"].stringValue)!),
videoAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!, options: options), videoAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!),
resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue)!, resolution: Stream.Resolution.from(resolution: $0["resolution"].stringValue)!,
kind: .adaptive, kind: .adaptive,
encoding: $0["encoding"].stringValue encoding: $0["encoding"].stringValue
) )
} }
} }
static func == (lhs: Video, rhs: Video) -> Bool {
lhs.id == rhs.id
}
} }

View File

@ -7,7 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
3705B180267B4DFB00704544 /* TrendingCountrySelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelectionView.swift */; }; 3705B180267B4DFB00704544 /* TrendingCountrySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelection.swift */; };
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; }; 3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; };
3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; }; 3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; };
3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; }; 3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B181267B4E4900704544 /* TrendingCategory.swift */; };
@ -129,7 +129,7 @@
37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */; }; 37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */; };
37BD07BE2698AC96003EBB87 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07BD2698AC96003EBB87 /* Defaults */; }; 37BD07BE2698AC96003EBB87 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07BD2698AC96003EBB87 /* Defaults */; };
37BD07C02698AC97003EBB87 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07BF2698AC97003EBB87 /* Siesta */; }; 37BD07C02698AC97003EBB87 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07BF2698AC97003EBB87 /* Siesta */; };
37BD07C12698AD3B003EBB87 /* TrendingCountrySelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelectionView.swift */; }; 37BD07C12698AD3B003EBB87 /* TrendingCountrySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelection.swift */; };
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; }; 37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; };
37BD07C72698B27B003EBB87 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07C62698B27B003EBB87 /* Introspect */; }; 37BD07C72698B27B003EBB87 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07C62698B27B003EBB87 /* Introspect */; };
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; }; 37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
@ -148,7 +148,7 @@
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; }; 37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; }; 37C7A1D6267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; }; 37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1D4267BFD9D0010EAD6 /* SponsorBlockSegment.swift */; };
37C7A1DA267CACF50010EAD6 /* TrendingCountrySelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelectionView.swift */; }; 37C7A1DA267CACF50010EAD6 /* TrendingCountrySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3705B17F267B4DFB00704544 /* TrendingCountrySelection.swift */; };
37C7A1DC267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; }; 37C7A1DC267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; };
37C7A1DD267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; }; 37C7A1DD267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; };
37C7A1DE267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; }; 37C7A1DE267CE9D90010EAD6 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C7A1DB267CE9D90010EAD6 /* Profile.swift */; };
@ -210,7 +210,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
3705B17F267B4DFB00704544 /* TrendingCountrySelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingCountrySelectionView.swift; sourceTree = "<group>"; }; 3705B17F267B4DFB00704544 /* TrendingCountrySelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingCountrySelection.swift; sourceTree = "<group>"; };
3705B181267B4E4900704544 /* TrendingCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingCategory.swift; sourceTree = "<group>"; }; 3705B181267B4E4900704544 /* TrendingCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingCategory.swift; sourceTree = "<group>"; };
3711403E26B206A6005B3555 /* SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchState.swift; sourceTree = "<group>"; }; 3711403E26B206A6005B3555 /* SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchState.swift; sourceTree = "<group>"; };
371231832683E62F0000B307 /* VideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosView.swift; sourceTree = "<group>"; }; 371231832683E62F0000B307 /* VideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosView.swift; sourceTree = "<group>"; };
@ -412,6 +412,7 @@
37AAF27F26737550007FC770 /* SearchView.swift */, 37AAF27F26737550007FC770 /* SearchView.swift */,
37AAF29F26741C97007FC770 /* SubscriptionsView.swift */, 37AAF29F26741C97007FC770 /* SubscriptionsView.swift */,
37AAF2932674086B007FC770 /* TabSelection.swift */, 37AAF2932674086B007FC770 /* TabSelection.swift */,
3705B17F267B4DFB00704544 /* TrendingCountrySelection.swift */,
3714166E267A8ACC006CA35D /* TrendingView.swift */, 3714166E267A8ACC006CA35D /* TrendingView.swift */,
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */, 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
37AAF29926740A01007FC770 /* VideosListView.swift */, 37AAF29926740A01007FC770 /* VideosListView.swift */,
@ -462,7 +463,6 @@
37B76E95268747C900CE5671 /* OptionsView.swift */, 37B76E95268747C900CE5671 /* OptionsView.swift */,
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */, 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */,
373CFAC52696617C003CB2C6 /* SearchOptionsView.swift */, 373CFAC52696617C003CB2C6 /* SearchOptionsView.swift */,
3705B17F267B4DFB00704544 /* TrendingCountrySelectionView.swift */,
37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */, 37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */,
37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */, 37B17D9F268A1F25006AEE9B /* VideoContextMenuView.swift */,
37B17DA3268A285E006AEE9B /* VideoDetailsView.swift */, 37B17DA3268A285E006AEE9B /* VideoDetailsView.swift */,
@ -758,7 +758,7 @@
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */, 37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, 37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */, 37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */,
37C7A1DA267CACF50010EAD6 /* TrendingCountrySelectionView.swift in Sources */, 37C7A1DA267CACF50010EAD6 /* TrendingCountrySelection.swift in Sources */,
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */, 37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
3711403F26B206A6005B3555 /* SearchState.swift in Sources */, 3711403F26B206A6005B3555 /* SearchState.swift in Sources */,
@ -851,7 +851,7 @@
377FC7DE267A082100A6BBAF /* VideosListView.swift in Sources */, 377FC7DE267A082100A6BBAF /* VideosListView.swift in Sources */,
37D4B19826717E1500C925CA /* Video.swift in Sources */, 37D4B19826717E1500C925CA /* Video.swift in Sources */,
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */, 37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
37BD07C12698AD3B003EBB87 /* TrendingCountrySelectionView.swift in Sources */, 37BD07C12698AD3B003EBB87 /* TrendingCountrySelection.swift in Sources */,
3711404026B206A6005B3555 /* SearchState.swift in Sources */, 3711404026B206A6005B3555 /* SearchState.swift in Sources */,
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */, 373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
@ -925,7 +925,7 @@
3711404126B206A6005B3555 /* SearchState.swift in Sources */, 3711404126B206A6005B3555 /* SearchState.swift in Sources */,
379775952689365600DD52A8 /* Array+Next.swift in Sources */, 379775952689365600DD52A8 /* Array+Next.swift in Sources */,
37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */, 37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */,
3705B180267B4DFB00704544 /* TrendingCountrySelectionView.swift in Sources */, 3705B180267B4DFB00704544 /* TrendingCountrySelection.swift in Sources */,
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */, 373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
37141675267A8E10006CA35D /* Country.swift in Sources */, 37141675267A8E10006CA35D /* Country.swift in Sources */,
373CFAC42696616C003CB2C6 /* CoverSectionRowView.swift in Sources */, 373CFAC42696616C003CB2C6 /* CoverSectionRowView.swift in Sources */,

View File

@ -31,9 +31,10 @@ struct AppSidebarNavigation: View {
var content: some View { var content: some View {
NavigationView { NavigationView {
sidebar sidebar
.frame(minWidth: 180)
Text("Select section") Text("Select section")
.frame(maxWidth: 600)
Text("Select video") Text("Select video")
} }
} }

View File

@ -1,512 +1,234 @@
// swiftlint:disable switch_case_on_newline // swiftlint:disable switch_case_on_newline
enum Country: String, CaseIterable, Identifiable { enum Country: String, CaseIterable, Identifiable, Hashable {
var id: String { var id: String {
rawValue rawValue
} }
case af = "AF" func hash(into hasher: inout Hasher) {
case ax = "AX" hasher.combine(id)
case al = "AL" }
case dz = "DZ" case dz = "DZ"
case `as` = "AS"
case ad = "AD"
case ao = "AO"
case ai = "AI"
case aq = "AQ"
case ag = "AG"
case ar = "AR" case ar = "AR"
case am = "AM"
case aw = "AW"
case au = "AU" case au = "AU"
case at = "AT" case at = "AT"
case az = "AZ" case az = "AZ"
case bs = "BS"
case bh = "BH" case bh = "BH"
case bd = "BD" case bd = "BD"
case bb = "BB"
case by = "BY" case by = "BY"
case be = "BE" case be = "BE"
case bz = "BZ"
case bj = "BJ"
case bm = "BM"
case bt = "BT"
case bo = "BO" case bo = "BO"
case bq = "BQ"
case ba = "BA" case ba = "BA"
case bw = "BW"
case bv = "BV"
case br = "BR" case br = "BR"
case io = "IO"
case bn = "BN"
case bg = "BG" case bg = "BG"
case bf = "BF"
case bi = "BI"
case cv = "CV"
case kh = "KH"
case cm = "CM"
case ca = "CA" case ca = "CA"
case ky = "KY"
case cf = "CF"
case td = "TD"
case cl = "CL" case cl = "CL"
case cn = "CN"
case cx = "CX"
case cc = "CC"
case co = "CO" case co = "CO"
case km = "KM"
case cg = "CG"
case cd = "CD"
case ck = "CK"
case cr = "CR" case cr = "CR"
case ci = "CI"
case hr = "HR" case hr = "HR"
case cu = "CU"
case cw = "CW"
case cy = "CY" case cy = "CY"
case cz = "CZ" case cz = "CZ"
case dk = "DK" case dk = "DK"
case dj = "DJ"
case dm = "DM"
case `do` = "DO" case `do` = "DO"
case ec = "EC" case ec = "EC"
case eg = "EG" case eg = "EG"
case sv = "SV" case sv = "SV"
case gq = "GQ"
case er = "ER"
case ee = "EE" case ee = "EE"
case et = "ET"
case fk = "FK"
case fo = "FO"
case fj = "FJ"
case fi = "FI" case fi = "FI"
case fr = "FR" case fr = "FR"
case gf = "GF"
case pf = "PF"
case tf = "TF"
case ga = "GA"
case gm = "GM"
case ge = "GE" case ge = "GE"
case de = "DE" case de = "DE"
case gh = "GH" case gh = "GH"
case gi = "GI"
case gr = "GR" case gr = "GR"
case gl = "GL"
case gd = "GD"
case gp = "GP"
case gu = "GU"
case gt = "GT" case gt = "GT"
case gg = "GG"
case gn = "GN"
case gw = "GW"
case gy = "GY"
case ht = "HT"
case hm = "HM"
case va = "VA"
case hn = "HN" case hn = "HN"
case hk = "HK" case hk = "HK"
case hu = "HU" case hu = "HU"
case `is` = "IS" case `is` = "IS"
case `in` = "IN" case `in` = "IN"
case id = "ID" case id = "ID"
case ir = "IR"
case iq = "IQ" case iq = "IQ"
case ie = "IE" case ie = "IE"
case im = "IM"
case il = "IL" case il = "IL"
case it = "IT" case it = "IT"
case jm = "JM" case jm = "JM"
case jp = "JP" case jp = "JP"
case je = "JE"
case jo = "JO" case jo = "JO"
case kz = "KZ" case kz = "KZ"
case ke = "KE" case ke = "KE"
case ki = "KI"
case kp = "KP"
case kr = "KR" case kr = "KR"
case kw = "KW" case kw = "KW"
case kg = "KG"
case la = "LA"
case lv = "LV" case lv = "LV"
case lb = "LB" case lb = "LB"
case ls = "LS"
case lr = "LR"
case ly = "LY" case ly = "LY"
case li = "LI" case li = "LI"
case lt = "LT" case lt = "LT"
case lu = "LU" case lu = "LU"
case mo = "MO"
case mk = "MK" case mk = "MK"
case mg = "MG"
case mw = "MW"
case my = "MY" case my = "MY"
case mv = "MV"
case ml = "ML"
case mt = "MT" case mt = "MT"
case mh = "MH"
case mq = "MQ"
case mr = "MR"
case mu = "MU"
case yt = "YT"
case mx = "MX" case mx = "MX"
case fm = "FM"
case md = "MD"
case mc = "MC"
case mn = "MN"
case me = "ME" case me = "ME"
case ms = "MS"
case ma = "MA" case ma = "MA"
case mz = "MZ"
case mm = "MM"
case na = "NA"
case nr = "NR"
case np = "NP" case np = "NP"
case nl = "NL" case nl = "NL"
case nc = "NC"
case nz = "NZ" case nz = "NZ"
case ni = "NI" case ni = "NI"
case ne = "NE"
case ng = "NG" case ng = "NG"
case nu = "NU"
case nf = "NF"
case mp = "MP"
case no = "NO" case no = "NO"
case om = "OM" case om = "OM"
case pk = "PK" case pk = "PK"
case pw = "PW"
case ps = "PS"
case pa = "PA" case pa = "PA"
case pg = "PG" case pg = "PG"
case py = "PY" case py = "PY"
case pe = "PE" case pe = "PE"
case ph = "PH" case ph = "PH"
case pn = "PN"
case pl = "PL" case pl = "PL"
case pt = "PT" case pt = "PT"
case pr = "PR" case pr = "PR"
case qa = "QA" case qa = "QA"
case re = "RE"
case ro = "RO" case ro = "RO"
case ru = "RU" case ru = "RU"
case rw = "RW"
case bl = "BL"
case sh = "SH"
case kn = "KN"
case lc = "LC"
case mf = "MF"
case pm = "PM"
case vc = "VC"
case ws = "WS"
case sm = "SM"
case st = "ST"
case sa = "SA" case sa = "SA"
case sn = "SN" case sn = "SN"
case rs = "RS" case rs = "RS"
case sc = "SC"
case sl = "SL"
case sg = "SG" case sg = "SG"
case sx = "SX"
case sk = "SK" case sk = "SK"
case si = "SI" case si = "SI"
case sb = "SB"
case so = "SO"
case za = "ZA" case za = "ZA"
case gs = "GS"
case ss = "SS"
case es = "ES" case es = "ES"
case lk = "LK" case lk = "LK"
case sd = "SD"
case sr = "SR"
case sj = "SJ"
case sz = "SZ"
case se = "SE" case se = "SE"
case ch = "CH" case ch = "CH"
case sy = "SY"
case tw = "TW" case tw = "TW"
case tj = "TJ"
case tz = "TZ" case tz = "TZ"
case th = "TH" case th = "TH"
case tl = "TL"
case tg = "TG"
case tk = "TK"
case to = "TO"
case tt = "TT"
case tn = "TN" case tn = "TN"
case tr = "TR" case tr = "TR"
case tm = "TM"
case tc = "TC"
case tv = "TV"
case ug = "UG" case ug = "UG"
case ua = "UA" case ua = "UA"
case ae = "AE" case ae = "AE"
case gb = "GB" case gb = "GB"
case us = "US" case us = "US"
case um = "UM"
case uy = "UY" case uy = "UY"
case uz = "UZ"
case vu = "VU"
case ve = "VE" case ve = "VE"
case vn = "VN" case vn = "VN"
case vg = "VG"
case vi = "VI" case vi = "VI"
case wf = "WF"
case eh = "EH"
case ye = "YE" case ye = "YE"
case zm = "ZM"
case zw = "ZW" case zw = "ZW"
} }
extension Country { extension Country {
var name: String { var name: String {
switch self { switch self {
case .af: return "Afghanistan"
case .ax: return "Åland Islands"
case .al: return "Albania"
case .dz: return "Algeria" case .dz: return "Algeria"
case .as: return "American Samoa"
case .ad: return "Andorra"
case .ao: return "Angola"
case .ai: return "Anguilla"
case .aq: return "Antarctica"
case .ag: return "Antigua and Barbuda"
case .ar: return "Argentina" case .ar: return "Argentina"
case .am: return "Armenia"
case .aw: return "Aruba"
case .au: return "Australia" case .au: return "Australia"
case .at: return "Austria" case .at: return "Austria"
case .az: return "Azerbaijan" case .az: return "Azerbaijan"
case .bs: return "Bahamas"
case .bh: return "Bahrain" case .bh: return "Bahrain"
case .bd: return "Bangladesh" case .bd: return "Bangladesh"
case .bb: return "Barbados"
case .by: return "Belarus" case .by: return "Belarus"
case .be: return "Belgium" case .be: return "Belgium"
case .bz: return "Belize"
case .bj: return "Benin"
case .bm: return "Bermuda"
case .bt: return "Bhutan"
case .bo: return "Bolivia (Plurinational State of)" case .bo: return "Bolivia (Plurinational State of)"
case .bq: return "Bonaire, Sint Eustatius and Saba"
case .ba: return "Bosnia and Herzegovina" case .ba: return "Bosnia and Herzegovina"
case .bw: return "Botswana"
case .bv: return "Bouvet Island"
case .br: return "Brazil" case .br: return "Brazil"
case .io: return "British Indian Ocean Territory"
case .bn: return "Brunei Darussalam"
case .bg: return "Bulgaria" case .bg: return "Bulgaria"
case .bf: return "Burkina Faso"
case .bi: return "Burundi"
case .cv: return "Cabo Verde"
case .kh: return "Cambodia"
case .cm: return "Cameroon"
case .ca: return "Canada" case .ca: return "Canada"
case .ky: return "Cayman Islands"
case .cf: return "Central African Republic"
case .td: return "Chad"
case .cl: return "Chile" case .cl: return "Chile"
case .cn: return "China"
case .cx: return "Christmas Island"
case .cc: return "Cocos (Keeling) Islands"
case .co: return "Colombia" case .co: return "Colombia"
case .km: return "Comoros"
case .cg: return "Congo"
case .cd: return "Congo (Democratic Republic of the)"
case .ck: return "Cook Islands"
case .cr: return "Costa Rica" case .cr: return "Costa Rica"
case .ci: return "Côte d'Ivoire"
case .hr: return "Croatia" case .hr: return "Croatia"
case .cu: return "Cuba"
case .cw: return "Curaçao"
case .cy: return "Cyprus" case .cy: return "Cyprus"
case .cz: return "Czechia" case .cz: return "Czechia"
case .dk: return "Denmark" case .dk: return "Denmark"
case .dj: return "Djibouti"
case .dm: return "Dominica"
case .do: return "Dominican Republic" case .do: return "Dominican Republic"
case .ec: return "Ecuador" case .ec: return "Ecuador"
case .eg: return "Egypt" case .eg: return "Egypt"
case .sv: return "El Salvador" case .sv: return "El Salvador"
case .gq: return "Equatorial Guinea"
case .er: return "Eritrea"
case .ee: return "Estonia" case .ee: return "Estonia"
case .et: return "Ethiopia"
case .fk: return "Falkland Islands (Malvinas)"
case .fo: return "Faroe Islands"
case .fj: return "Fiji"
case .fi: return "Finland" case .fi: return "Finland"
case .fr: return "France" case .fr: return "France"
case .gf: return "French Guiana"
case .pf: return "French Polynesia"
case .tf: return "French Southern Territories"
case .ga: return "Gabon"
case .gm: return "Gambia"
case .ge: return "Georgia" case .ge: return "Georgia"
case .de: return "Germany" case .de: return "Germany"
case .gh: return "Ghana" case .gh: return "Ghana"
case .gi: return "Gibraltar"
case .gr: return "Greece" case .gr: return "Greece"
case .gl: return "Greenland"
case .gd: return "Grenada"
case .gp: return "Guadeloupe"
case .gu: return "Guam"
case .gt: return "Guatemala" case .gt: return "Guatemala"
case .gg: return "Guernsey"
case .gn: return "Guinea"
case .gw: return "Guinea-Bissau"
case .gy: return "Guyana"
case .ht: return "Haiti"
case .hm: return "Heard Island and McDonald Islands"
case .va: return "Holy See"
case .hn: return "Honduras" case .hn: return "Honduras"
case .hk: return "Hong Kong" case .hk: return "Hong Kong"
case .hu: return "Hungary" case .hu: return "Hungary"
case .is: return "Iceland" case .is: return "Iceland"
case .in: return "India" case .in: return "India"
case .id: return "Indonesia" case .id: return "Indonesia"
case .ir: return "Iran (Islamic Republic of)"
case .iq: return "Iraq" case .iq: return "Iraq"
case .ie: return "Ireland" case .ie: return "Ireland"
case .im: return "Isle of Man"
case .il: return "Israel" case .il: return "Israel"
case .it: return "Italy" case .it: return "Italy"
case .jm: return "Jamaica" case .jm: return "Jamaica"
case .jp: return "Japan" case .jp: return "Japan"
case .je: return "Jersey"
case .jo: return "Jordan" case .jo: return "Jordan"
case .kz: return "Kazakhstan" case .kz: return "Kazakhstan"
case .ke: return "Kenya" case .ke: return "Kenya"
case .ki: return "Kiribati"
case .kp: return "Korea (Democratic People's Republic of)"
case .kr: return "Korea (Republic of)" case .kr: return "Korea (Republic of)"
case .kw: return "Kuwait" case .kw: return "Kuwait"
case .kg: return "Kyrgyzstan"
case .la: return "Lao People's Democratic Republic"
case .lv: return "Latvia" case .lv: return "Latvia"
case .lb: return "Lebanon" case .lb: return "Lebanon"
case .ls: return "Lesotho"
case .lr: return "Liberia"
case .ly: return "Libya" case .ly: return "Libya"
case .li: return "Liechtenstein" case .li: return "Liechtenstein"
case .lt: return "Lithuania" case .lt: return "Lithuania"
case .lu: return "Luxembourg" case .lu: return "Luxembourg"
case .mo: return "Macao"
case .mk: return "Macedonia (the former Yugoslav Republic of)" case .mk: return "Macedonia (the former Yugoslav Republic of)"
case .mg: return "Madagascar"
case .mw: return "Malawi"
case .my: return "Malaysia" case .my: return "Malaysia"
case .mv: return "Maldives"
case .ml: return "Mali"
case .mt: return "Malta" case .mt: return "Malta"
case .mh: return "Marshall Islands"
case .mq: return "Martinique"
case .mr: return "Mauritania"
case .mu: return "Mauritius"
case .yt: return "Mayotte"
case .mx: return "Mexico" case .mx: return "Mexico"
case .fm: return "Micronesia (Federated States of)"
case .md: return "Moldova (Republic of)"
case .mc: return "Monaco"
case .mn: return "Mongolia"
case .me: return "Montenegro" case .me: return "Montenegro"
case .ms: return "Montserrat"
case .ma: return "Morocco" case .ma: return "Morocco"
case .mz: return "Mozambique"
case .mm: return "Myanmar"
case .na: return "Namibia"
case .nr: return "Nauru"
case .np: return "Nepal" case .np: return "Nepal"
case .nl: return "Netherlands" case .nl: return "Netherlands"
case .nc: return "New Caledonia"
case .nz: return "New Zealand" case .nz: return "New Zealand"
case .ni: return "Nicaragua" case .ni: return "Nicaragua"
case .ne: return "Niger"
case .ng: return "Nigeria" case .ng: return "Nigeria"
case .nu: return "Niue"
case .nf: return "Norfolk Island"
case .mp: return "Northern Mariana Islands"
case .no: return "Norway" case .no: return "Norway"
case .om: return "Oman" case .om: return "Oman"
case .pk: return "Pakistan" case .pk: return "Pakistan"
case .pw: return "Palau"
case .ps: return "Palestine, State of"
case .pa: return "Panama" case .pa: return "Panama"
case .pg: return "Papua New Guinea" case .pg: return "Papua New Guinea"
case .py: return "Paraguay" case .py: return "Paraguay"
case .pe: return "Peru" case .pe: return "Peru"
case .ph: return "Philippines" case .ph: return "Philippines"
case .pn: return "Pitcairn"
case .pl: return "Poland" case .pl: return "Poland"
case .pt: return "Portugal" case .pt: return "Portugal"
case .pr: return "Puerto Rico" case .pr: return "Puerto Rico"
case .qa: return "Qatar" case .qa: return "Qatar"
case .re: return "Réunion"
case .ro: return "Romania" case .ro: return "Romania"
case .ru: return "Russian Federation" case .ru: return "Russian Federation"
case .rw: return "Rwanda"
case .bl: return "Saint Barthélemy"
case .sh: return "Saint Helena, Ascension and Tristan da Cunha"
case .kn: return "Saint Kitts and Nevis"
case .lc: return "Saint Lucia"
case .mf: return "Saint Martin (French part)"
case .pm: return "Saint Pierre and Miquelon"
case .vc: return "Saint Vincent and the Grenadines"
case .ws: return "Samoa"
case .sm: return "San Marino"
case .st: return "Sao Tome and Principe"
case .sa: return "Saudi Arabia" case .sa: return "Saudi Arabia"
case .sn: return "Senegal" case .sn: return "Senegal"
case .rs: return "Serbia" case .rs: return "Serbia"
case .sc: return "Seychelles"
case .sl: return "Sierra Leone"
case .sg: return "Singapore" case .sg: return "Singapore"
case .sx: return "Sint Maarten (Dutch part)"
case .sk: return "Slovakia" case .sk: return "Slovakia"
case .si: return "Slovenia" case .si: return "Slovenia"
case .sb: return "Solomon Islands"
case .so: return "Somalia"
case .za: return "South Africa" case .za: return "South Africa"
case .gs: return "South Georgia and the South Sandwich Islands"
case .ss: return "South Sudan"
case .es: return "Spain" case .es: return "Spain"
case .lk: return "Sri Lanka" case .lk: return "Sri Lanka"
case .sd: return "Sudan"
case .sr: return "Suriname"
case .sj: return "Svalbard and Jan Mayen"
case .sz: return "Swaziland"
case .se: return "Sweden" case .se: return "Sweden"
case .ch: return "Switzerland" case .ch: return "Switzerland"
case .sy: return "Syrian Arab Republic"
case .tw: return "Taiwan, Province of China[a]" case .tw: return "Taiwan, Province of China[a]"
case .tj: return "Tajikistan"
case .tz: return "Tanzania, United Republic of" case .tz: return "Tanzania, United Republic of"
case .th: return "Thailand" case .th: return "Thailand"
case .tl: return "Timor-Leste"
case .tg: return "Togo"
case .tk: return "Tokelau"
case .to: return "Tonga"
case .tt: return "Trinidad and Tobago"
case .tn: return "Tunisia" case .tn: return "Tunisia"
case .tr: return "Turkey" case .tr: return "Turkey"
case .tm: return "Turkmenistan"
case .tc: return "Turks and Caicos Islands"
case .tv: return "Tuvalu"
case .ug: return "Uganda" case .ug: return "Uganda"
case .ua: return "Ukraine" case .ua: return "Ukraine"
case .ae: return "United Arab Emirates" case .ae: return "United Arab Emirates"
case .gb: return "United Kingdom of Great Britain and Northern Ireland" case .gb: return "United Kingdom of Great Britain and Northern Ireland"
case .us: return "United States of America" case .us: return "United States of America"
case .um: return "United States Minor Outlying Islands"
case .uy: return "Uruguay" case .uy: return "Uruguay"
case .uz: return "Uzbekistan"
case .vu: return "Vanuatu"
case .ve: return "Venezuela (Bolivarian Republic of)" case .ve: return "Venezuela (Bolivarian Republic of)"
case .vn: return "Viet Nam" case .vn: return "Viet Nam"
case .vg: return "Virgin Islands (British)"
case .vi: return "Virgin Islands (U.S.)" case .vi: return "Virgin Islands (U.S.)"
case .wf: return "Wallis and Futuna"
case .eh: return "Western Sahara"
case .ye: return "Yemen" case .ye: return "Yemen"
case .zm: return "Zambia"
case .zw: return "Zimbabwe" case .zw: return "Zimbabwe"
} }
} }
@ -536,11 +258,11 @@ extension Country {
} }
static func searchByPartialName(_ name: String) -> [Country] { static func searchByPartialName(_ name: String) -> [Country] {
guard name.count >= 2 else { guard !name.isEmpty else {
return [] return []
} }
return filteredCountries { stringFolding($0).contains(name) } return filteredCountries { stringFolding($0).contains(stringFolding(name)) }
} }
private static func stringFolding(_ string: String) -> String { private static func stringFolding(_ string: String) -> String {

View File

@ -0,0 +1,101 @@
import SwiftUI
struct TrendingCountrySelection: View {
static let prompt = "Country Name or Code"
@Binding var selectedCountry: Country?
@ObservedObject private var store = Store(Country.allCases)
@State private var query: String = ""
@State private var selection: Country?
@FocusState var countryIsFocused
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
#if os(macOS)
HStack {
TextField("Country", text: $query, prompt: Text(TrendingCountrySelection.prompt))
.focused($countryIsFocused)
Button("Done") { selectCountryAndDismiss() }
}
.padding([.horizontal, .top])
countriesList
#else
NavigationView {
countriesList
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button("Done") { selectCountryAndDismiss() }
}
}
#if os(iOS)
.navigationBarTitle("Trending Country", displayMode: .automatic)
#endif
}
#endif
}
.onAppear {
countryIsFocused = true
}
.onSubmit { selectCountryAndDismiss() }
#if !os(macOS)
.searchable(text: $query, placement: searchPlacement, prompt: Text(TrendingCountrySelection.prompt))
#endif
#if os(tvOS)
.background(.thinMaterial)
#endif
}
var countriesList: some View {
ScrollViewReader { _ in
List(store.collection, selection: $selection) { country in
#if os(macOS)
Text(country.name)
.tag(country)
.id(country)
#else
Button(country.name) { selectCountryAndDismiss(country) }
#endif
}
.onChange(of: query) { newQuery in
let results = Country.search(newQuery)
store.replace(results)
selection = results.first
}
}
#if os(macOS)
.listStyle(.inset(alternatesRowBackgrounds: true))
.padding(.bottom, 5)
#endif
}
var searchPlacement: SearchFieldPlacement {
#if os(iOS)
.navigationBarDrawer(displayMode: .always)
#else
.automatic
#endif
}
func selectCountryAndDismiss(_ country: Country? = nil) {
let selected = country ?? selection
if selected != nil {
selectedCountry = selected
}
dismiss()
}
}
struct TrendingCountrySelection_Previews: PreviewProvider {
static var previews: some View {
TrendingCountrySelection(selectedCountry: .constant(.pl))
}
}

View File

@ -3,7 +3,7 @@ import SwiftUI
struct TrendingView: View { struct TrendingView: View {
@State private var category: TrendingCategory = .default @State private var category: TrendingCategory = .default
@State private var country: Country = .pl @State private var country: Country! = .pl
@State private var selectingCountry = false @State private var selectingCountry = false
@ObservedObject private var store = Store<[Video]>() @ObservedObject private var store = Store<[Video]>()
@ -16,62 +16,107 @@ struct TrendingView: View {
resource.addObserver(store) resource.addObserver(store)
} }
var toolbar: some View {
HStack {
HStack {
Text("Category")
.foregroundColor(.secondary)
categoryButton
}
#if os(iOS)
Spacer()
#endif
HStack {
Text("Country")
.foregroundColor(.secondary)
countryButton
}
}
}
var body: some View { var body: some View {
Section { Section {
VStack(alignment: .center, spacing: 2) { VStack(alignment: .center, spacing: 2) {
#if os(tvOS) #if os(tvOS)
HStack { toolbar
Text("Category") .scaleEffect(0.85)
.foregroundColor(.secondary)
categoryButton
Text("Country")
.foregroundColor(.secondary)
countryFlag
countryButton
}
.scaleEffect(0.85)
#endif #endif
VideosView(videos: store.collection) VideosView(videos: store.collection)
#if os(iOS)
toolbar
.font(.system(size: 14))
.animation(nil)
.padding(.horizontal)
.padding(.vertical, 10)
.overlay(Divider().offset(x: 0, y: -2), alignment: .topTrailing)
#endif
} }
} }
#if !os(tvOS) #if os(tvOS)
.fullScreenCover(isPresented: $selectingCountry, onDismiss: { setCountry(country) }) {
TrendingCountrySelection(selectedCountry: $country)
}
#else
.sheet(isPresented: $selectingCountry, onDismiss: { setCountry(country) }) {
TrendingCountrySelection(selectedCountry: $country)
#if os(macOS)
.frame(minWidth: 400, minHeight: 400)
#endif
}
.navigationTitle("Trending") .navigationTitle("Trending")
#endif #endif
#if os(macOS)
.toolbar {
ToolbarItemGroup {
categoryButton
countryButton
}
}
#endif
.onAppear { .onAppear {
resource.loadIfNeeded() resource.loadIfNeeded()
} }
} }
var categoryButton: some View { var categoryButton: some View {
Button(category.name) { #if os(tvOS)
setCategory(category.next()) Button(category.name) {
} setCategory(category.next())
.contextMenu {
ForEach(TrendingCategory.allCases) { category in
Button(category.name) { setCategory(category) }
} }
} .contextMenu {
} ForEach(TrendingCategory.allCases) { category in
Button(category.name) { setCategory(category) }
var countryFlag: some View { }
Text(country.flag) }
.font(.system(size: 60)) #else
Menu(category.name) {
ForEach(TrendingCategory.allCases) { category in
Button(action: { setCategory(category) }) {
if category == self.category {
Label(category.name, systemImage: "checkmark")
} else {
Text(category.name)
}
}
}
}
#endif
} }
var countryButton: some View { var countryButton: some View {
Button(country.rawValue) { Button(action: {
selectingCountry.toggle() selectingCountry.toggle()
resource.removeObservers(ownedBy: store) resource.removeObservers(ownedBy: store)
}) {
Text("\(country.flag) \(country.id)")
} }
#if os(tvOS)
.fullScreenCover(isPresented: $selectingCountry, onDismiss: { setCountry(country) }) {
TrendingCountrySelectionView(selectedCountry: $country)
}
#endif
} }
fileprivate func setCategory(_ category: TrendingCategory) { fileprivate func setCategory(_ category: TrendingCategory) {
@ -87,3 +132,10 @@ struct TrendingView: View {
resource.loadIfNeeded() resource.loadIfNeeded()
} }
} }
struct TrendingView_Previews: PreviewProvider {
static var previews: some View {
TrendingView()
.environmentObject(NavigationState())
}
}

View File

@ -193,7 +193,7 @@ struct VideoView: View {
minHeight: Double = 140, minHeight: Double = 140,
maxHeight: Double = .infinity maxHeight: Double = .infinity
) -> some View { ) -> some View {
ZStack(alignment: .trailing) { ZStack(alignment: .leading) {
thumbnail(.maxres, minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight) thumbnail(.maxres, minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight)
VStack { VStack {
@ -222,6 +222,7 @@ struct VideoView: View {
.padding(10) .padding(10)
} }
} }
.frame(maxWidth: 600)
} }
func thumbnail( func thumbnail(

View File

@ -6,17 +6,25 @@ struct VideosListView: View {
var body: some View { var body: some View {
Section { Section {
List { ScrollViewReader { scrollView in
ForEach(videos) { video in List {
VideoView(video: video, layout: .list) ForEach(videos) { video in
.contextMenu { VideoContextMenuView(video: video) } VideoView(video: video, layout: .list)
#if os(tvOS) .contextMenu { VideoContextMenuView(video: video) }
.listRowInsets(listRowInsets) #if os(tvOS)
.listRowInsets(listRowInsets)
#elseif os(iOS)
.listRowInsets(EdgeInsets(.zero))
.listRowSeparator(.hidden)
#endif
}
.onChange(of: videos) { videos in
guard let video = videos.first else {
return
}
#elseif os(iOS) scrollView.scrollTo(video.id, anchor: .top)
.listRowInsets(EdgeInsets(.zero)) }
.listRowSeparator(.hidden)
#endif
} }
} }
#if os(tvOS) #if os(tvOS)