Skip to content

Commit

Permalink
Merge pull request #37 from 0x1-company/create-post
Browse files Browse the repository at this point in the history
feat: create post
  • Loading branch information
tomokisun authored Dec 27, 2023
2 parents 5b1c2d7 + 55b0128 commit cb67e73
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 49 deletions.
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

0 comments on commit cb67e73

Please sign in to comment.