Skip to content

feat: 카드 상세에 카카오톡/인스타그램 스토리 공유 기능 추가 - #65

Merged
soyeonLee126 merged 23 commits into
developfrom
feature/share
Sep 17, 2026
Merged

soyeonLee126 merged 23 commits into
developfrom
feature/share

Conversation

@seunghee17

@seunghee17 seunghee17 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

작업 개요

카드 상세 팝업에 카카오톡 텍스트 공유와 인스타그램 스토리 이미지 공유를 추가한다. 카드를 Compose 캡처로 비트맵화해 인스타그램 9:16 캔버스에 합성하고, 카카오톡은 카드 제목·링크를 한 줄로 보낸다. 공유 대상마다 보내는 것이 달라 시스템 공유 시트 대신 카카오톡/인스타그램을 고르는 자체 시트(ShareTargetSheet)를 둔다.

작업 유형

  • feat: 새 기능
  • fix: 버그 수정
  • refactor: 리팩토링 (동작 변경 없음)
  • build: 빌드/의존성 설정
  • chore: 기타 (문서, 설정 등)
  • test: 테스트 추가/수정

변경 사항

  • 카드 상세 팝업에 공유 대상 선택 시트를 추가하고, 카카오톡(ACTION_SEND)·인스타그램 스토리(ADD_TO_STORY) 공유를 각각 구현한다
  • ComposeCapture로 카드를 비트맵으로 캡처하고, 9:16 캔버스 가운데 배치해 인스타그램 스토리 배경 이미지로 합성한다
  • 공유 링크를 gamss.kr 랜딩 주소로 바꾸고, 카카오톡/인스타그램 미설치·권한 거부·이미지 생성 실패를 각각 사용자 메시지로 안내한다
  • 인스타그램 공유 캐시(FileProvider)에 TTL 기반 정리를 추가해 지난 공유 이미지가 쌓이지 않게 한다
  • 인스타그램 중복 공유 및 카카오톡 공유 중복 탭(시트 닫히는 애니메이션 중 재진입)을 막는다
  • 공유 Intent·FileProvider·캐시 정리·실패 분기(ActivityNotFoundException 등)에 대한 계측/단위 테스트를 추가한다

관련 이슈

  • Closes #

관련 작업 (Notion)

스크린샷 / 동작 화면

체크리스트

  • base 브랜치가 develop으로 설정되어 있다
  • 로컬 빌드 및 테스트를 확인했다
  • 커밋 메시지가 컨벤션(feat:, fix: 등)을 따른다
  • 불필요한 로그/주석/디버그 코드를 제거했다
  • 리뷰어가 이해할 수 있도록 설명을 작성했다

리뷰 요청 사항

  • CardDetailDialog의 캡처 타이밍(isCapturing → 재구성 → captureAfterNextDraw)과 인스타그램 중복 공유 가드가 의도대로 동작하는지 확인 부탁드립니다.
  • 인스타 그램과 카카오톡 공유하기가 실기기에서 동작하는지 확인 부탁드립니다.

soyeonLee126 and others added 11 commits August 18, 2026 09:11
캘린더 카드 상세의 '공유하기'를 텍스트 공유에서 카드 이미지 공유로 바꾼다.

- 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) 계측 테스트를 추가한다
@seunghee17
seunghee17 marked this pull request as ready for review September 7, 2026 15:47

@soyeonLee126 soyeonLee126 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

캡처 타이밍, 비트맵 소유권, FileProvider 권한처럼 까다로운 부분이 잘 정리되어 있는 것 같습니다. 회수 시점이 없는 grantUriPermission 대신 인텐트 플래그만 쓴 것, source !== this 로 원본 소유권을 지킨 것, storyCardBounds 를 순수 함수로 떼서 단위 테스트를 붙인 것, GamssEmotionCardFooter sealed 로 죽은 인자를 구조적으로 없앤 것은 그대로 두면 좋겠습니다.

크게 두 가지만 봐 주시면 좋겠습니다. 공유 이미지 생성이 메인 스레드에서 도는 것으로 보이는 점, 그리고 카카오톡 쪽 isSharing 가드가 실제로는 열리는 구간이 없어 보이는 점입니다. 나머지는 확인 부탁드리는 정도입니다.

Comment thread core/ui/src/main/java/com/gamss/android/core/ui/share/InstagramStoryShare.kt Outdated
Comment thread core/ui/src/main/java/com/gamss/android/core/ui/share/ComposeCapture.kt Outdated
Comment thread core/ui/src/main/java/com/gamss/android/core/ui/share/ComposeCapture.kt Outdated
Comment thread core/ui/src/main/java/com/gamss/android/core/ui/share/InstagramStoryShare.kt Outdated
- 공유 이미지 캐시 생성 작업을 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 soyeonLee126 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

공유 기능 잘 봤습니다. HARDWARE 비트맵 처리랑 캡처 중에만 layer를 기록하는 부분은 꼼꼼하게 되어 있어서 따로 짚을 게 없었어요.

네 가지만 남깁니다. 앞의 두 개는 확인해 주시면 좋을 것 같고, 뒤의 두 개는 판단에 맡길게요. 전부 코드만 읽고 남긴 거라 실기기 확인은 못 했습니다.

- 스토리 공유 성공 시점에는 캐시 정리 시점이 없어 다음 공유 전까지 파일이 남는 문제를 보완
- 앱 시작 시 clearStaleInstagramStoryShareCache 를 호출해 TTL 지난 캐시 파일을 삭제
- 캡처 진행 중 카드 위에 반투명 오버레이를 덮어 액션 영역이 워드마크로 전환되는 과정이 화면에 보이지 않게 함
- 카드를 Box로 감싸 캡처 오버레이를 중첩 배치하도록 구조 변경
- 앱 전역 초기화에서 수행하던 공유 캐시 정리를 Activity 재개 시점으로 이동
- 인스타그램 공유 후 앱으로 돌아올 때 오래된 공유 이미지가 정리되도록 처리
- 공유 대상 선택 시 짧은 시간 내 반복 탭을 무시하도록 처리
- 인스타그램 스토리 공유 중 재진입 방지 상태를 명확히 분리
- 카드 닫기 및 삭제 흐름에서 공유 시트 상태도 함께 초기화

@soyeonLee126 soyeonLee126 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@seunghee17

빌드해서 확인해보니 시안과 상이한 부분이 있는데요, figma

해당 부분은 완성된 시안이 아닌 것 같아 일단 머지하고 추후에 ui sync 맞추는것이 좋아보입니다. 고생하셨어요!

@soyeonLee126
soyeonLee126 merged commit ae92f80 into develop Sep 17, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants