feat: 카드 상세에 카카오톡/인스타그램 스토리 공유 기능 추가 - #65
Conversation
캘린더 카드 상세의 '공유하기'를 텍스트 공유에서 카드 이미지 공유로 바꾼다. - Compose GraphicsLayer 로 카드를 캡처해 FileProvider URI 로 넘긴다 - 인스타그램이 배경 이미지를 9:16 으로 늘려 채우므로, 캔버스 가운데에 비율을 유지해 카드를 놓고 남는 곳을 진한 배경색으로 채운다 - 캡처한 비트맵은 HARDWARE 설정이라 소프트웨어 캔버스에 그리기 전에 복사한다 - 공유 이미지에는 누를 수 없는 버튼과 닫기 아이콘을 남기지 않는다 - 로그인·서버 없이 공유 경로를 확인하는 디버그 액티비티를 추가한다
develop 이 feature:calendar 를 feature:archive 로 대체했으므로, 카드 상세의 인스타그램 스토리 공유를 archive 쪽 CardDetailDialog 로 옮기고 코드 리뷰 지적사항을 함께 반영한다. 충돌 해소 - feature:calendar 잔여 파일(공유 검증용 디버그 액티비티 포함)을 지우고 archive 로 이식한다 - ArchiveDetailScreen 의 텍스트 공유 경로와 archive_card_share_chooser_title 을 제거한다 - app 매니페스트는 FCM 서비스와 FileProvider 를 모두 남긴다 - GamssEmotionCard 는 develop 의 점선 divider 를 유지한 채 액션 영역만 조건부로 그린다 리뷰 반영 - 캡처 상태를 try/finally 로 되돌려 버튼과 닫기 아이콘이 사라진 카드로 남지 않게 한다 - 인스타그램 부재와 이미지 생성 실패를 StoryShareResult 로 구분하고 각각 다른 안내를 띄운다 - 프레임 수 추측 대신 실제 그리기 신호를 기다려 캡처한다(시간 초과 시 실패로 처리) - 공유 파일을 매번 새로 만들고 오래된 것만 지워 읽는 중 덮어쓰기를 피한다 - 회수 시점이 없는 grantUriPermission 을 없애고 인텐트 플래그만 쓴다 - showActions 플래그를 GamssEmotionCardActions? 로 바꿔 죽은 인자를 없앤다 - 여백 배경색을 GamssCardExportBackground 토큰으로 옮긴다(테마 반전 금지 이유 명시) - 합성에 쓴 중간 비트맵과 캡처 비트맵을 해제한다 - 스토리 캔버스 배치 계산을 분리해 단위 테스트를 붙인다
공유용 카드 시안(Figma 4243:18230)은 상세 팝업과 아래쪽만 다르다. 점선은 그대로 두고 그 밑 버튼·공유 행 자리에 GAMSS 워드마크가 들어간다. 캡처할 때 액션만 걷어내면 점선까지 사라지고 아래가 빈 채로 남으므로, 무엇이 오는지를 GamssEmotionCardFooter 로 갈라 준다. - GamssEmotionCardActions -> GamssEmotionCardFooter.Actions / Brand - 워드마크는 카드가 고정으로 쓰는 라이트 팔레트의 purple 로 물들여 시안 색과 맞춘다 - 공유 이미지 모양을 프리뷰로 남긴다
보내는 것이 대상마다 달라 시스템 공유 시트를 쓰지 않는다. 카카오톡은 카드 문구 한 줄과 링크를, 인스타그램은 카드 그림을 보내므로 두 경로를 나눠 두고 시트에서 고르게 한다. - ShareTargetSheet: 카카오톡 / 인스타그램 스토리 두 항목 - 카카오톡은 ACTION_SEND 를 com.kakao.talk 에 직접 지정한다. 한 줄 문구와 링크만 보내므로 SDK·앱키·메시지 템플릿을 들이지 않는다 - 시트 두 개가 같은 여백을 각자 들고 있었으므로 SheetChrome 으로 모았다 - 매니페스트 queries 에 com.kakao.talk 추가(API 30+ 는 이게 없으면 setPackage 해석이 실패한다) 링크 주소는 CardShareLink 한 곳에만 둔다. 지금은 열리지 않는 예약 도메인이므로 웹 랜딩 주소를 받아 바꿔야 한다.
- 공유 링크를 gamss.kr 랜딩 주소로 변경한다 - 카카오톡·인스타그램 실행 실패를 처리한다 - 인스타그램 중복 공유와 PNG 캐시 생성 실패를 방지한다 - 공유 Intent, FileProvider, 캐시 정리 테스트를 추가한다
HARDWARE 비트맵을 소프트웨어 비트맵으로 복사하는 데 실패하면(메모리 부족 등) 원본(HARDWARE)을 그대로 돌려주고 있었다. 이걸 소프트웨어 캔버스에 그리려 하면 IllegalStateException 이 나는데 어디서도 잡지 않아 앱이 죽었다. 변환 실패 시 null 을 돌려주도록 바꿔, 이미 있던 ImageUnavailable 처리 경로로 흘러가게 한다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVCS63pcgDR6Z8kGbQ8HCG
# Conflicts: # core/designsystem/src/main/java/com/gamss/android/core/designsystem/card/GamssImageCard.kt # feature/archive/src/main/java/com/gamss/android/feature/archive/ArchiveDetailScreen.kt # feature/archive/src/main/java/com/gamss/android/feature/archive/component/CardDetailDialog.kt # feature/archive/src/main/res/values/strings.xml
- GamssEmotionCard 의 GamssEmotionCardFooter(Actions/Brand) 분기를 복원한다 - CardDetailDialog 의 카드 캡처·공유 시트·카카오톡/인스타그램 스토리 연동을 복원한다 - ArchiveDetailScreen 의 옛 ACTION_SEND 공유 배선(onCardShare/shareCard)을 제거한다 - 공유 시트 문자열을 복원하고 쓰지 않는 chooser 문자열을 지운다
- 인스타그램/카카오톡 미설치·권한 거부처럼 이미 처리되는 실패라 warn 대신 info로 남긴다
- CardDetailDialog 에서 카카오톡 공유도 isSharing 으로 감싸 시트가 닫히는 동안 두 번 눌려 ACTION_SEND 가 중복 되지 않게 한다 - 인스타그램/카카오톡 미설치(ActivityNotFoundException), 공유 캐시 쓰기 실패(ImageUnavailable) 계측 테스트를 추가한다
soyeonLee126
left a comment
There was a problem hiding this comment.
캡처 타이밍, 비트맵 소유권, FileProvider 권한처럼 까다로운 부분이 잘 정리되어 있는 것 같습니다. 회수 시점이 없는 grantUriPermission 대신 인텐트 플래그만 쓴 것, source !== this 로 원본 소유권을 지킨 것, storyCardBounds 를 순수 함수로 떼서 단위 테스트를 붙인 것, GamssEmotionCardFooter sealed 로 죽은 인자를 구조적으로 없앤 것은 그대로 두면 좋겠습니다.
크게 두 가지만 봐 주시면 좋겠습니다. 공유 이미지 생성이 메인 스레드에서 도는 것으로 보이는 점, 그리고 카카오톡 쪽 isSharing 가드가 실제로는 열리는 구간이 없어 보이는 점입니다. 나머지는 확인 부탁드리는 정도입니다.
- 공유 이미지 캐시 생성 작업을 suspend 함수로 전환해 IO 디스패처에서 실행 - 공유 배경색을 내부 기본값으로 고정해 호출부의 불필요한 파라미터 제거 - 변경된 suspend API에 맞춰 공유 계측 테스트를 runBlocking으로 실행
- 공유 함수가 사용자 메시지 대신 결과 타입을 반환하도록 변경 - 다이얼로그에서 공유 결과에 따라 토스트 메시지를 결정 - 공유 캐시 정리 대상을 공유 이미지 파일로 제한
- 만료된 공유 이미지 캐시를 스토리 공유 파일명 규칙에 맞게 생성 - 한 시간 지난 캐시 삭제 검증이 실제 대상 파일 패턴을 따르도록 수정
- 배경색을 파라미터로 넘기지 않고 GamssCardExportBackground 를 직접 참조하도록 정리 - 인스타그램 미설치/권한 부족으로 공유에 실패하면 생성해둔 캐시 이미지 URI 를 삭제
- captureAfterNextDraw를 captureWith로 바꿔 setCapturing on/off를 캡처 함수가 직접 책임지고, 그리기 대기 구간에만 타임아웃을 걸어 캡처 자체는 취소되지 않게 한다 - captureTo에 isCapturing 플래그를 추가해 캡처 중이 아닐 때는 오프스크린 레이어 기록 없이 바로 그리도록 해 불필요한 레이어 비용을 없앤다 - toStoryCanvas에서 story 비트맵 생성이 실패해도 복사본 source는 try/finally로 반드시 recycle되게 한다
- 인스타그램/카카오톡 공유 실패 시 Toast를 CardDetailDialog가 아닌 ArchiveDetailScreen이 SideEffect로 처리하도록 이동 - 공유 시트 표시 여부(isShareSheetVisible)를 컴포저블 로컬 상태에서 ArchiveDetailState로 승격해 ViewModel이 소유하도록 변경 - CardDetailDialog는 공유 결과 판단 없이 콜백만 호출하도록 단순화
soyeonLee126
left a comment
There was a problem hiding this comment.
공유 기능 잘 봤습니다. HARDWARE 비트맵 처리랑 캡처 중에만 layer를 기록하는 부분은 꼼꼼하게 되어 있어서 따로 짚을 게 없었어요.
네 가지만 남깁니다. 앞의 두 개는 확인해 주시면 좋을 것 같고, 뒤의 두 개는 판단에 맡길게요. 전부 코드만 읽고 남긴 거라 실기기 확인은 못 했습니다.
- 스토리 공유 성공 시점에는 캐시 정리 시점이 없어 다음 공유 전까지 파일이 남는 문제를 보완 - 앱 시작 시 clearStaleInstagramStoryShareCache 를 호출해 TTL 지난 캐시 파일을 삭제
- 캡처 진행 중 카드 위에 반투명 오버레이를 덮어 액션 영역이 워드마크로 전환되는 과정이 화면에 보이지 않게 함 - 카드를 Box로 감싸 캡처 오버레이를 중첩 배치하도록 구조 변경
- 앱 전역 초기화에서 수행하던 공유 캐시 정리를 Activity 재개 시점으로 이동 - 인스타그램 공유 후 앱으로 돌아올 때 오래된 공유 이미지가 정리되도록 처리
- 공유 대상 선택 시 짧은 시간 내 반복 탭을 무시하도록 처리 - 인스타그램 스토리 공유 중 재진입 방지 상태를 명확히 분리 - 카드 닫기 및 삭제 흐름에서 공유 시트 상태도 함께 초기화
…to feature/share
soyeonLee126
left a comment
There was a problem hiding this comment.
빌드해서 확인해보니 시안과 상이한 부분이 있는데요, figma
해당 부분은 완성된 시안이 아닌 것 같아 일단 머지하고 추후에 ui sync 맞추는것이 좋아보입니다. 고생하셨어요!
작업 개요
카드 상세 팝업에 카카오톡 텍스트 공유와 인스타그램 스토리 이미지 공유를 추가한다. 카드를 Compose 캡처로 비트맵화해 인스타그램 9:16 캔버스에 합성하고, 카카오톡은 카드 제목·링크를 한 줄로 보낸다. 공유 대상마다 보내는 것이 달라 시스템 공유 시트 대신 카카오톡/인스타그램을 고르는 자체 시트(
ShareTargetSheet)를 둔다.작업 유형
변경 사항
ACTION_SEND)·인스타그램 스토리(ADD_TO_STORY) 공유를 각각 구현한다ComposeCapture로 카드를 비트맵으로 캡처하고, 9:16 캔버스 가운데 배치해 인스타그램 스토리 배경 이미지로 합성한다gamss.kr랜딩 주소로 바꾸고, 카카오톡/인스타그램 미설치·권한 거부·이미지 생성 실패를 각각 사용자 메시지로 안내한다FileProvider)에 TTL 기반 정리를 추가해 지난 공유 이미지가 쌓이지 않게 한다관련 이슈
관련 작업 (Notion)
스크린샷 / 동작 화면
체크리스트
develop으로 설정되어 있다feat:,fix:등)을 따른다리뷰 요청 사항
CardDetailDialog의 캡처 타이밍(isCapturing→ 재구성 →captureAfterNextDraw)과 인스타그램 중복 공유 가드가 의도대로 동작하는지 확인 부탁드립니다.