Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create post #37

Merged
merged 5 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions Packages/FlyCam/GraphQL/Mutations/CreatePost.graphql
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# import RankingRow from '../fragments/RankingRow.graphql'

mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
altitude
videoUrl
user {
id
displayName
}
...RankingRow
}
}
2 changes: 2 additions & 0 deletions Packages/FlyCam/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ let package = Package(
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]),
.target(name: "CameraResultFeature", dependencies: [
"FlyCamClient",
.product(name: "AnalyticsClient", package: "SDK"),
.product(name: "FirebaseStorageClient", package: "SDK"),
.product(name: "FeedbackGeneratorClient", package: "SDK"),
.product(name: "AVPlayerNotificationClient", package: "SDK"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
Expand Down
3 changes: 0 additions & 3 deletions Packages/FlyCam/Sources/CameraFeature/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ public struct CameraLogic {
}
return .none

case .child(.result(.sendButtonTapped)):
return .send(.delegate(.dismiss), animation: .default)

default:
return .none
}
Expand Down
73 changes: 64 additions & 9 deletions Packages/FlyCam/Sources/CameraResultFeature/CameraResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import AVKit
import AVPlayerNotificationClient
import ComposableArchitecture
import FeedbackGeneratorClient
import FirebaseStorageClient
import FlyCam
import FlyCamClient
import SwiftUI

@Reducer
Expand All @@ -10,23 +13,41 @@ public struct CameraResultLogic {

public struct State: Equatable {
let altitude: Double
let videoURL: URL
let player: AVPlayer

var isActivityIndicatorVisible = false

public init(altitude: Double, videoURL: URL) {
self.altitude = altitude
self.videoURL = videoURL
player = AVPlayer(url: videoURL)
}
}

public enum Action: Equatable {
public enum Action {
case onTask
case sendButtonTapped
case didPlayToEndTime
case uploadResponse(Result<URL, Error>)
case createPostResponse(Result<FlyCam.CreatePostMutation.Data, Error>)
case delegate(Delegate)

public enum Delegate: Equatable {
case sendCompleted
}
}

@Dependency(\.uuid) var uuid
@Dependency(\.flycam.createPost) var createPost
@Dependency(\.firebaseStorage) var firebaseStorage
@Dependency(\.feedbackGenerator) var feedbackGenerator
@Dependency(\.avplayerNotification.didPlayToEndTimeNotification) var didPlayToEndTimeNotification

enum Cancel {
case createPost
}

public var body: some Reducer<State, Action> {
Reduce<State, Action> { state, action in
switch action {
Expand All @@ -39,14 +60,43 @@ public struct CameraResultLogic {
}

case .sendButtonTapped:
return .run { _ in
let path = "video/quicktime/\(uuid().uuidString).mov"

state.isActivityIndicatorVisible = true
return .run { [videoURL = state.videoURL] send in
let data = try Data(contentsOf: videoURL)
await feedbackGenerator.impactOccurred()
await send(.uploadResponse(Result {
try await firebaseStorage.uploadMov(path: path, uploadData: data)
}))
}

case .didPlayToEndTime:
state.player.seek(to: CMTime.zero)
state.player.play()
return .none

case let .uploadResponse(.success(url)):
let input = FlyCam.CreatePostInput(
altitude: state.altitude,
videoUrl: url.absoluteString
)
return .run { send in
await send(.createPostResponse(Result {
try await createPost(input)
}))
}

case .uploadResponse(.failure):
state.isActivityIndicatorVisible = false
return .none

case .createPostResponse:
state.isActivityIndicatorVisible = false
return .send(.delegate(.sendCompleted), animation: .default)

default:
return .none
}
}
}
Expand All @@ -68,15 +118,20 @@ public struct CameraResultView: View {
.clipShape(RoundedRectangle(cornerRadius: 24))

VStack(spacing: 16) {
Text("\(viewStore.altitude)メートル")
Text("\(viewStore.altitude)Meter", bundle: .module)

Button {
store.send(.sendButtonTapped)
} label: {
HStack(spacing: 4) {
Text("Send")
if viewStore.isActivityIndicatorVisible {
ProgressView()
.tint(Color.primary)
} else {
Button {
store.send(.sendButtonTapped)
} label: {
HStack(spacing: 4) {
Text("Send", bundle: .module)

Image(systemName: "paperplane.fill")
Image(systemName: "paperplane.fill")
}
}
}
}
Expand Down
20 changes: 17 additions & 3 deletions Packages/FlyCam/Sources/CameraResultFeature/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
{
"sourceLanguage" : "en",
"strings" : {
"%lfメートル" : {

"%lfMeter" : {
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lfメートル"
}
}
}
},
"Send" : {

"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "送信"
}
}
}
}
},
"version" : "1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ public struct DisplayNameEditLogic {

case .updateDisplayNameResponse:
state.isActivityIndicatorVisible = false
return .none
return .run { send in
await dismiss()
}

default:
return .none
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public extension FlyCam {
public static let operationName: String = "CreatePost"
public static let operationDocument: ApolloAPI.OperationDocument = .init(
definition: .init(
#"mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { __typename id altitude videoUrl user { __typename id displayName } } }"#
#"mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { __typename ...RankingRow } }"#,
fragments: [RankingRow.self]
))

public var input: CreatePostInput
Expand Down Expand Up @@ -41,33 +42,19 @@ public extension FlyCam {
public static var __parentType: ApolloAPI.ParentType { FlyCam.Objects.Post }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", FlyCam.ID.self),
.field("altitude", Double.self),
.field("videoUrl", String.self),
.field("user", User.self),
.fragment(RankingRow.self),
] }

public var id: FlyCam.ID { __data["id"] }
public var altitude: Double { __data["altitude"] }
public var videoUrl: String { __data["videoUrl"] }
public var user: User { __data["user"] }
public var user: RankingRow.User { __data["user"] }

/// CreatePost.User
///
/// Parent Type: `User`
public struct User: FlyCam.SelectionSet {
public struct Fragments: FragmentContainer {
public let __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

public static var __parentType: ApolloAPI.ParentType { FlyCam.Objects.User }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("id", FlyCam.ID.self),
.field("displayName", String.self),
] }

public var id: FlyCam.ID { __data["id"] }
public var displayName: String { __data["displayName"] }
public var rankingRow: RankingRow { _toFragment() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ public struct RootNavigationLogic {
case .camera(.delegate(.dismiss)):
state.camera = nil
return .none

case .camera(.child(.result(.delegate(.sendCompleted)))):
state.camera = nil
return RankingLogic()
.reduce(into: &state.ranking, action: .refresh)
.map(Action.ranking)

default:
return .none
Expand Down
25 changes: 19 additions & 6 deletions Packages/FlyCam/Sources/RankingFeature/Ranking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public struct RankingLogic {
public enum Action {
case onTask
case onAppear
case refresh
case list(RankingListLogic.Action)
case rankingResponse(Result<FlyCam.RankingQuery.Data, Error>)
}
Expand All @@ -34,17 +35,17 @@ public struct RankingLogic {
switch action {
case .onTask:
return .run { send in
for try await data in flycam.ranking() {
await send(.rankingResponse(.success(data)))
}
} catch: { error, send in
await send(.rankingResponse(.failure(error)))
await rankingRequest(send: send)
}
.cancellable(id: Cancel.ranking, cancelInFlight: true)

case .onAppear:
analytics.logScreen(screenName: "Ranking", of: self)
return .none

case .refresh:
return .run { send in
await rankingRequest(send: send)
}

case let .rankingResponse(.success(data)):
let banners = data.banners
Expand All @@ -70,6 +71,18 @@ public struct RankingLogic {
RankingListLogic()
}
}

func rankingRequest(send: Send<Action>) async {
await withTaskCancellation(id: Cancel.ranking, cancelInFlight: true) {
do {
for try await data in flycam.ranking() {
await send(.rankingResponse(.success(data)))
}
} catch {
await send(.rankingResponse(.failure(error)))
}
}
}
}

public struct RankingView: View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public struct RankingRowLogic {

public init(state: EnumeratedSequence<[FlyCam.RankingRow]>.Iterator.Element) {
id = state.element.id
rank = state.offset
rank = state.offset + 1
altitude = state.element.altitude
displayName = state.element.user.displayName
}
Expand Down
1 change: 1 addition & 0 deletions Packages/SDK/Sources/FirebaseStorageClient/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import Foundation
@DependencyClient
public struct FirebaseStorageClient: Sendable {
public var upload: @Sendable (_ path: String, _ uploadData: Data) async throws -> URL
public var uploadMov: @Sendable (_ path: String, _ uploadData: Data) async throws -> URL
}
7 changes: 7 additions & 0 deletions Packages/SDK/Sources/FirebaseStorageClient/LiveKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ extension FirebaseStorageClient: DependencyKey {
metadata.contentType = "image/png"
_ = try await reference.putDataAsync(uploadData, metadata: metadata, onProgress: nil)
return try await reference.downloadURL()
},
uploadMov: { path, uploadData in
let reference = storage.reference().child(path)
let metadata = StorageMetadata()
metadata.contentType = "video/quicktime"
_ = try await reference.putDataAsync(uploadData, metadata: metadata, onProgress: nil)
return try await reference.downloadURL()
}
)
}()
Expand Down