Загружаем каталог…
Загружаем каталог…
2026-09-23 움직이는 배경화면 Android 전용 UI, 구버전(스토어) 호환 처리, "무제" 번역 누락 수정, 댓글 시트 새로고침 수정, KG이니시스 본인인증 실 연동 움직이는 배경화면 기능이 Android 전용으로 확정된 뒤, 오늘은 iOS 사용자 UX를 다듬는 작업으로 시작해서 스토어 구버전 호환 문제까지 이어졌다. 중간에 몇 가지 실사용 버그 리포트("무제"가 한글로만 뜨는 문제, 댓글 시트가 새로고침 안 되는 문제)도 같이 잡았고, 마지막엔 판매자 정산용 KG이니시스 본인인증을 실제 서비스와 연동해 끝까지 테스트까지 마쳤다. 1. 움직이는 배경화면 — iOS에는 "Android 전용" 표시 iOS는 Live Photo 배경화면 자격 문제가 구조적으로 막혀있어(전날 결론) 움직이는 배경화면을 아예 못 쓴다. 지금까지는 상세 화면에만 안내 문구 박스가 있었는데, 피드 단계에서부터 알 수 있게 했다. LiveWallpaperPreview ( lib/widgets.dart ) — 움직이는 배경화면 미리보기를 그리는 단일 공용 위젯이라 피드·상세·마이페이지·작가 프로필 등 어디서 써도 이 한 곳만 고치면 전부 적용된다. !isAndroid 일 때만 Stack 에 Positioned.fill 로 반투명 검정( Colors.black.withValues(alpha: .45) ) 스크림 + Icons.android + 기존 liveWallpaperAndroidOnly l10n 문구를 오버레이로 얹었다. 새 l10n 문자열을 추가하지 않고 이미 16개 언어로 있던 문구를 재사용해서 번역 작업을 늘리지 않았다. 2. 스토어 구버전이 홈피드에서 에러나는 문제 — 근본 원인 고치기 구버전(움직이는 배경화면을 모르는) 클라이언트가 홈피드에서 type: 'live' 상품을 만나면 verse_ref / verse_text 가 null이라고 가정하고 캐스팅하다가 "불러오지 못했어요" 에러를 냈다(2026.09.22 재현). 처음엔 LIVE_WALLPAPER_AUTO_PUBLISH 시크릿으로 자동 공개 자체를 꺼서 구버전이 아예 못 보게 막는 임시 게이트를 만들었는데, 오늘은 "구버전도 볼 수 있게" 근본 해결로 바꿨다. 더미 텍스트 : SupabaseLiveWallpaperUploadRepository.publish() ( lib/data/repositories/supabase_live_wallpaper_upload_repository.dart )에서 상품 insert 시 verse_ref / verse_text 를 draft.title 로 채운다. 구버전은 이 값을 카드 제목처럼 그대로 보여줄 뿐이라 크래시 없이 넘어간다. 안내 이미지 : 구버전은 previews/{productId}/verse-preview.jpg · plain-preview.jpg 경로에 파일이 있다고 가정하고 무조건 그 URL로 이미지를 요청한다. Cloud Run 영상 변환 워커( worker/index.js )가 변환 성공 시마다 그 두 경로에 고정 이미지 한 장을 그대로 복사 업로드하도록 추가했다. 이미지는 Python Pillow로 만들었다 — 앱 기존 디자인 토큰(카드 배경 #F3EAD9 , 잉크 #2B2420 )과 assets/images/wordmark.png (로고 텍스트를 새로 그리지 않고 기존 에셋 재사용, logo/README.md 규칙)를 그대로 써서 "움직이는 작품은 업데이트하면 볼 수 있어요" 문구를 얹었다. worker/legacy-preview.jpg 로 저장해 Dockerfile에 COPY legacy-preview.jpg ./ 로 번들시켰다. 이중 안전장치 : LIVE_WALLPAPER_AUTO_PUBLISH 게이트( transcode-live-wallpaper Edge Function)는 되살려뒀다 — 위 두 가지로 이론상 안전해졌지만, 실기기(구버전 빌드)로 검증되기 전까지는 계속 hidden 상태로 남겨두는 편이 안전하다고 판단했다. 실제로 오늘 supabase secrets set LIVE_WALLPAPER_AUTO_PUBLISH=true 로 켜고, 이미 hidden으로 남아있던 테스트 상품( title: 'test' )을 supabase db query --linked 로 직접 status = 'published' 로 바꿔서 검증했다. previews/{id}/ 스토리지에 verse-preview.jpg · plain-preview.jpg 가 실제로 올라간 것도 supabase storage ls --experimental 로 확인. 문구는 처음 "업데이트하면 볼 수 있어요"였다가 "움직이는 작품은 업데이트하면 볼 수 있어요"로, 영어도 "Update the app to view moving wallpapers"로 다시 구웠다. Cloud Run은 gcloud run deploy atelier316-video-worker --source ./worker --region asia-northeast3 로 재배포(저장소 루트에서 실행해야 함 — ios/ 같은 하위 디렉터리에서 실행하면 --source . 가 엉뚱한 폴더를 빌드하려다 실패한다). 3. iOS 전용 팝업 추가했다가 취소 — ARB 파일 복구 사고 iOS에서 움직이는 배경화면 상세로 들어오면 뜨는 팝업( _IosLiveWallpaperPopup , detail.dart )을 추가했었는데, 위 2번 작업으로 카드 단계에서부터 이미 충분히 안내되니 필요 없다는 판단으로 다시 제거했다. 제거 과정에서 사고가 하나 있었다 — 16개 언어 ARB 파일에서 키 하나를 지우는 파이썬 스크립트가 json.load(fh, object_pairs_hook=lambda pairs: pairs) 를 썼는데, 이 훅이 최상위뿐 아니라 모든 중첩 객체 에 적용돼서 @sellerStats 같은 플레이스홀더 메타데이터( {"placeholders": {"subs": {"type": "int"}, ...}} )가 통째로 [["placeholders", [["subs", [["type","int"]]]]]] 같은 배열로 뒤집혀버렸다. flutter gen-l10n 이 "리소스 속성이 올바른 Map이 아니다"라고 실패해서 바로 발견했고, 중첩 리스트-of-2-tuple 패턴을 재귀적으로 dict로 되돌리는 복구 스크립트를 따로 짜서 16개 파일 전부 고쳤다. git diff 로 원본 커밋 대비 순수 추가분만 남았는지 확인 후 flutter gen-l10n · dart analyze · flutter test (97개) 전부 통과 재확인. 4. "무제"가 항상 한글로만 뜨는 버그 말씀 블록 없이 올린 정지 배경화면은 verse_ref 에 한국어 리터럴 "무제" 를 그대로 저장하는데( UploadViewModel.publish() , upload_view_model.dart:609 ), 책 이름 번역 함수 locRef() ( presentation/format.dart )는 "책이름 + 장:절" 정규식에만 매칭해서 "무제"는 어느 언어로 봐도 번역 없이 그대로 통과됐다. locRef() 에 if (ref == '무제') return untitledByCode[code] ?? ref; 특수 분기를 추가하고, content.dart 에 untitledByCode (16개 언어 "무제"/"Untitled"/ "無題"/... 번역표)를 새로 만들어 해결했다. 새 DB 값을 쓰지 않고 클라이언트 번역표만으로 해결해서, 예전에 "무제"로 저장된 기존 상품들도 즉시 소급 적용된다. 5. 댓글 시트 진입 시 새로고침 안 되던 버그 commentsProvider ( FutureProvider.family , providers.dart )가 autoDispose 가 아니라서 한 번 읽으면 앱이 살아있는 동안 계속 캐시로 남는다 — 댓글 바텀시트를 닫았다 다시 열어도 다른 사람이 그 사이 단 댓글·답글·좋아요가 안 보였다. CommentsSection ( comments_section.dart )의 initState 에서 Future.microtask(() => ref.invalidate(commentsProvider(widget.productId))) 를 추가해서, 시트를 열 때마다 서버에서 새로 읽어오게 했다. Future.microtask 로 감싼 건 initState 동기 실행 중 Riverpod 프로바이더를 건드리다 생기는 lifecycle 경합을 피하기 위한 보수적 선택. 6. 그 외 확인만 하고 코드는 안 건드린 것들 작가 구독 FCM 알림 : notify_follow 트리거(2026.08.17, on_follow_created )가 이미 알림 행을 만들고, send-push 의 PUSHABLE 세트에 'follow' 가 이미 있으며 16개 언어 문구도 이미 있었다. 실제 DB에도 최근(9/21)까지 follow 알림이 쌓이고 있는 걸 supabase db query 로 확인 — 이미 정상 동작 중이라 추가 구현 불필요했다. KG이니시스 본인인증 코드 연결 : 서버 코드( verify-identity , _shared/portone.ts )는 포트원 V1 범용 REST API( api.iamport.kr )만 쓰고 어떤 PG(이니시스·다날 등)를 쓰는지 전혀 모른다 — 라우팅은 전부 포트원 콘솔 채널 설정으로 결정되므로 앱 코드 자체는 처음부터 손댈 게 없었다(7번 참고). 7. KG이니시스 본인인증 — 실제로 붙여서 끝까지 테스트 이니시스 통합인증서비스 심사가 끝났다는 연락을 받고 실기기로 테스트를 진행했다. 1차 시도 : payout.dart 가 쓰던 pg: 'inicis' (포트원 V1 일반결제용 코드)로 이니시스 간편인증 화면(삼성패스·IBK·금융인증서·우리WON·하나·토스·네이버) 자체는 떴지만 카카오·페이코가 빠져 있었다. 원인 조사 : 포트원 공식 문서 확인 결과 inicis_unified 가 통합인증서비스 전용 pg 값이고, inicis 는 별개의(일반결제) 상품이었다 — pg: 'inicis_unified' 로 바꿔서 재시도했지만 화면이 똑같았다(사용자가 hot restart까지 해서 재확인). 진짜 원인 : inicis_unified 로 바꾸자 이번엔 "정상적인 통합인증 처리를 위해 m_redirect_url 파라미터 세팅이 필요합니다" 에러가 떴다. portone_flutter 패키지( CertificationData.mRedirectUrl , IamportCertification 위젯 소스 직접 확인)의 기본 리다이렉트 도메인을 이니시스가 안 받아준 것 — 심사 때 등록한 도메인만 허용한다(이니시스 안내 메일 "등록된 도메인 외 이용 불가" 그대로). 이미 스토어 등록용 법적 문서(개인정보처리방침 등)에 쓰고 있던 https://nohyeongtaek.github.io/atelier316-info/ (GitHub Pages, 우리가 실제 소유·사용 중인 도메인)를 mRedirectUrl 로 채웠더니 정상 동작했다. 화면이 안 바뀐 원인(카카오 미노출)은 별개로, KG이니시스 통합인증서비스와 포트원 통합인증( inicis_unified )이 계약과 무관하게 자체적으로 제공하는 민간인증 목록(간편인증 vs 전자서명 vs 본인확인 구분 포함)을 웹 검색으로 정리한 결과, 카카오 인증은 별도 서류 제출 + 이니시스 계약담당자를 통한 신청 이 필요한 것으로 확인됐다(가맹점관리자사이트 iniweb.inicis.com → 변경/추가 → 4. 계약 및 특약 체결 → 인증서비스 계약 메뉴로 추정). 지금은 이미 뜨는 토스·네이버만으로도(판매자 전용 플로우라 전환율 이슈 없음) 충분하다고 판단해 카카오 신청은 보류하기로 했다. 정산 자동 인증과의 연결 : 본인인증이 실제로 동작하면서, payout.dart:166 의 기존 로직(국내계좌 + 본인인증 완료 시 저장 직후 verify-payout-account 를 자동 호출해 예금주명 일치까지 확인되면 관리자 확인 없이 바로 payout_verified: true )도 실사용 가능해졌다. PayPal 등 해외 계좌나 예금주명 불일치 케이스는 여전히 관리자 수동 인증 목록( /admin/payouts )에 남는 안전망 구조는 그대로 유지. 기술 스택 요약 Flutter/Riverpod : FutureProvider.family 캐시 무효화( ref.invalidate )로 댓글 새로고침 처리. 공용 위젯( LiveWallpaperPreview ) 한 곳만 고쳐 여러 화면에 동시 반영. Python Pillow : 서버(워커)에 번들할 구버전 호환 안내 이미지를 앱 폰트 ( NotoSerifKR-Regular.ttf )와 기존 wordmark PNG 에셋으로 직접 렌더링. Node.js/Express (Cloud Run 워커) : ffmpeg 변환 성공 시 고정 안내 이미지를 previews/{id}/verse-preview.jpg · plain-preview.jpg 에 추가 업로드하도록 worker/index.js 확장, Dockerfile 에 정적 자산 COPY 추가. gcloud run deploy --source 로 재배포. Supabase Edge Functions (Deno) : LIVE_WALLPAPER_AUTO_PUBLISH 시크릿 기반 조건부 게이트 유지. supabase secrets set 으로 원격 시크릿 토글, supabase db query --linked 로 운영 DB 직접 조회/수정, supabase storage ls --experimental 로 업로드 결과 검증. PortOne(포트원) V1 + KG이니시스 통합인증서비스 : portone_flutter 패키지의 CertificationData(pg: 'inicis_unified', mRedirectUrl: ...) 로 웹뷰 기반 간편인증 호출. 서버는 포트원 V1 REST API( api.iamport.kr/certifications/ {impUid} )로 재검증 — PG사가 바뀌어도 서버 코드는 그대로인 구조. l10n : flutter gen-l10n + ARB 16개 언어 직접 편집. JSON 중첩 구조를 깨뜨리지 않도록 object_pairs_hook 없이 표준 json.load / json.dump 로 프로그래매틱 편집하는 게 안전하다는 걸 이번에 사고로 배웠다. 2026-09-25 영상 트림 UI 라이브러리 전환, 아이폰식 트림 프레임, 움직이는 배경화면 말씀 위치 통일, 게시·적용 미리보기와 진행률, 작품 제목, 코치마크 SKIP·iOS 위치 수정, 댓글 새로고침 어제 점심에 움직이는 배경화면 트림 UI를 video_trimmer 라이브러리로 갈아엎던 중에 주간 토큰 한도에 걸려 대화가 그대로 끊겼다. 오늘은 그 세션을 이어받아서 끝내는 것부터 시작했는데, 끝내고 나서 실제로 써보니 "영상이 안 움직인다", "긴 영상에선 구간을 못 옮긴다", "피드에서 말씀이 카드를 꽉 채운다", "적용하면 말씀이 길쭉해진다"가 줄줄이 나왔다. 결국 하루 종일 움직이는 배경화면 하나를 붙잡고 있었고, 마지막에 코치마크· 댓글 같은 자잘한 것들을 정리했다. 1. 끊긴 세션 이어받기 컴파일이 안 되는 상태로 멈춰 있었다 새 세션에서 dart analyze 를 돌리니 upload.dart 에 지워진 _pickAny 를 부르는 곳이 하나 남아 있었다. 어제 마지막 요청이 "+ 버튼 분기 없애고 통합 피커로 가자 → 아니다 그냥 분기하자"로 뒤집혔고, 분기로 되돌리는 도중(교체 아이콘 두 곳 중 하나를 고치다가) 끊긴 것이었다. 처음엔 이 맥락을 모르고 주변 코드만 보고 vm.pickVideo 로 고쳤는데, 사용자가 "뭐 하고 있었는지 알고 한 거냐"고 물어서 제대로 확인했다. Claude Code 대화는 ~/.claude/projects/<프로젝트>/ 에 세션별 .jsonl 로 남아 있어서, 파이썬으로 파일 수정 시각과 마지막 사용자 메시지를 뽑아 어제 세션을 찾았다. 결과적으로 고친 방향은 맞았지만, 추측으로 진행한 건 반성할 점이다. ( claude --resume <세션id> 로 그대로 이어갈 수도 있다.) 2. 영상을 골라도 캔버스에서 안 움직였다 원인: 한 번만 오는 이벤트를 놓쳤다 video_trimmer 의 Trimmer.loadVideo() 는 로드가 끝날 때 TrimmerEvent.initialized 를 브로드캐스트 스트림으로 딱 한 번 보낸다. TrimViewer 와 VideoViewer 는 이 이벤트를 받아야 비로소 필름스트립과 영상을 그린다. 그런데 뷰모델은 영상을 먼저 로드하고, 그 다음 notifyListeners() 로 위젯을 화면에 붙였다. 위젯이 붙었을 땐 이벤트가 이미 지나간 뒤라 트림 UI는 통째로 안 뜨고 영상은 첫 프레임에 멈춰 있었다. 사용자 눈엔 "일반 배경화면처럼 동작"하는 걸로 보였다. notifyListeners(); // 뷰어 위젯을 먼저 붙이고 await WidgetsBinding.instance.endOfFrame; // 한 프레임 기다린 뒤 await trimmer.loadVideo(videoFile: file); // 로드 → 이벤트를 받을 수 있다 순서만 바꾸면 끝이었다. 두 가지가 더 있었다. 자동 재생이 없다. 패키지는 재생을 알아서 시작하지 않는다. TrimViewer 가 초기화될 때 처음 부르는 onChangeEnd 에서 재생을 시작하고, 구간 끝에서 멈췄다는 신호 ( onChangePlaybackState(false) )가 오면 구간 처음으로 seekTo 후 다시 play() 해서 반복 재생을 만들었다. 두 번째 영상에서 죽는다. VideoViewer.dispose() 가 trimmer.dispose() 로 스트림을 닫아버려서, 같은 Trimmer 로 영상을 다시 로드하면 "Cannot add new events after calling close" 예외가 났다. 영상을 고를 때마다 Trimmer 를 새로 만든다. 덤: "동영상을 업로드 해주세요" 문구가 안 보였다 사진 모드엔 빈 캔버스에 "이미지를 업로드 해주세요"가 있는데 영상 모드엔 없다는 요청이 왔다. 문구를 새로 만들 필요는 없었다. 이미 있었는데 영상을 고르기 전에도 빈 영상 뷰어가 캔버스를 덮어서 가려지고 있었다. 뷰어는 videoFile != null 일 때만 그리고, 문구 키 uploadSlotVideo 를 16개 언어로 추가했다. 3. 긴 영상에서 구간 가운데를 잡아 옮길 수가 없었다 요구사항은 "양끝을 잡으면 길이 조절, 가운데를 잡으면 구간 이동"이었다. 이게 제일 오래 걸렸다. 세 번 고쳤다. 1차: 뷰어 타입이 auto라서 TrimViewer 의 기본값 ViewerType.auto 는 영상이 길면 scrollable 뷰어로 바뀐다. 이 뷰어는 창이 고정되고 필름이 밀리는 방식이라 가운데 드래그 자체가 없다. type: ViewerType.fixed 로 고정했다. 2차: 35초 영상에선 여전히 안 됐다 fixed 뷰어는 필름 전체 폭이 영상 전체 길이라서, 35초 영상이면 3초 창이 약 29px밖에 안 된다. 패키지 소스( fixed_trim_viewer.dart )를 읽어보니 문제가 두 겹이었다. 옆면 터치 영역( sideTapSize , 기본 24px)이 창 안쪽을 잡아먹어서 가운데 영역이 "창 폭 − 24×2"로 음수가 된다. 더 근본적인 건 Flutter의 드래그 인식 방식이었다. 손가락이 터치 슬롭(약 18px)만큼 움직인 뒤의 위치 를 onHorizontalDragStart 로 넘기는데, 패키지는 그 위치가 창 안인지로 판정한다. 창이 29px이면 가운데를 눌러도 판정 시점엔 이미 창 밖이다. 설정값으로는 못 고친다. 그래서 video_trimmer 5.0.0 을 packages/video_trimmer/ 로 복사하고 pubspec.yaml 에서 path: 의존성으로 바꿨다. 포크해서 GitHub에 올리는 것보다, 고칠 곳이 파일 하나라 저장소 안에 두는 쪽이 추적하기 쉽다. 고친 곳마다 [PATCH] 주석을 달고 PATCH.md 에 사유를 적어뒀다. 3차: 손잡이를 구간 바깥으로 — 아이폰처럼 2차 패치 뒤에도 "여전히 잘 안 된다", "오른쪽 쉐브론을 왼쪽 끝까지 밀면 두 손잡이가 엇갈린다"는 리포트가 왔다. 둘 다 손잡이를 구간 안쪽 에 그리는 구조 때문이었다. 아이폰 사진 앱은 손잡이가 선택 구간 바깥 에 붙어 있다. 그대로 따라했다. 패키지 필름 양옆에 손
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
1.3.0 업데이트. 2026-09-23 움직이는 배경화면 Android 전용 UI, 구버전(스토어) 호환 처리, "무제" 번역 누락 수정, 댓글 시트 새로고침 수정, KG이니시스 본인인증 실 연동 움직이는 배경화면 기능이 Android 전용으로 확정된 뒤, 오늘은 iOS 사용자 UX를 다듬는 작업으로 시작해서 스토어 구버전 호환 문제까지 이어졌다. 중간에 몇 가지 실사용 버그 리포트("무제"가 한글로만 뜨는 문제, 댓글 시트가 새로고침 안 되는 문제)도 같이 잡았고, 마지막엔 판매자 정산용 KG이니시스 본인인증을 실제 서비스와 연동해 끝까지 테스트까지 마쳤다. 1. 움직이는 배경화면 — iOS에는 "Android 전용" 표시 iOS는 Live Photo 배경화면 자격 문제가 구조적으로 막혀있어(전날…
Открыть источник