Skip to content

Commit 4ed5d65

Browse files
committed
⚡️ More movie details
Signed-off-by: Peter Friese <[email protected]>
1 parent 9d203fb commit 4ed5d65

File tree

9 files changed

+463
-81
lines changed

9 files changed

+463
-81
lines changed

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/Features/Details/MovieDetailsView.swift

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,49 @@
1515
import FirebaseDataConnect
1616
import FriendlyFlixSDK
1717
import SwiftUI
18+
import NukeUI
1819

1920
struct MovieDetailsView: View {
20-
var movie: Movie
21+
private var movie: Movie
2122

23+
private var movieDetails: MovieDetails? {
24+
DataConnect.friendlyFlixConnector
25+
.getMovieByIdQuery
26+
.ref(id: movie.id)
27+
.data?.movie.map { movieDetails in
28+
MovieDetails(
29+
title: movieDetails.title,
30+
description: movieDetails.description ?? "",
31+
releaseYear: movieDetails.releaseYear,
32+
rating: movieDetails.rating ?? 0,
33+
imageUrl: movieDetails.imageUrl,
34+
mainActors: movieDetails.mainActors.map({ mainActor in
35+
MovieActor(id: mainActor.id,
36+
name: mainActor.name,
37+
imageUrl: mainActor.imageUrl)
38+
}),
39+
supportingActors: movieDetails.supportingActors.map({ supportingActor in
40+
MovieActor(id: supportingActor.id,
41+
name: supportingActor.name,
42+
imageUrl: supportingActor.imageUrl)
43+
}),
44+
reviews: movieDetails.reviews.map({ review in
45+
Review(id: review.id,
46+
reviewText: review.reviewText ?? "",
47+
rating: review.rating ?? 0,
48+
userName: review.user.username)
49+
})
50+
)
51+
}
52+
}
53+
54+
public init(movie: Movie) {
55+
self.movie = movie
56+
}
57+
58+
}
59+
60+
extension MovieDetailsView {
2261
var body: some View {
2362
VStack(alignment: .leading, spacing: 10) {
2463
// description
@@ -34,11 +73,93 @@ struct MovieDetailsView: View {
3473
Spacer()
3574
}
3675
}
76+
77+
if let movieDetails {
78+
if !movieDetails.mainActors.isEmpty {
79+
actorsSection(title: "Main actors", actors: movieDetails.mainActors)
80+
}
81+
if !movieDetails.supportingActors.isEmpty {
82+
actorsSection(title: "Supporting actors", actors: movieDetails.supportingActors)
83+
}
84+
85+
// Reviews
86+
DetailsSection("Ratings & Reviews") {
87+
VStack(alignment: .leading) {
88+
HStack(alignment: .center) {
89+
Text("\(movieDetails.rating, specifier: "%.1f")")
90+
.font(.system(size: 64, weight: .bold))
91+
Spacer()
92+
VStack(alignment: .trailing) {
93+
StarRatingView(rating: Double(movieDetails.rating))
94+
Text("23 Ratings")
95+
.font(.title)
96+
.bold()
97+
}
98+
}
99+
Text("Most Helpful Reviews")
100+
.font(.title3)
101+
.bold()
102+
ScrollView(.horizontal) {
103+
LazyHStack {
104+
ForEach(movieDetails.reviews) { review in
105+
MovieReviewCard(title: "Feedback",
106+
rating: Double(review.rating),
107+
reviewerName: review.userName,
108+
review: review.reviewText)
109+
.frame(width: 350)
110+
}
111+
}
112+
.scrollTargetLayout()
113+
}
114+
.scrollTargetBehavior(.viewAligned)
115+
.scrollIndicators(.hidden)
116+
}
117+
}
118+
}
37119
}
38120
.padding()
39121
}
40122
}
41123

124+
extension MovieDetailsView {
125+
func actorsSection(title: String, actors: [MovieActor]) -> some View {
126+
DetailsSection(title) {
127+
ScrollView(.horizontal) {
128+
LazyHStack {
129+
ForEach(actors, id: \.id) { actor in
130+
VStack(alignment: .center) {
131+
if let imageUrl = URL(string: actor.imageUrl) {
132+
LazyImage(url: imageUrl) { state in
133+
if let image = state.image {
134+
image
135+
.resizable()
136+
.scaledToFill()
137+
.frame(width: 96, height: 96, alignment: .center)
138+
.clipShape(Circle())
139+
} else if state.error != nil {
140+
Color.red
141+
.redacted(if: true)
142+
} else {
143+
Image(systemName: "person.circle.fill")
144+
.resizable()
145+
.scaledToFit()
146+
.frame(width: 96, height: 96, alignment: .center)
147+
.redacted(reason: .placeholder)
148+
.clipShape(Circle())
149+
}
150+
}
151+
}
152+
Text(actor.name)
153+
.font(.headline)
154+
}
155+
.padding(.horizontal, 8)
156+
}
157+
}
158+
}
159+
}
160+
}
161+
}
162+
42163
#Preview {
43164
MovieDetailsView(movie: Movie.mock)
44165
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright © 2024 Google LLC. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
import SwiftUI
17+
18+
struct MovieReviewCard: View {
19+
var title: String
20+
var rating: Double
21+
var reviewerName: String
22+
var review: String
23+
24+
var body: some View {
25+
VStack(alignment: .leading, spacing: 10) {
26+
Text(title)
27+
.font(.headline)
28+
HStack {
29+
StarRatingView(rating: rating)
30+
Text("·")
31+
Text(reviewerName)
32+
}
33+
.font(.subheadline)
34+
Text(review)
35+
Spacer()
36+
}
37+
.padding(16)
38+
.frame(height: 200)
39+
.background(Color(UIColor.secondarySystemBackground))
40+
.clipShape(
41+
UnevenRoundedRectangle(
42+
cornerRadii: .init(
43+
topLeading: 16,
44+
bottomLeading: 16,
45+
bottomTrailing: 16,
46+
topTrailing: 16),
47+
style: .continuous))
48+
}
49+
}
50+
51+
#Preview {
52+
ScrollView {
53+
MovieReviewCard(
54+
title: "Really great",
55+
rating: 4.5,
56+
reviewerName: "John Doe",
57+
review:
58+
"Velit officia quis ut ut dolor velit voluptate magna Lorem. Sint do ex adipisicing laboris magna et duis aute fugiat culpa minim id culpa nulla do. Occaecat in anim ad Lorem eu aute consectetur excepteur fugiat laboris eiusmod. Et tempor Lorem quis eu magna cillum adipisicing consectetur."
59+
)
60+
.padding()
61+
}
62+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright © 2024 Google LLC. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
16+
import SwiftUI
17+
18+
struct StarRatingView: View {
19+
var rating: Double
20+
21+
var body: some View {
22+
HStack(spacing: 4) {
23+
ForEach(0..<5) { index in
24+
Image(systemName: self.starType(for: index))
25+
.foregroundColor(.yellow)
26+
}
27+
}
28+
}
29+
30+
func starType(for index: Int) -> String {
31+
if rating > Double(index) + 0.75 {
32+
return "star.fill"
33+
} else if rating > Double(index) + 0.25 {
34+
return "star.lefthalf.fill"
35+
} else {
36+
return "star"
37+
}
38+
}
39+
}

Examples/FriendlyFlix/app/FriendlyFlix/FriendlyFlix/Model/Movie.swift

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,55 @@
1919
import Foundation
2020
import SwiftUI
2121

22+
struct Review: Identifiable, Hashable {
23+
let id: UUID
24+
var reviewText: String
25+
var rating: Int
26+
var userName: String
27+
}
28+
29+
struct MovieActor: Identifiable, Hashable {
30+
var id: UUID
31+
var name: String
32+
var imageUrl: String
33+
}
34+
35+
36+
struct MovieDetails: Identifiable, Hashable {
37+
let id: UUID
38+
let title: String
39+
let description: String
40+
let releaseYear: Int?
41+
var rating: Double
42+
let imageUrl: String
43+
44+
let mainActors: [MovieActor]
45+
let supportingActors: [MovieActor]
46+
let reviews: [Review]
47+
48+
init(
49+
id: UUID = UUID(),
50+
title: String,
51+
description: String,
52+
releaseYear: Int?,
53+
rating: Double,
54+
imageUrl: String,
55+
mainActors: [MovieActor],
56+
supportingActors: [MovieActor],
57+
reviews: [Review]
58+
) {
59+
self.id = id
60+
self.title = title
61+
self.description = description
62+
self.releaseYear = releaseYear
63+
self.rating = rating
64+
self.imageUrl = imageUrl
65+
self.mainActors = mainActors
66+
self.supportingActors = supportingActors
67+
self.reviews = reviews
68+
}
69+
}
70+
2271
struct Movie: Identifiable, Hashable {
2372
let id: UUID
2473
let title: String
@@ -101,10 +150,5 @@ extension Movie: Mockable {
101150
static var topMovies = Array<Movie>(mockList.prefix(3))
102151
static var watchList = Array<Movie>(mockList.suffix(5))
103152

104-
// static var featured = Array<Movie>([mockList[0]])
105-
// static var topMovies = Array<Movie>([mockList[1]])
106-
// static var watchList = Array<Movie>([mockList[2]])
107-
108-
109153
}
110154

Examples/FriendlyFlix/app/FriendlyFlixSDK/Sources/FriendlyFlixClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import FirebaseDataConnect
1313

1414
public extension DataConnect {
1515

16-
static var friendlyFlixConnector: FriendlyFlixConnector = {
16+
static let friendlyFlixConnector: FriendlyFlixConnector = {
1717
let dc = DataConnect.dataConnect(connectorConfig: FriendlyFlixConnector.connectorConfig, callerSDKType: .generated)
1818
return FriendlyFlixConnector(dataConnect: dc)
1919
}()
@@ -22,7 +22,7 @@ public extension DataConnect {
2222

2323
public class FriendlyFlixConnector {
2424

25-
var dataConnect: DataConnect
25+
let dataConnect: DataConnect
2626

2727
public static let connectorConfig = ConnectorConfig(serviceId: "dataconnect", location: "us-central1", connector: "friendly-flix")
2828

Examples/FriendlyFlix/app/FriendlyFlixSDK/Sources/FriendlyFlixKeys.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ extension ActorKey : Hashable {
5656
}
5757
}
5858

59+
extension ActorKey : Sendable {}
60+
5961

6062

6163
public struct FavoriteMovieKey {
@@ -126,6 +128,8 @@ extension FavoriteMovieKey : Hashable {
126128
}
127129
}
128130

131+
extension FavoriteMovieKey : Sendable {}
132+
129133

130134

131135
public struct MovieActorKey {
@@ -196,6 +200,8 @@ extension MovieActorKey : Hashable {
196200
}
197201
}
198202

203+
extension MovieActorKey : Sendable {}
204+
199205

200206

201207
public struct MovieMetadataKey {
@@ -250,6 +256,8 @@ extension MovieMetadataKey : Hashable {
250256
}
251257
}
252258

259+
extension MovieMetadataKey : Sendable {}
260+
253261

254262

255263
public struct MovieKey {
@@ -304,6 +312,8 @@ extension MovieKey : Hashable {
304312
}
305313
}
306314

315+
extension MovieKey : Sendable {}
316+
307317

308318

309319
public struct ReviewKey {
@@ -374,6 +384,8 @@ extension ReviewKey : Hashable {
374384
}
375385
}
376386

387+
extension ReviewKey : Sendable {}
388+
377389

378390

379391
public struct UserKey {
@@ -428,4 +440,6 @@ extension UserKey : Hashable {
428440
}
429441
}
430442

443+
extension UserKey : Sendable {}
444+
431445

0 commit comments

Comments
 (0)