Created
February 9, 2024 17:51
-
-
Save saroar/3b7f76d2b79e347466eeca98c85541d4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import BSON | |
import NukeUI | |
import SwiftUI | |
import Foundation | |
import LPGSharedModels | |
import LocationReducer | |
import UserDefaultsClient | |
import ComposableArchitecture | |
import ComposableCoreLocation | |
/// Have to think link here | |
extension AttachmentInOutPut { | |
var imageURL: URL? { URL(string: self.imageUrlString ?? "think about this link") } | |
} | |
@Reducer | |
public struct ImageGalleryReducer { | |
@ObservableState | |
public struct State: Equatable { | |
public init( | |
id: ObjectId, | |
attachments: IdentifiedArrayOf<AttachmentInOutPut> = [], | |
isDetailView: Bool = false, | |
selectedImageUrl: URL? = nil | |
) { | |
self.id = id | |
self.attachments = attachments | |
self.isDetailView = isDetailView | |
self.selectedImageUrl = selectedImageUrl | |
} | |
// make it as SwapId when we are in SwapsView | |
// we can navigate SwapDetailView | |
public var id: ObjectId | |
public var isDetailView: Bool = false | |
public var attachments: IdentifiedArrayOf<AttachmentInOutPut> = [] | |
// This is the stored property that actually holds the selected image URL. | |
fileprivate var _selectedImageUrl: URL? | |
// This computed property manages the logic for the selected image URL. | |
fileprivate var selectedImageUrl: URL? { | |
get { | |
// If `_selectedImageUrl` is nil, return the first image URL from attachments. | |
_selectedImageUrl ?? attachments.first(where: { $0.type == .image })?.imageURL | |
} | |
set { | |
// When `selectedImageUrl` is set, store the new value in `_selectedImageUrl`. | |
_selectedImageUrl = newValue | |
} | |
} | |
var igLinks: [URL] { | |
return self.attachments | |
.filter { $0.type == .image } | |
.compactMap { $0.imageURL } | |
} | |
} | |
@CasePathable | |
public enum Action: Equatable { | |
case onAppear | |
case updateImage(image: URL) | |
case moveToDetailsView | |
} | |
public init() {} | |
public var body: some ReducerOf<Self> { | |
Reduce(self.core) | |
} | |
func core(state: inout State, action: Action) -> Effect<Action> { | |
switch action { | |
case .onAppear: | |
return .none | |
case .updateImage(image: let url): | |
state._selectedImageUrl = url | |
return .none | |
case .moveToDetailsView: | |
return .none | |
} | |
} | |
} | |
public struct ImageGalleryView: View { | |
private var gridLayout = [ GridItem() ] | |
private var threeColumnGrid = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())] | |
let store: StoreOf<ImageGalleryReducer> | |
public init(store: StoreOf<ImageGalleryReducer>) { | |
self.store = store | |
} | |
public var body: some View { | |
WithPerceptionTracking { | |
VStack { | |
Button(action: { | |
self.store.send(.moveToDetailsView) | |
}) { | |
LazyImage(request: ImageRequest(url: store.selectedImageUrl)) { state in | |
if let image = state.image { | |
image | |
.resizable() | |
.scaledToFill() | |
.frame(minWidth: 0, maxWidth: .infinity) | |
.frame(maxHeight: 250) | |
.cornerRadius(10) | |
.shadow(color: Color.primary.opacity(0.3), radius: 1) | |
.shadow(color: Color.black.opacity(0.6), radius: 8, x: 0, y: 5) | |
.padding(.bottom, 5) | |
} else if state.error != nil { | |
Color.blue | |
.frame(minWidth: 0, maxWidth: .infinity) | |
.frame(minHeight: 250 ,maxHeight: 250) | |
.cornerRadius(10) | |
.shadow(color: Color.primary.opacity(0.3), radius: 1) | |
.shadow(color: Color.black.opacity(0.6), radius: 8, x: 0, y: 5) | |
.padding(.bottom, 5) | |
} else { | |
Color.blue | |
.frame(minWidth: 0, maxWidth: .infinity) | |
.frame(minHeight: 250 ,maxHeight: 250) | |
.cornerRadius(10) | |
.shadow(color: Color.primary.opacity(0.3), radius: 1) | |
.shadow(color: Color.black.opacity(0.6), radius: 8, x: 0, y: 5) | |
.padding(.bottom, 5) | |
} | |
} | |
} | |
.allowsHitTesting(!self.store.isDetailView) | |
LazyVGrid(columns: [GridItem(.adaptive(minimum: 50))]) { | |
ForEach(store.igLinks, id: \.self) { igLink in | |
Button(action: { | |
self.store.send(.updateImage(image: igLink)) | |
}) { | |
LazyImage(request: ImageRequest(url: igLink)) { state in | |
if let image = state.image { | |
image | |
.resizable() | |
.scaledToFill() | |
.frame(minWidth: 0, maxWidth: .infinity) | |
.frame(height: 50) | |
.cornerRadius(10) | |
} else { | |
Color.blue | |
.scaledToFill() | |
.frame(minWidth: 0, maxWidth: .infinity) | |
.frame(height: 50) | |
.cornerRadius(10) | |
} | |
} | |
} | |
} | |
} | |
.frame(minHeight: 0, maxHeight: .infinity, alignment: .top) | |
.padding(.bottom, 16) | |
} | |
.onAppear { | |
store.send(.onAppear) | |
} | |
.padding(.horizontal) | |
} | |
} | |
private func formattedDate(_ date: Date, isDetailsView: Bool) -> String { | |
let formatter = DateFormatter() | |
formatter.dateStyle = .medium // Choose the style that fits your needs | |
if isDetailsView { | |
formatter.timeStyle = .medium | |
} | |
return formatter.string(from: date) | |
} | |
} | |
#if DEBUG | |
struct ImageGalleryView_Previews: PreviewProvider { | |
static var previews: some View { | |
ImageGalleryView( | |
store: .init( | |
initialState: .init( | |
id: .init(), | |
attachments: .init(uniqueElements: [ | |
AttachmentInOutPut.image1, | |
AttachmentInOutPut.image2, | |
AttachmentInOutPut.image3, | |
AttachmentInOutPut.image4 | |
]) | |
) | |
) { | |
ImageGalleryReducer() | |
} | |
) | |
.frame(width: 200, height: 150) | |
} | |
} | |
#endif | |
@Reducer | |
public struct ChatRow { | |
@ObservableState | |
public struct State: Equatable, Identifiable { | |
public var messageItem: MessageItem | |
public var id: ObjectId { messageItem.id } | |
public var createdAt: Date? { messageItem.createdAt } | |
public var currentUser: UserOutput | |
public var isCurrentUser: Bool { | |
currentUser.id == messageItem.sender.id | |
} | |
public var imageGalleryState: ImageGalleryReducer.State? | |
} | |
public enum Action: Equatable { | |
case imageGalleryAction(ImageGalleryReducer.Action) | |
} | |
@Dependency(\.keychainClient) var keychainClient | |
@Dependency(\.build) var build | |
public var body: some Reducer<State, Action> { | |
Reduce(self.core) | |
.ifLet(\.imageGalleryState, action: \.imageGalleryAction) { | |
ImageGalleryReducer() | |
} | |
} | |
func core(state: inout State, action: Action) -> Effect<Action> { | |
switch action { | |
case .imageGalleryAction: | |
return .none | |
} | |
} | |
} | |
struct ChatRowView: View { | |
@Environment(\.colorScheme) var colorScheme | |
let store: StoreOf<ChatRow> | |
var body: some View { | |
WithPerceptionTracking { | |
switch store.messageItem.messageType { | |
case .text: | |
Group { | |
if store.isCurrentUser { | |
OpponentUsersRowView(store: store) | |
.listRowSeparatorHidden() | |
} else { | |
CurrentUserRowView(store: store) | |
.listRowSeparatorHidden() | |
} | |
} | |
case .audio: | |
Group { | |
if store.isCurrentUser { | |
Image(systemName: "headphones") | |
} else { | |
Image(systemName: "headphones.fill") | |
} | |
} | |
case .video: | |
Group { | |
if store.isCurrentUser { | |
Image(systemName: "person.crop.square.badge.video") | |
} else { | |
Image(systemName: "person.crop.square.badge.video.fill") | |
} | |
} | |
case .image: | |
Group { | |
if store.isCurrentUser { | |
Image(systemName: "text.below.photo") | |
} else { | |
Image(systemName: "text.below.photo.fill") | |
} | |
} | |
case .product: | |
Group { | |
if store.isCurrentUser { | |
if let childStore = self.store.scope( | |
state: \.imageGalleryState, | |
action: \.imageGalleryAction | |
) { | |
ImageGalleryView(store: childStore) | |
} else { | |
Text("Nothing to show") | |
} | |
} else { | |
if let childStore = self.store.scope( | |
state: \.imageGalleryState, | |
action: \.imageGalleryAction | |
) { | |
ImageGalleryView(store: childStore) | |
} else { | |
Text("Nothing to show") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
ChatRowView( | |
store: Store( | |
initialState: ChatRow.State( | |
messageItem: MessageItem.generateMockMessages(count: 9)[6], | |
currentUser: .withNumber, | |
imageGalleryState: .init(id: .init()) | |
) | |
) { | |
ChatRow() | |
} | |
) | |
} | |
struct AvatarView: View { | |
@Environment(\.colorScheme) var colorScheme | |
let store: StoreOf<ChatRow> | |
var body: some View { | |
WithPerceptionTracking { | |
if let avatarUrl = self.store.messageItem.sender.getLastImageAttachmentURLString() { | |
LazyImage(request: ImageRequest(url: URL(string: avatarUrl)!)) { state in | |
if let image = state.image { | |
image.resizable() | |
.aspectRatio(contentMode: .fill) | |
.frame(maxWidth: 40, maxHeight: 40) | |
.clipShape(Circle()) | |
} else if state.error != nil { | |
// Image(systemName: "photo") | |
Color.red // Indicates an error. | |
} else { | |
Color.blue // Acts as a placeholder. | |
} | |
} | |
.aspectRatio(contentMode: .fit) | |
.frame(width: 40, height: 40) | |
.clipShape(Circle()) | |
.padding(.trailing, 5) | |
} else { | |
Image(systemName: "person.fill") | |
.font(.title2) | |
.aspectRatio(contentMode: .fit) | |
.frame(width: 40, height: 40) | |
.foregroundColor(Color.backgroundColor(for: self.colorScheme)) | |
.clipShape(Circle()) | |
.overlay(Circle().stroke(Color.black, lineWidth: 1)) | |
.padding(.trailing, 5) | |
} | |
} | |
} | |
} | |
struct OpponentUsersRowView: View { | |
@Environment(\.colorScheme) var colorScheme | |
let store: StoreOf<ChatRow> | |
var body: some View { | |
WithPerceptionTracking { | |
HStack { | |
Group { | |
Spacer() | |
Text(self.store.messageItem.messageBody) | |
.bold() | |
.foregroundColor(Color.white) | |
.padding(10) | |
.background(Color.red) | |
.cornerRadius(10) | |
AvatarView(store: store) | |
} | |
} | |
.background(Color(.systemBackground)) | |
} | |
} | |
} | |
struct CurrentUserRowView: View { | |
let store: StoreOf<ChatRow> | |
var body: some View { | |
WithPerceptionTracking { | |
HStack { | |
Group { | |
AvatarView(store: store) | |
Text(self.store.messageItem.messageBody) | |
.bold() | |
.padding(10) | |
.foregroundColor(Color.white) | |
.background(Color.blue) | |
.cornerRadius(10) | |
} | |
.background(Color(.systemBackground)) | |
Spacer() | |
} | |
.background(Color(.systemBackground)) | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment