Skip to content

Commit

Permalink
wip(ios): introduce PhotoCrop reducer to start working on crop tool
Browse files Browse the repository at this point in the history
  • Loading branch information
autoreleasefool committed Jul 6, 2024
1 parent 527c5da commit 98f2e47
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 10 deletions.
16 changes: 10 additions & 6 deletions ios/Approach/Sources/AvatarEditorFeature/AvatarEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ public struct AvatarEditor: Reducer {
public let isPhotoAvatarsEnabled: Bool

public init(avatar: Avatar.Summary?) {
@Dependency(\.featureFlags) var featureFlags
let isPhotoAvatarsEnabled = featureFlags.isFlagEnabled(.photoAvatars)
self.isPhotoAvatarsEnabled = isPhotoAvatarsEnabled

@Dependency(\.uuid) var uuid
self.id = avatar?.id ?? uuid()
self.initialAvatar = avatar
self.avatarKind = avatar?.kind ?? .text
self.text = TextAvatarEditor.State(avatar: avatar)
self.photo = PhotoAvatarEditor.State(avatar: avatar)

@Dependency(\.featureFlags) var featureFlags
let isPhotoAvatarsEnabled = featureFlags.isFlagEnabled(.photoAvatars)
self.isPhotoAvatarsEnabled = isPhotoAvatarsEnabled
if isPhotoAvatarsEnabled {
self.avatarKind = avatar?.kind ?? .text
} else {
self.avatarKind = .text
}
}

var hasChanges: Bool {
Expand Down Expand Up @@ -168,7 +172,7 @@ public struct AvatarEditor: Reducer {
extension Color {
var rgb: Avatar.Background.RGB {
let (red, green, blue, _) = UIColor(self).rgba
return .init(red, green, blue)
return Avatar.Background.RGB(red, green, blue)
}
}

Expand Down
32 changes: 28 additions & 4 deletions ios/Approach/Sources/AvatarEditorFeature/PhotoAvatarEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public struct PhotoAvatarEditor: Reducer {
public struct State: Equatable {
public var imageState: ImageState
public var photosPickerItem: PhotosPickerItem?
@Presents public var photoCrop: PhotoCrop.State?

var value: Avatar.Value? {
if let data = imageState.photoData {
Expand Down Expand Up @@ -43,6 +44,7 @@ public struct PhotoAvatarEditor: Reducer {
@CasePathable public enum Internal {
case didStartLoadingPhoto
case didLoadPhoto(Result<PhotoData?, Error>)
case photoCrop(PresentationAction<PhotoCrop.Action>)
}

case view(View)
Expand Down Expand Up @@ -112,27 +114,44 @@ public struct PhotoAvatarEditor: Reducer {
return .none

case let .didLoadPhoto(.success(photoData)):
state.imageState = if let photoData {
.success(photoData)
if let photoData {
state.photoCrop = PhotoCrop.State(image: photoData.image)
} else {
.empty
state.imageState = .empty
}
return .none

case let .didLoadPhoto(.failure(error)):
state.imageState = .failure(.init(error))
state.imageState = .failure(AlwaysEqual(error))
return .none

case let .photoCrop(.presented(.delegate(.didFinishCropping(image)))):
guard let data = image.pngData() else {
state.imageState = .empty
return .none
}

state.imageState = .success(PhotoData(data: data, image: image))
return .none

case .photoCrop(.dismiss),
.photoCrop(.presented(.view)), .photoCrop(.presented(.binding)), .photoCrop(.presented(.internal)):
return .none
}

case .delegate, .binding:
return .none
}
}
.ifLet(\.$photoCrop, action: \.internal.photoCrop) {
PhotoCrop()
}
}

private func loadTransferable(from imageSelection: PhotosPickerItem) async throws -> PhotoData? {
let avatarImage = try await imageSelection.loadTransferable(type: AvatarImage.self)
guard let avatarImage else { return nil }
try await Task.sleep(for: .seconds(0.5))
return PhotoData(data: avatarImage.data, image: avatarImage.image)
}
}
Expand All @@ -154,5 +173,10 @@ public struct PhotoAvatarEditorView: View {
photoLibrary: .shared()
)
}
.sheet(item: $store.scope(state: \.photoCrop, action: \.internal.photoCrop)) { store in
NavigationStack {
PhotoCropView(store: store)
}
}
}
}
77 changes: 77 additions & 0 deletions ios/Approach/Sources/AvatarEditorFeature/PhotoCrop.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import ComposableArchitecture
import FeatureActionLibrary
import SwiftUI

@Reducer
public struct PhotoCrop: Reducer {
@ObservableState
public struct State: Equatable {
public var image: UIImage
public var offset: CGSize = .zero

public init(image: UIImage) {
self.image = image
}
}

public enum Action: ViewAction, FeatureAction, BindableAction {
@CasePathable public enum View {
case didTapDone
}

@CasePathable public enum Delegate {
case didFinishCropping(UIImage)
}

@CasePathable public enum Internal {
case doNothing
}

case view(View)
case delegate(Delegate)
case `internal`(Internal)
case binding(BindingAction<State>)
}

public init() {}

public var body: some ReducerOf<Self> {
BindingReducer()

Reduce<State, Action> { state, action in
switch action {
case let .view(viewAction):
switch viewAction {
case .didTapDone:
return .none
}

case let .internal(internalAction):
switch internalAction {
case .doNothing:
return .none
}

case .delegate, .binding:
return .none
}
}
}
}

@ViewAction(for: PhotoCrop.self)
public struct PhotoCropView: View {
@Bindable public var store: StoreOf<PhotoCrop>

public init(store: StoreOf<PhotoCrop>) {
self.store = store
}

public var body: some View {
ZStack {
Image(uiImage: store.image)
.resizable()
.scaledToFit()
}
}
}

0 comments on commit 98f2e47

Please sign in to comment.