Skip to content

Instantly share code, notes, and snippets.

@saroar
Created February 9, 2024 17:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saroar/3b7f76d2b79e347466eeca98c85541d4 to your computer and use it in GitHub Desktop.
Save saroar/3b7f76d2b79e347466eeca98c85541d4 to your computer and use it in GitHub Desktop.
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