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: 1.0.6 버전 업데이트에 따른 기능 추가 및 수정 #166

Merged
merged 31 commits into from
Dec 4, 2024

Conversation

ShapeKim98
Copy link
Contributor

#️⃣연관된 이슈

#157

📝작업 내용

  • 링크 미리보기 스켈레톤 ui 적용
  • PokitIconLInput, PokitIconRInputPokitTextInput으로 통일
  • 텍스트 필드를 쓰는 모든 기능에 텍스트 지우기 버튼 추가
  • 컨텐츠 추가에서 포킷 추가 버튼 변경사항 반영
  • PokitLinkPopup 변경
  • 컨텐츠 상세 플로우 변경사항 반영
  • PokitListButton 추가
  • ContentDetail 기능 변경사항 반영
  • PokitTextArea에 메모 상태 추가
  • dismissKeyboard modifer 추가
  • ContentCard에 즐겨찾기 수정 기능 추가
  • 재파싱한 썸네일 url 업데이트 API 적용
  • Lazy한 뷰에서 Store Scope시 발생하는 스레드 안정성 문제 수정

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

스크린샷 (선택)

Simulator Screen Recording - iPhone 16 Pro - 2024-12-02 at 22 22 15 Simulator Screen Recording - iPhone 16 Pro - 2024-12-02 at 22 24 27
Simulator Screen Recording - iPhone 16 Pro - 2024-12-02 at 22 25 33 Simulator Screen Recording - iPhone 16 Pro - 2024-12-02 at 22 23 57
--- ---

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

PokitIconLInput, PokitIconRInputPokitTextInput으로 통일하였습니다

  • 최근 PM 요청 사항으로 모든 텍스트 필드에 텍스트 지우기 버튼이 있었으면 좋겠다는 요청이 있어 이를 반영중에 기존 텍스트 필드가 너무 유연하지 않다는 것을 깨달았습니다.
    기존 PokitIconL/RInputPokitTextInput과 상당히 달랐고, 지우기 버튼 하나 넣는데에 코드를 좀 바꿨어야 했습니다. 그래서 PokitTextInput으로 통일했습니다.
  • 그동안 디자인 시스템을 너무 막 만들었단 생각이 많이 드는거 같아요... 많이 반성하고 있습니다..ㅠ 추후에 버튼 컴포넌트도 통일할 예정입니다.
PokitTextInput(
    text: $store.text,
    type: store.text.isEmpty ? .text : .iconR(
        icon: .icon(.x),
        action: { send(.닉네임지우기_버튼_눌렀을때) }
    ),
    shape: .rectangle,
    state: $store.textfieldState,
    info: "한글, 영어, 숫자로만 입력이 가능합니다.",
    maxLetter: 10,
    focusState: $isFocused,
    equals: true
)

PokitTextInput(
    text: $store.nicknameText,
    shape: .rectangle,
    state: $store.textfieldState,
    info: "한글, 영어, 숫자로만 입력이 가능합니다.",
    maxLetter: 10,
    focusState: $isFocused,
    equals: true
)

PokitLinkPopup 변경

  • PokitLinkPopup도 위와 같은 이유로 좀 더 유연하게 바꿨습니다.
PokitLinkPopup(
    type: .constant(.link(
        title: "복사한 링크 저장하기",
        url: "https://www.youtube.com/watch?v=xSTwqKUyM8k"
    ))
)

PokitLinkPopup(
    type: .constant(.text(title: "최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요."))
)

PokitLinkPopup(
    type: .constant(.success(title: "링크저장 완료"))
)

PokitLinkPopup(
    type: .constant(.error(title: "링크저장 실패"))
)

PokitLinkPopup(
    type: .constant(.warning(title: "저장공간 부족"))
)

PokitListButton 컴포넌트를 새로 추가하였습니다.

  • PokitListButton은 피그마 컴포넌트 페이지에 있는 list_ver2 컴포넌트 인데요, 이것 역시 기존 PokitBottomSheet가 너무 유연하지 않다는 생각이 들어 새로 추가하였습니다.
    PokitBottomSheet는 디자이너들도 컴포넌트에 잘못 만들었다고 해주셔서 점차 PokitListButton으로 대체하겠습니다.
#Preview {
    @Previewable
    @State var isOn: Bool = false
    
    PokitListButton(
        title: "공지사항",
        type: .default(icon: .icon(.arrowRight)),
        action: { }
    )
    
    PokitListButton(
        title: "공지사항",
        type: .bottomSheet(icon: .icon(.edit)),
        action: { }
    )
    
    PokitListButton(
        title: "공지사항",
        type: .subText(
            icon: .icon(.arrowRight),
            subeText: "포킷에 저장된 링크가 다른 사용자에게 추천됩니다."
        ),
        action: { }
    )
    
    PokitListButton(
        title: "공지사항",
        type: .toggle(subeText: "포킷에 저장된 링크가 다른 사용자에게 추천됩니다."),
        isOn: $isOn,
        action: { }
    )
}

dismissKeyboard modifer 추가하였습니다.

  • 키보드 외 영역을 터치하면 키보드가 내려가는 역할을 하며, Modifier이긴 하나, 디자인적 요소보단 기능적 요소에 가까운것 같아 Util에 구현하였습니다. 의견 있으시면 말씀해주세여
extension View {
    public func dismissKeyboard(
        focused: FocusState<Bool>.Binding
    ) -> some View {
        self
            .overlay {
                if focused.wrappedValue {
                    Color.clear
                        .contentShape(Rectangle())
                        .onTapGesture {
                            focused.wrappedValue = false
                        }
                }
            }
    }
    
    public func dismissKeyboard<Value: Hashable>(
        focused: FocusState<Value?>.Binding
    ) -> some View {
        self
            .overlay {
                if focused.wrappedValue != nil {
                    Color.clear
                        .contentShape(Rectangle())
                        .onTapGesture {
                            focused.wrappedValue = nil
                        }
                }
            }
    }
}

재파싱한 썸네일 url 업데이트 API 적용하였습니다.

  • 기존에 인스타그램에 썸네일 조회 이슈가 있어 조회가 안될때마다 썸네일을 새로 파싱하는 로직을 추가하였습니다. Fix/#152 인스타그램 이미지 조회 문제 수정 #155
    하지만, 우리 서버에 새로 파싱한 썸네일 url을 보내지 않고 내부적으로만 사용하다 보니 서버의 썸네일 url과 새로 파싱한 썸네일 url이 달라 이미지 캐싱 적용이 되지않는다는 문제가 있었습니다.
    그래서 서버에 썸네일 업데이트 API를 요청하여 적용하였습니다.

Lazy한 뷰에서 Store Scope시 발생하는 스레드 안정성 문제 수정하였습니다.

  • ContentCardFeature를 새로 추가하면서 Lazy한 뷰에 Store를 사용할 일이 발생하였는데, 아래와 같은 경고를 마주쳤습니다.
스크린샷 2024-12-01 17 51 38 알려주신 코드를 통해 위와 같은 문제를 수정하였습니다.

코드

close #157

@ShapeKim98 ShapeKim98 added Feat 기능구현 Design 🎨 디자인 작업 Fix 기능 수정 labels Dec 2, 2024
@ShapeKim98 ShapeKim98 requested a review from stealmh December 2, 2024 14:05
@ShapeKim98 ShapeKim98 self-assigned this Dec 2, 2024
@stealmh
Copy link
Contributor

stealmh commented Dec 3, 2024

고생많으셨습니다 천천히 살펴볼게요~!

Comment on lines +102 to +103
if state != .memo(isReadOnly: true) &&
state != .memo(isReadOnly: false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요건 무슨조건인가요? state가 memo가 아닐 때 맞나요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 맞습니다! 디자인 보면 메모 표시할 땐 텍스트 카운트가 없어서 넣었습니다

Copy link
Contributor

@stealmh stealmh Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 그렇다면 요렇게 표현할 수도 있을 것 같아요
연관값이 반드시 필요한 것은 아니라고 느껴서 끄적여봤습니다 😃

/// - `memo`일 때 제외하고 `textCount`를 보여줌
if case .memo = state { }
else { textCount.pokitBlurReplaceTransition(.pokitDissolve) }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 넵 알겠습니다!

}
.padding(.top, 4)
}

private var textCount: some View {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네이밍이 Int Type같아요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음.. 한번 고민해보겠습니다

Comment on lines 145 to 152
if let id = state.domain.contentId {
return .send(.async(.컨텐츠_상세_조회_API(id: id)))
} else if let content = state.domain.content {
state.memo = content.memo
return .none
} else {
return .none
}
Copy link
Contributor

@stealmh stealmh Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case .뷰가_나타났을때:
if let id = state.domain.contentId {
    return .send(.async(.컨텐츠_상세_조회_API(id: id)))
} 

if let content = state.domain.content {
    state.memo = content.memo
    return .none
} 

return .none

요렇게 바꿔도 좋을 것 같아요 가독성이 글쎄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!

Comment on lines +172 to +183
PokitListButton(
title: "즐겨찾기",
type: .bottomSheet(
icon: favorites
? .icon(.starFill)
: .icon(.star),
iconColor: favorites
? .pokit(.icon(.brand))
: .pokit(.icon(.primary))
),
action: { send(.즐겨찾기_버튼_눌렀을때) }
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 느낀 디자인시스템은 모든곳에서 사용하기 위해서 많이(광적으로..?) 추상화되어있다고 생각이 들어요
이런부분에서는 구체화해서 내부로 숨겨버리는게 더 깔끔하지 않나 하는 제 의견입니다..

디쟌시스템 관련해서 길게 생각해보시져 같이!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 좋습니다...!!


return isEdit
? .send(.async(.컨텐츠_수정_API))
: .send(.async(.컨텐츠_추가_API))
case .포킷추가_버튼_눌렀을때:
guard state.domain.categoryTotalCount < 30 else {
/// 🚨 Error Case [1]: 포킷 갯수가 30개 이상일 경우
state.showMaxCategoryPopup = true
state.linkPopup = .text(title: "최대 30개의 포킷을 생성할 수 있습니다.\n포킷을 삭제한 뒤에 추가해주세요.")
Copy link
Contributor

@stealmh stealmh Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요런 타이틀들 Constants로 묶어서 관리하는게 유지보수하기 편해보여요
String extension이나 이런저런 많은 방법이 있을듯!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오.. 반영해보겠습니다!

state.linkImageURL = imageURL
let contentTitle = state.title.isEmpty ? "제목을 입력해주세요" : state.title
state.linkTitle = title ?? contentTitle
state.linkImageURL = imageURL ?? "https://pokit-storage.s3.ap-northeast-2.amazonaws.com/logo/pokit.png"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요것두 url 따로 관리

Comment on lines 445 to 450
} catch: { error, send in
guard let errorResponse = error as? ErrorResponse else { return }
await send(
.inner(.링크팝업_활성화(.error(title: errorResponse.message))),
animation: .pokitSpring
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catch문보다가 문득 든 생각이 요런것두 액션으로 받아서 한번에 처리하는거 어떠신가요??

enum InnerAction {
   case error(Error) // or ErrorResponse
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 좋아여

Comment on lines -53 to +52
if store.state.showMaxCategoryPopup {
if store.linkPopup != nil {
PokitLinkPopup(
"최대 30개의 포킷을 생성할 수 있습니다. \n포킷을 삭제한 뒤에 추가해주세요.",
isPresented: $store.showMaxCategoryPopup,
type: .text
)
.animation(.pokitSpring, value: store.showMaxCategoryPopup)
} else if store.state.showDetectedURLPopup {
PokitLinkPopup(
"복사한 링크 저장하기",
isPresented: $store.showDetectedURLPopup,
type: .link(url: store.link ?? ""),
action: { send(.링크복사_버튼_눌렀을때, animation: .pokitSpring) }
type: $store.linkPopup,
action: { send(.링크팝업_버튼_눌렀을때, animation: .pokitSpring) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PokitLinkPopup 내부에서도 type에 대해 optional unwrapping을 하는 것으로 보이는데 내부적 처리로는 동작을 제대로 하지 못해서 외부에서도 nil 여부를 한번더 체크하는건가요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.onReceive(timer) { _ in
    guard second < 2 && type != nil else {
        closedPopup()
        return
    }
    second += 1
}

혹시 내부적 처리라는게 이걸 뜻하는건가요? 저 type != nil은 기존 코드에서 무작정 바꾸다 보니 저도 모르게 썼던거 같아요.. 없애겠습니다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.onReceive(timer) { _ in
    guard second < 2 && type != nil else {
        closedPopup()
        return
    }
    second += 1
}

혹시 내부적 처리라는게 이걸 뜻하는건가요? 저 type != nil은 기존 코드에서 무작정 바꾸다 보니 저도 모르게 썼던거 같아요.. 없애겠습니다

넵 onReceive도 그렇고 64번째줄, 72번째줄도 type이 쓰이더라구여! 그거 관련해서 물어봤습니다

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한건 지우도록 하겠습니당

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.onReceive(timer) { _ in
    guard second < 2 && type != nil else {
        closedPopup()
        return
    }
    second += 1
}

혹시 내부적 처리라는게 이걸 뜻하는건가요? 저 type != nil은 기존 코드에서 무작정 바꾸다 보니 저도 모르게 썼던거 같아요.. 없애겠습니다

넵 onReceive도 그렇고 64번째줄, 72번째줄도 type이 쓰이더라구여! 그거 관련해서 물어봤습니다

64번째 줄은 팝업이 link 타입일 경우 url을 표시하기 위함이고, 72번째 줄은 success 타입일 경우 체크마크를 표시하기 위함입니다!

Comment on lines 100 to 101
title: store.linkTitle == "제목을 입력해주세요"
? store.title.isEmpty ? "제목을 입력해주세요" : store.title
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 말했던 Constants를 사용해 하드코딩된 String값을 비교하면 실수가 덜 할 것 같아요

Comment on lines 158 to 160
guard let url = URL(string: content.data) else {
return .none
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한줄

Comment on lines 293 to 294
/// - 링크에 대한 `공유` / `수정` / `삭제` delegate
switch action {
case .bottomSheet(let delegate, let content):
switch delegate {
case .deleteCellButtonTapped:
state.alertItem = content
return .none
case .editCellButtonTapped:
return .send(.delegate(.링크수정(id: content.id)))
case .favoriteCellButtonTapped:
return .none
case .shareCellButtonTapped:
state.shareSheetItem = content
return .none
}
}
return .none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

않이 주석도 지워주세요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아...!!! 지울게여 ㅎㅎ..;;;

Copy link
Contributor

@stealmh stealmh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지울 코드도 엄청많았고 디자인시스템 꾸려서 적용하는게 쉽지는 않았을텐데 고생 너무 많았습니다..!! 짱짱
이것저것 소소하게 고칠 것들이나 궁금한 것들 남겨뒀습니다 😃

View Extension과 같은 경우에는 내부적으로 정해야 할 것 같아요 이것도 하루 날 잡고 모듈 별로 위치해야 할 것들 정리가 필요해보입니다! 일단은 그 위치에 놔두는 것 괜찮습니다

@stealmh stealmh linked an issue Dec 3, 2024 that may be closed by this pull request
9 tasks
@ShapeKim98 ShapeKim98 merged commit bfe5652 into develop Dec 4, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design 🎨 디자인 작업 Feat 기능구현 Fix 기능 수정
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1.0.6 버전 업데이트에 따른 기능 추가 및 수정
2 participants