Compare commits

198 Commits

Author SHA1 Message Date
7c44f3d66c docs(i18n): 일본어 리소스 PRD 완료 기준을 갱신한다 2026-08-11 19:01:50 +09:00
43b6496289 chore(app): 앱 버전을 1.56.1로 올린다 2026-08-11 18:53:30 +09:00
30a4a9ffc7 feat(i18n): 일본어 리소스를 반영한다 2026-08-11 17:58:56 +09:00
84cd9bbf41 docs(i18n): 일본어 리소스 반영 계획을 추가한다 2026-08-11 17:57:50 +09:00
75480e7a71 docs(creator): 커뮤니티 후속 작업 기록을 갱신한다 2026-08-11 00:11:11 +09:00
8b8b384b29 feat(creator): 커뮤니티 구매와 메뉴 동작을 정리한다 2026-08-11 00:10:46 +09:00
02cd583f56 chore(app): 데이터스토어 버전을 올린다 2026-08-04 14:13:01 +09:00
3c21a764ae fix(home): 라이브 전체 버튼을 오른쪽에 고정한다 2026-08-04 11:45:29 +09:00
d20d6aa971 docs(agent): 테스트 범위를 로직으로 제한한다 2026-08-04 11:44:58 +09:00
f50f987282 chore(app): 앱 버전 코드를 올린다 2026-08-03 23:04:16 +09:00
f508d847ea feat(creator): 내 채널 팔로워 목록을 연결한다 2026-08-03 21:39:23 +09:00
e86e5a1c3d chore(app): 앱 버전을 1.56.0으로 올린다 2026-08-03 18:21:42 +09:00
26eae18edc fix(content): 콘텐츠 상세 펼침 말줄임을 복원한다 2026-08-03 16:33:35 +09:00
b5cfdc83e3 fix(live): 전체 라이브 제목을 On Air로 통일한다 2026-08-03 15:07:31 +09:00
dd4a5167a9 fix(home): 라이브 전체 항목을 항상 표시한다 2026-07-31 18:34:23 +09:00
d592038edd fix(home): 홈 추천 후속 검증을 반영한다 2026-07-31 16:29:06 +09:00
a106548f87 fix(content): 콘텐츠 고정 한도 안내를 수정한다 2026-07-31 03:48:35 +09:00
49f6fe592c fix(home): 팔로잉 탭 후속 검증을 반영한다 2026-07-31 03:22:50 +09:00
f600d10e8b feat(home): 최근 활동 LIVE 라우팅을 추가한다 2026-07-30 16:42:33 +09:00
a1aa9c2243 docs(docs): 문서 샘플과 리뷰 규칙을 갱신한다 2026-07-30 14:18:07 +09:00
3a62ed8463 chore(git): worktree 디렉터리를 제외한다 2026-07-22 18:11:14 +09:00
6755c8ae99 feat(main): 메인 탭 당겨서 새로고침을 추가한다 2026-07-22 17:48:33 +09:00
b17e250665 docs(main): 당겨서 새로고침 계획을 추가한다 2026-07-22 00:35:34 +09:00
074c945008 chore(app): 앱 버전을 1.55.1로 올린다 2026-07-20 15:41:48 +09:00
16aa946621 feat(main): 홈 크리에이터 FAB를 추가한다 2026-07-17 00:08:33 +09:00
831533b4f6 refactor(creator): 진행 중 라이브 컴포넌트를 공통화한다 2026-07-16 22:59:07 +09:00
de90f771c1 refactor(v2): 도메인 액션 경로를 통합한다 2026-07-16 16:57:39 +09:00
4373452478 docs(v2): 라이브 액션 검증 기록을 갱신한다 2026-07-15 21:27:53 +09:00
0b3042d29e refactor(onair): 라이브 입장 흐름을 공통화한다 2026-07-15 21:27:44 +09:00
cc23992d2e refactor(home): 추천 라이브 진입을 공통화한다 2026-07-15 21:27:37 +09:00
3a49158243 refactor(creator): 라이브 액션을 공통화한다 2026-07-15 21:27:31 +09:00
d1509d4359 refactor(live): 공통 액션 코디네이터를 추가한다 2026-07-15 21:27:23 +09:00
c223b3b8a4 feat(live): 생성 결과 계약을 추가한다 2026-07-15 21:26:53 +09:00
b582545991 feat(live): 입장 정책 모델을 추가한다 2026-07-15 21:26:37 +09:00
fe79431744 refactor(content): 상세 진입을 공통 액션으로 통합한다 2026-07-14 22:11:10 +09:00
1c5fbb38f1 style(content): 주석 오디오 너비 계산 줄바꿈을 정리한다 2026-07-14 20:48:53 +09:00
dadf3a519d test(home): 팔로잉 empty 문구 검증을 정리한다 2026-07-14 20:45:34 +09:00
ddc949f9d9 refactor(live): 온에어 접근 가드를 공통화한다 2026-07-14 20:45:29 +09:00
6973816b8c refactor(creator): 크리에이터 접근 가드를 공통화한다 2026-07-14 20:45:23 +09:00
e5b99be9c4 refactor(main): 기존 로그인 가드를 제거한다 2026-07-14 20:45:16 +09:00
5ff92980a2 refactor(chat): 채팅 접근 가드를 공통화한다 2026-07-14 20:44:58 +09:00
2fda80d602 refactor(content): 콘텐츠 접근 가드를 공통화한다 2026-07-14 20:44:53 +09:00
7e12aa8720 refactor(home): 홈 접근 가드를 공통화한다 2026-07-14 20:44:42 +09:00
d5d3444c3a refactor(main): 메인 접근 가드를 공통화한다 2026-07-14 20:44:36 +09:00
a6601dcea1 feat(access): 공통 접근 실행기를 추가한다 2026-07-14 20:44:20 +09:00
4d51806cee feat(access): 접근 정책 모델을 추가한다 2026-07-14 20:44:13 +09:00
98db0b657e docs(v2): 공통 접근 가드 계획을 보강한다 2026-07-14 20:44:06 +09:00
9f70bcd3eb docs(v2): 공통 접근 가드 계획을 기록한다 2026-07-14 12:22:58 +09:00
cc8d061b51 docs(content): 상세 오디오 광고 제거 검증을 기록한다 2026-07-13 16:59:20 +09:00
1b952054fb fix(content): 상세 오디오 광고를 제거한다 2026-07-13 16:59:10 +09:00
2c609836fd fix(home): 팔로잉 empty 문구를 중앙 표시한다 2026-07-13 16:17:12 +09:00
da36964347 fix(home): 팔로잉 empty 문구 번역을 갱신한다 2026-07-13 16:17:06 +09:00
47b9c3dec2 docs(home): 팔로잉 empty 문구 계획을 기록한다 2026-07-13 16:17:01 +09:00
17a9a2f601 fix(creator): 채널 진입과 채팅 인증을 보강한다 2026-07-13 14:09:06 +09:00
c43b9cdbd8 docs(creator): 채널 로그인 가드 계획을 기록한다 2026-07-13 14:09:01 +09:00
7ec4e09fb4 versionCode 242 2026-07-13 12:00:54 +09:00
862cec8375 fix(dm): 광고 로드 실패 안내를 보강한다 2026-07-12 22:09:00 +09:00
2d4956ae25 fix(dm): 쿼터 안내 광고 버튼을 숨긴다 2026-07-12 22:08:41 +09:00
beadc7d4c4 chore(app): 버전 코드를 갱신한다 2026-07-12 20:52:07 +09:00
2633912e93 test(dm): 채팅 플로팅 버튼 제거 검증을 맞춘다 2026-07-12 20:52:01 +09:00
6cbc751f0c fix(community): 채널 이미지 원본비율 표시를 반영한다 2026-07-12 20:51:54 +09:00
2a499eaf41 fix(home): 커뮤니티 이미지 로드 크기를 전달한다 2026-07-12 20:51:36 +09:00
9244a00713 fix(feed): 커뮤니티 이미지 원본비율 높이를 적용한다 2026-07-12 20:51:30 +09:00
79eadbd39d docs(community): 이미지 원본비율 요구를 기록한다 2026-07-12 20:51:22 +09:00
c389d1945d fix(dm): 채팅 플로팅 버튼을 숨긴다 2026-07-12 17:45:19 +09:00
2d8f8cab81 fix(community): 상세 잠금 반응과 댓글을 숨긴다 2026-07-12 09:54:26 +09:00
f799449d54 docs(community): 상세 잠금 반응 숨김 계획을 기록한다 2026-07-12 09:54:18 +09:00
9da6c7381d fix(community): 리스트 잠금 반응 표시를 숨긴다 2026-07-12 09:54:10 +09:00
ced226c3ef docs(community): 리스트 잠금 반응 숨김 계획을 기록한다 2026-07-12 09:54:01 +09:00
f69a9a6ee0 docs(community): 그리드 잠금 오버레이 요구를 기록한다 2026-07-12 09:28:05 +09:00
94d2773d1e fix(community): 그리드 잠금 오버레이를 보정한다 2026-07-12 09:27:59 +09:00
bc5fd46671 fix(content): 페이월 가격 배지를 보정한다 2026-07-12 08:46:59 +09:00
a00745dd38 docs(content): 상세 오디오 구매 검증을 기록한다 2026-07-12 08:33:02 +09:00
01908c84d3 docs(content): 상세 오디오 구매 요구를 기록한다 2026-07-12 08:32:55 +09:00
39959c3f47 fix(content): 상세 오디오와 구매 흐름을 보정한다 2026-07-12 08:32:48 +09:00
53cc96b84d fix(home): 커뮤니티 하트 아이콘을 PNG로 교체한다 2026-07-12 07:46:40 +09:00
1aeb336898 fix(home): 추천 섹션 chevron을 숨긴다 2026-07-12 07:32:49 +09:00
1715c591a5 docs(home): 섹션 chevron 요구를 기록한다 2026-07-12 07:32:38 +09:00
eb41506bf4 fix(content): 상세 댓글 border를 반영한다 2026-07-12 06:29:50 +09:00
9fdcd49c98 docs(content): 상세 border 요구를 기록한다 2026-07-12 06:29:43 +09:00
8538f014aa fix(content): 댓글 섹션 제목을 변경한다 2026-07-12 05:39:04 +09:00
848cd03068 docs(home): 장르 크리에이터 미표시 검증을 기록한다 2026-07-12 05:29:44 +09:00
4debf4654d fix(home): 장르 크리에이터 섹션을 숨긴다 2026-07-12 05:29:37 +09:00
6ed2be4173 docs(home): 장르 크리에이터 미표시 요구를 기록한다 2026-07-12 05:29:26 +09:00
557de11bb3 fix(home): 최근 소식 상세 이동을 연결한다 2026-07-12 04:24:47 +09:00
97d917e35d docs(home): 최근 소식 상세 이동 요구를 기록한다 2026-07-12 04:24:42 +09:00
9b2666e671 fix(home): 팔로잉 라이브 카드 레이아웃을 보정한다 2026-07-12 03:47:55 +09:00
c883556dbf chore(gradle): Java toolchain resolver를 추가한다 2026-07-12 03:42:22 +09:00
5bcd0f64d2 fix(content): 전체 탭 pagination 스크롤을 안정화한다 2026-07-12 01:24:19 +09:00
d53167c48e docs(content): 전체 탭 pagination 안정화 요구를 기록한다 2026-07-12 01:24:02 +09:00
c2b212c1ce fix(splash): 스플래시 태그라인 문구를 변경한다 2026-07-11 23:55:41 +09:00
d1510b8ba3 fix(splash): 스플래시 화면 구성을 단순화한다 2026-07-11 23:55:27 +09:00
273feef017 chore(dm): WebSocket raw JSON 로그를 주입한다 2026-07-11 22:03:32 +09:00
11a42bf8a1 feat(dm): 메시지 시간을 표시한다 2026-07-11 22:03:24 +09:00
d436efdc66 fix(dm): 채팅방 입력과 프로필 표시를 보정한다 2026-07-11 22:03:16 +09:00
30f7bd7556 fix(dm): 실시간 연결 상태 처리를 보강한다 2026-07-11 22:03:03 +09:00
62007862a5 feat(dm): 음성 메시지 API 계약을 추가한다 2026-07-11 22:02:51 +09:00
2786e45c75 feat(dm): WebSocket envelope 계약을 보정한다 2026-07-11 22:02:40 +09:00
46c3a02655 refactor(dm): legacy realtime 구현을 제거한다 2026-07-11 22:02:29 +09:00
5ca07ef049 docs(dm): WebSocket 전환 요구를 보강한다 2026-07-11 22:02:20 +09:00
c715c890b5 feat(chat): 탭별 empty state를 표시한다 2026-07-11 22:02:13 +09:00
9925a954d1 feat(chat): empty state 문구 리소스를 추가한다 2026-07-11 22:02:04 +09:00
0c111aaf47 docs(chat): empty state 요구를 기록한다 2026-07-11 22:01:56 +09:00
2a2315ecbc docs(home): 커뮤니티 상세 이동 요구를 기록한다 2026-07-10 10:38:59 +09:00
17f77351ad fix(home): 커뮤니티 상세 이동을 연결한다 2026-07-10 10:38:42 +09:00
abd084cff8 fix(content-ranking): 카드 UI 크기를 반응형 적용한다 2026-07-10 10:16:36 +09:00
e77d0a1d5b fix(content-ranking): 텍스트 크기 기준을 추가한다 2026-07-10 10:16:27 +09:00
991d70f22b fix(creator-ranking): 카드 UI 크기를 반응형 적용한다 2026-07-10 10:16:18 +09:00
f477b563a2 fix(creator-ranking): 텍스트 크기 기준을 확장한다 2026-07-10 10:16:09 +09:00
63a7c28e96 feat(ranking): 반응형 폭 스케일을 추가한다 2026-07-10 10:16:01 +09:00
2b2cb66dfc docs(ranking): 반응형 크기 요구를 기록한다 2026-07-10 10:15:54 +09:00
bf5cc1d7bb fix(content): 전체 탭 sort-bar 표시를 정리한다 2026-07-10 07:35:46 +09:00
bbf94b8a0d feat(creator-channel): 정렬 팝업 옵션을 주입한다 2026-07-10 07:35:39 +09:00
951d7cf561 fix(content): 전체 탭 정렬 값을 보정한다 2026-07-10 07:35:31 +09:00
40337c9590 docs(content): 전체 탭 후속 요구를 기록한다 2026-07-10 07:35:19 +09:00
79a6ecdc0b test(json): 난독화 안정성 검증을 추가한다 2026-07-09 17:39:07 +09:00
33f42e62b6 fix(content): 콘텐츠 개요 query 값을 고정한다 2026-07-09 17:39:01 +09:00
1b660fe542 fix(content): 전체 탭 query 값을 고정한다 2026-07-09 17:38:55 +09:00
d0539b00b0 fix(content): 오디오 랭킹 query 값을 고정한다 2026-07-09 17:38:46 +09:00
e089ef6f8a fix(creator-channel): 콘텐츠 정렬 query 값을 고정한다 2026-07-09 17:38:39 +09:00
a8a70df304 fix(series): 요일 query 값을 고정한다 2026-07-09 17:38:34 +09:00
a207a8f840 fix(home): 홈 랭킹 query 값을 고정한다 2026-07-09 17:38:28 +09:00
6c85f81f98 fix(json): DTO 직렬화 이름을 고정한다 2026-07-09 17:38:18 +09:00
6a88019851 docs(json): 난독화 안정화 요구를 기록한다 2026-07-09 17:38:11 +09:00
379263ec87 fix(content): 추천 오디오 카드 폭을 보정한다 2026-07-09 14:56:19 +09:00
f3d610633c docs(content): 추천 오디오 폭 보정 요구를 기록한다 2026-07-09 14:56:06 +09:00
335db22680 docs(content): 댓글 많은 오디오 UI 보정 기록을 추가한다 2026-07-09 08:46:35 +09:00
b71bb43dd6 fix(content): 최근 댓글 오디오 카드 UI를 보정한다 2026-07-09 08:46:21 +09:00
dace1416c5 fix(content): 추천 섹션 순서를 조정한다 2026-07-09 08:28:23 +09:00
1baf3ac7e0 fix(creator-channel): FanTalk 문구를 한 줄로 표시한다 2026-07-09 08:19:04 +09:00
f3a4578a66 feat(creator-channel): 홈 섹션 이동을 연결한다 2026-07-09 08:19:00 +09:00
b745c3d675 docs(creator-channel): 홈 섹션 이동 요구를 기록한다 2026-07-09 08:18:54 +09:00
f993d343a9 feat(creator-channel): FanTalk 상세 진입점을 연결한다 2026-07-09 07:44:21 +09:00
1ba7846f4c feat(creator-channel): FanTalk 목록 상세 클릭을 추가한다 2026-07-09 07:44:15 +09:00
da26b65a73 feat(creator-channel): FanTalk 상세 화면을 추가한다 2026-07-09 07:44:06 +09:00
04dcb3f09f feat(creator-channel): FanTalk 상세 상태를 구현한다 2026-07-09 07:43:59 +09:00
6916459c9e feat(creator-channel): FanTalk 상세 모델을 추가한다 2026-07-09 07:43:53 +09:00
643fb9fa8e feat(creator-channel): FanTalk 답글 API 응답을 연결한다 2026-07-09 07:43:47 +09:00
f9160ccb02 docs(creator-channel): FanTalk 상세 작업 계획을 기록한다 2026-07-09 07:43:40 +09:00
e2b166de11 docs(creator-channel): FanTalk 상세 요구사항을 기록한다 2026-07-09 07:43:31 +09:00
89f3511864 test(creator-channel): 댓글 답글 후속 계약을 검증한다 2026-07-09 01:28:48 +09:00
3271c9e142 feat(creator-channel): 더보기 메뉴 폰트를 고정한다 2026-07-09 01:28:42 +09:00
1f1069b05d feat(creator-channel): 답글 수정 입력 모드를 구현한다 2026-07-09 01:28:35 +09:00
a325a1bbe9 feat(creator-channel): 댓글 수정 입력 모드를 구현한다 2026-07-09 01:28:29 +09:00
255e6dc67d feat(creator-channel): 더보기 메뉴 문구를 정리한다 2026-07-09 01:28:24 +09:00
b2685959ad docs(creator-channel): 댓글 답글 후속 요구를 기록한다 2026-07-09 01:28:18 +09:00
536effab40 test(creator-channel): 커뮤니티 상세 UI 계약을 검증한다 2026-07-09 00:54:12 +09:00
81537b1e96 feat(creator-channel): 커뮤니티 목록 클릭을 연결한다 2026-07-09 00:54:06 +09:00
e691f78e2e feat(creator-channel): 커뮤니티 상세 진입점을 연결한다 2026-07-09 00:53:59 +09:00
6125b9f7d1 feat(creator-channel): 커뮤니티 답글 화면을 구현한다 2026-07-09 00:53:48 +09:00
d10373d265 feat(creator-channel): 커뮤니티 상세 화면을 구현한다 2026-07-09 00:53:41 +09:00
5c1eb6204a feat(creator-channel): 커뮤니티 전송 아이콘을 추가한다 2026-07-09 00:53:35 +09:00
c3884ea917 feat(creator-channel): 커뮤니티 답글 화면 리소스를 추가한다 2026-07-09 00:53:30 +09:00
bc3fa9546b feat(creator-channel): 커뮤니티 상세 화면 리소스를 추가한다 2026-07-09 00:53:24 +09:00
ea66b6da1e fix(common): UTC 상대시간 파싱을 보완한다 2026-07-09 00:53:17 +09:00
c9e47214ea feat(creator-channel): 커뮤니티 답글 상태 관리를 추가한다 2026-07-09 00:53:12 +09:00
c04d603ac0 feat(creator-channel): 커뮤니티 상세 상태 관리를 추가한다 2026-07-09 00:53:06 +09:00
9e524d626a feat(creator-channel): 커뮤니티 상세 조회 계약을 추가한다 2026-07-09 00:52:59 +09:00
e0dc0c2cb7 docs(creator-channel): 커뮤니티 상세 요구사항을 기록한다 2026-07-09 00:52:49 +09:00
cbd4f04f1b docs(creator-channel): 후원 카드 공통화 작업을 기록한다 2026-07-08 18:44:15 +09:00
eeb777fa01 test(donation): 후원 header 색상 경계를 검증한다 2026-07-08 18:44:09 +09:00
9272db913c test(creator-channel): 후원 카드 바인더 계약을 검증한다 2026-07-08 18:43:56 +09:00
ff4c0104c5 refactor(creator-channel): 후원 카드 바인딩을 공통화한다 2026-07-08 18:43:47 +09:00
31ce0629d2 docs(creator-channel): 후원 header 색상 검증을 기록한다 2026-07-08 16:33:01 +09:00
6ab7878221 fix(creator-channel): 홈 후원 고액 header 색상을 보정한다 2026-07-08 16:32:54 +09:00
ddc18386fb fix(donation): 후원 내역 고액 header 색상을 보정한다 2026-07-08 16:32:46 +09:00
809ed5ce23 refactor(modal): V2 확인 모달 사용처를 교체한다 2026-07-08 16:04:01 +09:00
8e086c4888 feat(modal): V2 공통 모달창을 추가한다 2026-07-08 14:20:27 +09:00
bc6443e5bd docs(modal): V2 공통 모달창 계획을 기록한다 2026-07-08 14:20:20 +09:00
8d205690b3 feat(creator-channel): FanTalk 글쓰기 진입점을 연결한다 2026-07-08 03:44:54 +09:00
25af312d91 feat(creator-channel): FanTalk 글 입력 화면을 연결한다 2026-07-08 03:44:44 +09:00
b020b8096b chore(creator-channel): FanTalk 글 입력 다국어 문구를 추가한다 2026-07-08 03:44:36 +09:00
87f6bec138 feat(creator-channel): FanTalk 글 입력 기본 리소스를 추가한다 2026-07-08 03:44:31 +09:00
34266abd90 feat(creator-channel): FanTalk 글 입력 상태 관리를 추가한다 2026-07-08 03:44:22 +09:00
e8e8a63d6d docs(creator-channel): FanTalk 글 입력 계획을 기록한다 2026-07-08 03:44:15 +09:00
d61867af4e docs(creator-channel): FanTalk 글 입력 PRD를 기록한다 2026-07-08 03:44:09 +09:00
daeb0f75a2 fix(home): 영문 요일 약어 표기를 보완한다 2026-07-06 14:19:07 +09:00
6c497ed1d2 docs(main): 오디오 미니 플레이어 작업을 기록한다 2026-07-06 14:00:48 +09:00
7b975ec54d fix(audio): 오디오 알림 route extra를 전달한다 2026-07-06 14:00:34 +09:00
e3d81792bf fix(main): 오디오 알림 이동과 미니 플레이어를 연결한다 2026-07-06 14:00:23 +09:00
5b9b7eafa7 docs(home): 메인 홈 추천 탭 지금 라이브 중 입장 작업을 기록한다 2026-07-06 02:35:04 +09:00
5489c86cdc fix(home): 메인 홈 추천 탭 지금 라이브 중 입장을 연결한다 2026-07-06 02:34:41 +09:00
b1f605fda4 docs(home): On Air 라이브 아이템 작업을 기록한다 2026-07-03 14:47:44 +09:00
f9030ffd13 fix(home): On Air 라이브 아이템 UI를 보완한다 2026-07-03 14:47:27 +09:00
f74f5a534d docs(home): 팔로잉 최근 소식 구현 검증을 기록한다 2026-06-30 23:54:21 +09:00
3f3bacebd9 fix(home): 팔로잉 최근 소식 nested payload를 반영한다 2026-06-30 23:54:10 +09:00
2041036530 test(home): 팔로잉 최근 소식 nested payload 검증을 추가한다 2026-06-30 23:53:58 +09:00
81a2b541a0 docs(home): 팔로잉 최근 소식 응답 변경을 정의한다 2026-06-30 23:53:47 +09:00
e6cb94179b docs(home): 팔로잉 크리에이터 전체 버튼을 기록한다 2026-06-30 17:43:29 +09:00
658b4f0a9d fix(home): 팔로잉 크리에이터 전체 이동을 연결한다 2026-06-30 17:43:14 +09:00
05cbbdfddc docs(main): title bar 추가 범위를 기록한다 2026-06-30 16:12:52 +09:00
3c66ea986f fix(main): 콘텐츠와 대화 title bar 이동을 연결한다 2026-06-30 16:12:16 +09:00
d1a0cf27a7 fix(home): title bar 메뉴 이동을 연결한다 2026-06-30 15:46:55 +09:00
1aef4b1d89 docs(home): title bar 메뉴 액션 계획을 기록한다 2026-06-30 15:46:36 +09:00
c745481ab2 docs(home): 팔로잉 최근 소식 검증을 기록한다 2026-06-30 15:04:26 +09:00
7e281a15a5 fix(home): 팔로잉 최근 소식 표시를 보완한다 2026-06-30 15:04:14 +09:00
c5e94bbc92 feat(feed): 콘텐츠 카테고리 타입을 보완한다 2026-06-30 15:04:04 +09:00
61fe8a9fee fix(feed): 콘텐츠 카드 레이아웃을 보완한다 2026-06-30 15:03:50 +09:00
498 changed files with 37550 additions and 4475 deletions

1
.gitignore vendored
View File

@@ -319,5 +319,6 @@ app/release/
.kiro/ .kiro/
.omo/ .omo/
.antigravitycli/ .antigravitycli/
.worktrees/
# End of https://www.toptal.com/developers/gitignore/api/macos,android,androidstudio,visualstudiocode,git,kotlin,java # End of https://www.toptal.com/developers/gitignore/api/macos,android,androidstudio,visualstudiocode,git,kotlin,java

View File

@@ -114,12 +114,22 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
- 예: `docs/20260601_메인_홈_추천_UI와_API_연동/` - 예: `docs/20260601_메인_홈_추천_UI와_API_연동/`
- 기존에 생성된 `docs/prd/`, `docs/plan-task/` 문서는 유지하고, 신규 생성 문서부터 위 구조를 적용한다. - 기존에 생성된 `docs/prd/`, `docs/plan-task/` 문서는 유지하고, 신규 생성 문서부터 위 구조를 적용한다.
- 날짜는 `YYYYMMDD` 8자리 숫자를 사용한다. - 날짜는 `YYYYMMDD` 8자리 숫자를 사용한다.
- PRD 문서는 `sample-prd.md` 파일에서 작업에 필요한 부분만 발췌해 작성한다. `sample-prd.md`가 없거나 위치가 불명확하면 추측하지 말고 사용자에게 확인한다. - 문서 샘플의 기준 위치는 `docs/sample/`다.
- PRD 문서는 `docs/sample/sample-prd.md`, 구현 계획/TASK 문서는 `docs/sample/sample-plan-task.md`를 참조해 작업에 필요한 항목을 구체화한다.
- 샘플의 `<...>` placeholder와 예시 값은 실제 작업 값으로 교체한다. 필요한 샘플이 없거나 위치가 불명확하면 추측하지 말고 사용자에게 확인한다.
- 연속된 하나의 작업이라면 별도 새 문서를 만들지 말고 기존 PRD와 계획/TASK 문서에 추가 작업으로 이어서 기록한다. - 연속된 하나의 작업이라면 별도 새 문서를 만들지 말고 기존 PRD와 계획/TASK 문서에 추가 작업으로 이어서 기록한다.
- 작업 도중 범위가 변경되면 계획/TASK 문서 체크리스트를 먼저 업데이트한 뒤 구현한다. - 작업 도중 범위가 변경되면 계획/TASK 문서 체크리스트를 먼저 업데이트한 뒤 구현한다.
- 특정 Phase 또는 Task에 직접 대응되는 검증 기록은 해당 Phase 또는 Task 아래에 한국어로 남긴다. - 특정 Phase 또는 Task에 직접 대응되는 검증 기록은 해당 Phase 또는 Task 아래에 한국어로 남긴다.
- 여러 Phase에 걸치거나 문서 전체에 해당하는 통합 검증, 회귀 검증, 최종 수동 확인 기록은 문서 최하단 `Verification Log`에 한국어로 남긴다. - 여러 Phase에 걸치거나 문서 전체에 해당하는 통합 검증, 회귀 검증, 최종 수동 확인 기록은 문서 최하단 `Verification Log`에 한국어로 남긴다.
- 후속 수정이 발생해도 기존 검증 기록은 삭제하거나 덮어쓰지 않고 위치별로 누적한다. - 자동 테스트는 기본적으로 `app/src/test`의 로직/local unit test만 작성하고 실행한다.
- 사용자가 명시적으로 요청하지 않으면 `app/src/androidTest` UI/계측 테스트를 작성하거나 `connectedDebugAndroidTest`, 기기·에뮬레이터 UI 조작, 스크린샷·시각 QA를 실행하지 않는다.
- UI 변경의 테스트 코드는 adapter 분기, mapper, formatter, presentation model, 라우팅처럼 화면 표현을 결정하는 로직만 검증하며 View 크기·간격·constraint·visibility 등 레이아웃 속성을 직접 검증하지 않는다.
- 코드 리뷰, 코드 품질 점검, 완료 Phase 재검토를 수행하면 `docs/sample/sample-review.md`를 참조해 `docs/[날짜]_구현할내용한글/reviews/[리뷰범위]-review.md`에 결과를 기록한다.
- 리뷰 보고서명의 `[리뷰범위]``phase<번호>-<구현 내용을 나타내는 영문 kebab-case>` 형식으로 작성한다. 예: `phase2-main-home-recommendation-review.md`
- 일반 빌드, 테스트, 린트 실행 결과는 리뷰 보고서를 만들지 않고 계획/TASK 문서의 Task별 검증 기록 또는 `Verification Log`에 남긴다.
- 리뷰에서 수정 항목이 확정되면 코드를 수정하기 전에 review ID, 대상 파일, 수정 범위, 회귀 테스트, 완료 증거를 포함한 신규 회귀 수정 Task를 `plan-task.md`의 해당 Phase에 추가하고 그 Task에 따라 바로 수정한다.
- 리뷰 후속 수정 시 기존 완료 Task를 다시 열지 않으며, 기존 완료 체크박스와 검증·리뷰 기록은 삭제하거나 덮어쓰지 않고 위치별로 누적한다.
- 샘플의 위치, 문서 구조, 작성·운용 규칙이 변경되면 같은 작업에서 `AGENTS.md`와 관련 가이드 문서를 함께 갱신한다.
## 상세 참조 문서 ## 상세 참조 문서
- 빌드/린트/테스트는 `docs/agent-guides/build-test-style.md`를 참고한다. - 빌드/린트/테스트는 `docs/agent-guides/build-test-style.md`를 참고한다.

View File

@@ -86,8 +86,8 @@ android {
applicationId "kr.co.vividnext.sodalive" applicationId "kr.co.vividnext.sodalive"
minSdk 23 minSdk 23
targetSdk 35 targetSdk 35
versionCode 239 versionCode 246
versionName "1.55.0" versionName "1.56.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -116,8 +116,6 @@ android {
// release용 ad unit id는 배포 전 실제 값으로 교체한다. // release용 ad unit id는 배포 전 실제 값으로 교체한다.
buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID', '"R-M-19140295-3"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID', '"R-M-19140295-3"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID', '"R-M-19140295-4"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID', '"R-M-19140295-4"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_AUDIO_CONTENT_DETAIL_AD_UNIT_ID', '"R-M-19140295-5"'
buildConfigField 'String', 'YANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_ID', '"R-M-19140295-6"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_CREATOR_COMMUNITY_ALL_AD_UNIT_ID', '"R-M-19140295-7"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_CREATOR_COMMUNITY_ALL_AD_UNIT_ID', '"R-M-19140295-7"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_HOME_AD_UNIT_ID', '"R-M-19140295-8"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_HOME_AD_UNIT_ID', '"R-M-19140295-8"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_DAY_OF_WEEK_AD_UNIT_ID', '"R-M-19140295-9"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_DAY_OF_WEEK_AD_UNIT_ID', '"R-M-19140295-9"'
@@ -160,8 +158,6 @@ android {
buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID', '"R-M-19140297-3"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_TAB_AD_UNIT_ID', '"R-M-19140297-3"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID', '"R-M-19140297-4"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_LIVE_ROOM_DETAIL_AD_UNIT_ID', '"R-M-19140297-4"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_AUDIO_CONTENT_DETAIL_AD_UNIT_ID', '"R-M-19140297-5"'
buildConfigField 'String', 'YANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_ID', '"R-M-19140297-6"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_CREATOR_COMMUNITY_ALL_AD_UNIT_ID', '"R-M-19140297-7"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_CREATOR_COMMUNITY_ALL_AD_UNIT_ID', '"R-M-19140297-7"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_HOME_AD_UNIT_ID', '"R-M-19140297-8"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_HOME_AD_UNIT_ID', '"R-M-19140297-8"'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_DAY_OF_WEEK_AD_UNIT_ID', '"R-M-19140297-9"' buildConfigField 'String', 'YANDEX_INLINE_BANNER_SERIES_MAIN_DAY_OF_WEEK_AD_UNIT_ID', '"R-M-19140297-9"'
@@ -271,7 +267,7 @@ dependencies {
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel'
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'
} }
implementation "androidx.datastore:datastore-preferences:1.2.0" implementation "androidx.datastore:datastore-preferences:1.2.1"
// Gson // Gson
implementation "com.google.code.gson:gson:2.13.2" implementation "com.google.code.gson:gson:2.13.2"

View File

@@ -114,6 +114,18 @@
<activity android:name=".v2.main.MainV2Activity" /> <activity android:name=".v2.main.MainV2Activity" />
<activity android:name=".v2.main.content.overview.ContentOverviewActivity" /> <activity android:name=".v2.main.content.overview.ContentOverviewActivity" />
<activity android:name=".v2.creator.channel.CreatorChannelActivity" /> <activity android:name=".v2.creator.channel.CreatorChannelActivity" />
<activity
android:name=".v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity
android:name=".v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity
android:name=".v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity
android:name=".v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity android:name=".v2.live.onair.HomeOnAirLiveActivity" /> <activity android:name=".v2.live.onair.HomeOnAirLiveActivity" />
<activity <activity
android:name=".v2.main.chat.dm.DmChatRoomActivity" android:name=".v2.main.chat.dm.DmChatRoomActivity"

View File

@@ -361,17 +361,20 @@ class Agora(
fun deInitRtmClient(rtmEventListener: RtmEventListener) { fun deInitRtmClient(rtmEventListener: RtmEventListener) {
rtmClient?.removeEventListener(rtmEventListener) rtmClient?.removeEventListener(rtmEventListener)
rtmClient?.unsubscribe(roomChannelName, object : ResultCallback<Void> { rtmClient?.unsubscribe(
override fun onSuccess(responseInfo: Void?) { roomChannelName,
Logger.e("RTM unsubscribe - $roomChannelName") object : ResultCallback<Void> {
roomChannelName = null override fun onSuccess(responseInfo: Void?) {
} Logger.e("RTM unsubscribe - $roomChannelName")
roomChannelName = null
}
override fun onFailure(errorInfo: ErrorInfo) { override fun onFailure(errorInfo: ErrorInfo) {
Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}") Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}")
Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}") Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}")
}
} }
}) )
rtmClient?.unsubscribe( rtmClient?.unsubscribe(
"inbox_${SharedPreferenceManager.userId}", "inbox_${SharedPreferenceManager.userId}",
object : ResultCallback<Void> { object : ResultCallback<Void> {
@@ -383,7 +386,8 @@ class Agora(
Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}") Logger.e("RTM unsubscribe fail - ${errorInfo.errorCode}")
Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}") Logger.e("RTM unsubscribe fail - ${errorInfo.errorReason}")
} }
}) }
)
rtmClient?.logout(object : ResultCallback<Void> { rtmClient?.logout(object : ResultCallback<Void> {
override fun onSuccess(responseInfo: Void?) { override fun onSuccess(responseInfo: Void?) {
Logger.e("RTM logout") Logger.e("RTM logout")
@@ -398,7 +402,6 @@ class Agora(
// 상태 리셋 // 상태 리셋
rtmLoggedIn = false rtmLoggedIn = false
rtmLoginInProgress = false rtmLoginInProgress = false
} }
// endregion // endregion
} }

View File

@@ -136,7 +136,7 @@ class SodaLiveApp : Application(), DefaultLifecycleObserver {
applicationContext, applicationContext,
BuildConfig.NOTIFLY_PROJECT_ID, BuildConfig.NOTIFLY_PROJECT_ID,
BuildConfig.NOTIFLY_USERNAME, BuildConfig.NOTIFLY_USERNAME,
BuildConfig.NOTIFLY_PASSWORD, BuildConfig.NOTIFLY_PASSWORD
) )
} }

View File

@@ -14,5 +14,5 @@ data class AddAllPlaybackTrackingRequest(
data class PlaybackTrackingData( data class PlaybackTrackingData(
@SerializedName("contentId") val contentId: Long, @SerializedName("contentId") val contentId: Long,
@SerializedName("playDateTime") val playDateTime: String, @SerializedName("playDateTime") val playDateTime: String,
@SerializedName("isPreview") val isPreview: Boolean, @SerializedName("isPreview") val isPreview: Boolean
) )

View File

@@ -1,3 +1,5 @@
@file:Suppress("ktlint:package-name", "ktlint:standard:package-name")
package kr.co.vividnext.sodalive.audio_content package kr.co.vividnext.sodalive.audio_content
import android.app.NotificationChannel import android.app.NotificationChannel
@@ -471,9 +473,17 @@ class AudioContentPlayService :
} }
private fun updateNotification() { private fun updateNotification() {
val intent = Intent(this, MainV2Activity::class.java) val intent = Intent(this, MainV2Activity::class.java).apply {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra(
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
MainV2Activity.ROUTE_AUDIO_DETAIL
)
contentId?.let {
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
}
}
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
this, this,
0, 0,

View File

@@ -127,7 +127,7 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba
compositeDisposable.add( compositeDisposable.add(
repository.getCategoryList( repository.getCategoryList(
creatorId = userId, creatorId = userId,
token = "Bearer ${SharedPreferenceManager.token}", token = "Bearer ${SharedPreferenceManager.token}"
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())

View File

@@ -10,5 +10,5 @@ enum class PurchaseOption {
BUY_ONLY, BUY_ONLY,
@SerializedName("RENT_ONLY") @SerializedName("RENT_ONLY")
RENT_ONLY, RENT_ONLY
} }

View File

@@ -21,7 +21,7 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
class AudioContentNewAllAdapter( class AudioContentNewAllAdapter(
private val itemWidth: Int, private val itemWidth: Int,
private val onClickItem: (Long) -> Unit, private val onClickItem: (Long) -> Unit,
private val onClickCreator: (Long) -> Unit, private val onClickCreator: (Long) -> Unit
) : RecyclerView.Adapter<AudioContentNewAllAdapter.ViewHolder>() { ) : RecyclerView.Adapter<AudioContentNewAllAdapter.ViewHolder>() {
inner class ViewHolder( inner class ViewHolder(

View File

@@ -35,7 +35,7 @@ class AudioContentCommentReplyAdapter(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
), )
) )
} else { } else {
AudioContentCommentReplyItemViewHolder( AudioContentCommentReplyItemViewHolder(
@@ -172,9 +172,7 @@ class AudioContentCommentReplyItemViewHolder(
private val context: Context, private val context: Context,
private val creatorId: Long, private val creatorId: Long,
private val binding: ItemAudioContentCommentReplyBinding, private val binding: ItemAudioContentCommentReplyBinding,
private val showOptionMenu: ( private val showOptionMenu: (Context, View, Long, Long, Long, onClickModify: () -> Unit) -> Unit,
Context, View, Long, Long, Long, onClickModify: () -> Unit
) -> Unit,
private val modifyComment: (Long, String) -> Unit private val modifyComment: (Long, String) -> Unit
) : AudioContentCommentReplyViewHolder(binding) { ) : AudioContentCommentReplyViewHolder(binding) {

View File

@@ -1,3 +1,5 @@
@file:Suppress("ktlint:package-name", "ktlint:standard:package-name")
package kr.co.vividnext.sodalive.audio_content.detail package kr.co.vividnext.sodalive.audio_content.detail
import android.annotation.SuppressLint import android.annotation.SuppressLint
@@ -10,6 +12,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
@@ -28,18 +31,6 @@ import coil.transform.RoundedCornersTransformation
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.google.gson.Gson import com.google.gson.Gson
import com.orhanobut.logger.Logger
import com.yandex.mobile.ads.banner.BannerAdSize
import com.yandex.mobile.ads.common.AdError
import com.yandex.mobile.ads.common.AdRequest
import com.yandex.mobile.ads.common.AdRequestConfiguration
import com.yandex.mobile.ads.common.AdRequestError
import com.yandex.mobile.ads.common.ImpressionData
import com.yandex.mobile.ads.interstitial.InterstitialAd
import com.yandex.mobile.ads.interstitial.InterstitialAdEventListener
import com.yandex.mobile.ads.interstitial.InterstitialAdLoadListener
import com.yandex.mobile.ads.interstitial.InterstitialAdLoader
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
import kr.co.vividnext.sodalive.audio_content.PurchaseOption import kr.co.vividnext.sodalive.audio_content.PurchaseOption
@@ -72,7 +63,6 @@ import kr.co.vividnext.sodalive.report.ReportType
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.roundToInt
@UnstableApi @UnstableApi
class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>( class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBinding>(
@@ -101,42 +91,9 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
private lateinit var orderType: OrderType private lateinit var orderType: OrderType
private lateinit var imm: InputMethodManager private lateinit var imm: InputMethodManager
private var audioContentPlayInterstitialAdLoader: InterstitialAdLoader? = null
private var audioContentPlayInterstitialAd: InterstitialAd? = null
private var pendingAudioContentPlayAction: (() -> Unit)? = null
private var hasConsumedAudioContentPlayInterstitialAttempt = false
private var isAudioContentPlaying = false private var isAudioContentPlaying = false
private var isAudioContentInterstitialEligible = false
private var audioContentStartPlaybackAction: (() -> Unit)? = null private var audioContentStartPlaybackAction: (() -> Unit)? = null
private val audioContentPlayInterstitialAdLoadListener = object : InterstitialAdLoadListener {
override fun onAdLoaded(interstitialAd: InterstitialAd) {
clearAudioContentPlayInterstitialAd()
audioContentPlayInterstitialAd = interstitialAd
}
override fun onAdFailedToLoad(error: AdRequestError) {
Logger.e("Audio content interstitial failed to load: ${error.description}")
}
}
private val audioContentPlayInterstitialAdEventListener = object : InterstitialAdEventListener {
override fun onAdShown() = Unit
override fun onAdFailedToShow(adError: AdError) {
Logger.e("Audio content interstitial failed to show: ${adError.description}")
continuePendingAudioContentPlayAction()
}
override fun onAdDismissed() {
continuePendingAudioContentPlayAction()
}
override fun onAdClicked() = Unit
override fun onAdImpression(impressionData: ImpressionData?) = Unit
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
@@ -154,9 +111,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
binding.rlPreviewAlert.visibility = View.GONE binding.rlPreviewAlert.visibility = View.GONE
audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0) audioContentId = intent.getLongExtra(Constants.EXTRA_AUDIO_CONTENT_ID, 0)
hasConsumedAudioContentPlayInterstitialAttempt = false
releaseAudioContentPlayInterstitial()
setupAudioContentPlayInterstitial()
viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() } viewModel.getAudioContentDetail(audioContentId = audioContentId) { finish() }
} }
@@ -367,91 +321,9 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
dialog.show(screenWidth - 26.7f.dpToPx().toInt()) dialog.show(screenWidth - 26.7f.dpToPx().toInt())
} }
setupAudioContentDetailInlineBanner()
setupAudioContentPlayInterstitial()
setupBuyerList() setupBuyerList()
} }
private fun setupAudioContentDetailInlineBanner() {
binding.yandexInlineBannerView.post {
val density = resources.displayMetrics.density
val adWidthPixels = binding.yandexInlineBannerView.width.takeIf { it > 0 } ?: screenWidth
val adWidthDp = (adWidthPixels / density).roundToInt()
val maxAdHeightDp = 90
binding.yandexInlineBannerView.apply {
setAdUnitId(BuildConfig.YANDEX_INLINE_BANNER_AUDIO_CONTENT_DETAIL_AD_UNIT_ID)
setAdSize(
BannerAdSize.inlineSize(
this@AudioContentDetailActivity,
adWidthDp,
maxAdHeightDp
)
)
loadAd(AdRequest.Builder().build())
}
}
}
private fun setupAudioContentPlayInterstitial() {
val adUnitId = BuildConfig.YANDEX_INTERSTITIAL_AUDIO_CONTENT_PLAY_AD_UNIT_ID
if (adUnitId.isBlank()) {
Logger.e("Audio content interstitial blocked: ad unit id is blank.")
return
}
audioContentPlayInterstitialAdLoader = InterstitialAdLoader(this).apply {
setAdLoadListener(audioContentPlayInterstitialAdLoadListener)
}
audioContentPlayInterstitialAdLoader?.loadAd(
AdRequestConfiguration.Builder(adUnitId).build()
)
}
private fun playAudioContentWithInterstitialIfAvailable(playAction: () -> Unit) {
if (hasConsumedAudioContentPlayInterstitialAttempt || isFinishing || isDestroyed) {
playAction()
return
}
val interstitialAd = audioContentPlayInterstitialAd ?: run {
playAction()
return
}
hasConsumedAudioContentPlayInterstitialAttempt = true
pendingAudioContentPlayAction = playAction
interstitialAd.setAdEventListener(audioContentPlayInterstitialAdEventListener)
runCatching {
interstitialAd.show(this)
}.onFailure {
Logger.e("Audio content interstitial failed to show: ${it.message}")
continuePendingAudioContentPlayAction()
}
}
private fun continuePendingAudioContentPlayAction() {
val playAction = pendingAudioContentPlayAction
pendingAudioContentPlayAction = null
clearAudioContentPlayInterstitialAd()
if (isFinishing || isDestroyed) {
return
}
playAction?.invoke()
}
private fun clearAudioContentPlayInterstitialAd() {
audioContentPlayInterstitialAd?.setAdEventListener(null)
audioContentPlayInterstitialAd = null
}
private fun releaseAudioContentPlayInterstitial() {
audioContentPlayInterstitialAdLoader?.setAdLoadListener(null)
audioContentPlayInterstitialAdLoader = null
pendingAudioContentPlayAction = null
clearAudioContentPlayInterstitialAd()
}
private fun pauseAudioContentPlayback() { private fun pauseAudioContentPlayback() {
startService( startService(
Intent(this, AudioContentPlayService::class.java).apply { Intent(this, AudioContentPlayService::class.java).apply {
@@ -474,13 +346,7 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play) binding.ivPlayOrPause.setImageResource(R.drawable.btn_audio_content_play)
val startPlaybackClickListener = View.OnClickListener { val startPlaybackClickListener = View.OnClickListener { startPlaybackAction() }
if (isAudioContentInterstitialEligible) {
playAudioContentWithInterstitialIfAvailable(startPlaybackAction)
} else {
startPlaybackAction()
}
}
binding.ivPlayOrPause.setOnClickListener(startPlaybackClickListener) binding.ivPlayOrPause.setOnClickListener(startPlaybackClickListener)
binding.llPreview.setOnClickListener(startPlaybackClickListener) binding.llPreview.setOnClickListener(startPlaybackClickListener)
@@ -600,6 +466,11 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
viewModel.isExpandDetail.observe(this) { viewModel.isExpandDetail.observe(this) {
binding.tvDetail.ellipsize = if (it) {
null
} else {
TextUtils.TruncateAt.END
}
binding.tvDetail.maxLines = if (it) { binding.tvDetail.maxLines = if (it) {
Int.MAX_VALUE Int.MAX_VALUE
} else { } else {
@@ -944,12 +815,10 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
response.duration response.duration
) )
audioContentStartPlaybackAction = null audioContentStartPlaybackAction = null
isAudioContentInterstitialEligible = false
isAlertPreview = response.creator.creatorId != SharedPreferenceManager.userId && isAlertPreview = response.creator.creatorId != SharedPreferenceManager.userId &&
!response.existOrdered && !response.existOrdered &&
response.price > 0 response.price > 0
isAudioContentInterstitialEligible = response.price <= 0 || isAlertPreview
if ( if (
response.creator.creatorId != SharedPreferenceManager.userId && !response.existOrdered && response.creator.creatorId != SharedPreferenceManager.userId && !response.existOrdered &&
@@ -1046,7 +915,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
} else if (response.releaseDate == null) { } else if (response.releaseDate == null) {
audioContentStartPlaybackAction = null audioContentStartPlaybackAction = null
isAudioContentInterstitialEligible = false
binding.llPreviewNo.visibility = View.VISIBLE binding.llPreviewNo.visibility = View.VISIBLE
} }
@@ -1366,8 +1234,6 @@ class AudioContentDetailActivity : BaseActivity<ActivityAudioContentDetailBindin
} }
override fun onDestroy() { override fun onDestroy() {
binding.yandexInlineBannerView.destroy()
releaseAudioContentPlayInterstitial()
super.onDestroy() super.onDestroy()
} }

View File

@@ -51,7 +51,7 @@ data class GetAudioContentDetailResponse(
data class OtherContentResponse( data class OtherContentResponse(
@SerializedName("contentId") val contentId: Long, @SerializedName("contentId") val contentId: Long,
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("coverUrl") val coverUrl: String, @SerializedName("coverUrl") val coverUrl: String
) )
@Keep @Keep

View File

@@ -25,10 +25,15 @@ class AudioContentMainNewContentThemeAdapter(
fun bind(theme: String) { fun bind(theme: String) {
if ( if (
theme == selectedTheme || theme == selectedTheme ||
(selectedTheme == "" && theme == SodaLiveApplicationHolder.get() (
.getString(R.string.audio_content_label_all)) || selectedTheme == "" && theme == SodaLiveApplicationHolder.get()
(selectedTheme == "" && theme == SodaLiveApplicationHolder.get() .getString(R.string.audio_content_label_all)) ||
.getString(R.string.screen_home_sort_revenue)) (
selectedTheme == "" && theme == SodaLiveApplicationHolder.get()
.getString(
R.string.screen_home_sort_revenue
)
)
) { ) {
binding.tvTheme.setBackgroundResource( binding.tvTheme.setBackgroundResource(
R.drawable.bg_round_corner_16_7_transparent_3bb9f1 R.drawable.bg_round_corner_16_7_transparent_3bb9f1

View File

@@ -76,7 +76,8 @@ class AudioContentModifyActivity : BaseActivity<ActivityAudioContentModifyBindin
excludeGif = true, excludeGif = true,
isEnabledFreeStyleCrop = true, isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config( config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f, aspectX = 1f,
aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG, compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90 compressQuality = 90
), ),

View File

@@ -28,7 +28,7 @@ class AudioContentOrderConfirmDialog(
orderType: OrderType, orderType: OrderType,
price: Int, price: Int,
isAvailableUsePoint: Boolean, isAvailableUsePoint: Boolean,
confirmButtonClick: () -> Unit, confirmButtonClick: () -> Unit
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog

View File

@@ -1,20 +1,9 @@
package kr.co.vividnext.sodalive.audio_content.order package kr.co.vividnext.sodalive.audio_content.order
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentOrderListBinding import kr.co.vividnext.sodalive.databinding.ActivityAudioContentOrderListBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class AudioContentOrderListActivity : BaseActivity<ActivityAudioContentOrderListBinding>( class AudioContentOrderListActivity : BaseActivity<ActivityAudioContentOrderListBinding>(
ActivityAudioContentOrderListBinding::inflate ActivityAudioContentOrderListBinding::inflate

View File

@@ -20,5 +20,5 @@ data class GetAudioContentOrderListItem(
@SerializedName("isAdult") val isAdult: Boolean, @SerializedName("isAdult") val isAdult: Boolean,
@SerializedName("orderType") val orderType: OrderType, @SerializedName("orderType") val orderType: OrderType,
@SerializedName("likeCount") val likeCount: Int, @SerializedName("likeCount") val likeCount: Int,
@SerializedName("commentCount") val commentCount: Int, @SerializedName("commentCount") val commentCount: Int
) )

View File

@@ -429,7 +429,6 @@ class AudioContentPlayerFragment(
mediaController?.seekTo(progress.toLong()) mediaController?.seekTo(progress.toLong())
} }
} }
}) })
updateMediaMetadata(mediaController?.currentMediaItem?.mediaMetadata) updateMediaMetadata(mediaController?.currentMediaItem?.mediaMetadata)

View File

@@ -1,3 +1,5 @@
@file:Suppress("ktlint:package-name", "ktlint:standard:package-name")
package kr.co.vividnext.sodalive.audio_content.player package kr.co.vividnext.sodalive.audio_content.player
import android.app.PendingIntent import android.app.PendingIntent
@@ -155,6 +157,10 @@ class AudioContentPlayerService : MediaSessionService() {
private fun initMediaSession() { private fun initMediaSession() {
val contextIntent = Intent(applicationContext, MainV2Activity::class.java).apply { val contextIntent = Intent(applicationContext, MainV2Activity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra(
MainV2Activity.EXTRA_AUDIO_NOTIFICATION_ROUTE,
MainV2Activity.ROUTE_AUDIO_PLAYER
)
} }
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
applicationContext, applicationContext,

View File

@@ -1,8 +1,10 @@
package kr.co.vividnext.sodalive.audio_content.playlist.modify package kr.co.vividnext.sodalive.audio_content.playlist.modify
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.audio_content.playlist.create.PlaylistContentIdAndOrder import kr.co.vividnext.sodalive.audio_content.playlist.create.PlaylistContentIdAndOrder
@Keep
data class UpdatePlaylistRequest( data class UpdatePlaylistRequest(
@SerializedName("title") val title: String? = null, @SerializedName("title") val title: String? = null,
@SerializedName("desc") val desc: String? = null, @SerializedName("desc") val desc: String? = null,

View File

@@ -14,8 +14,11 @@ import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
class SeriesListAllViewModel(private val repository: SeriesRepository) : BaseViewModel() { class SeriesListAllViewModel(private val repository: SeriesRepository) : BaseViewModel() {
enum class SeriesSortType { enum class SeriesSortType {
@SerializedName("NEWEST") NEWEST, @SerializedName("NEWEST")
@SerializedName("OLDEST") OLDEST NEWEST,
@SerializedName("OLDEST")
OLDEST
} }
private val _toastLiveData = MutableLiveData<String?>() private val _toastLiveData = MutableLiveData<String?>()

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.audio_content.series.detail package kr.co.vividnext.sodalive.audio_content.series.detail
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle

View File

@@ -5,7 +5,6 @@ import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenreListResponse import kr.co.vividnext.sodalive.audio_content.series.main.by_genre.GetSeriesGenreListResponse
import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse import kr.co.vividnext.sodalive.audio_content.series.main.home.SeriesHomeResponse
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.Query import retrofit2.http.Query
@@ -23,7 +22,7 @@ interface SeriesMainApi {
@GET("/audio-content/series/main/day-of-week") @GET("/audio-content/series/main/day-of-week")
fun getDayOfWeekSeriesList( fun getDayOfWeekSeriesList(
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek, @Query("dayOfWeek") dayOfWeek: String,
@Query("page") page: Int, @Query("page") page: Int,
@Query("size") size: Int, @Query("size") size: Int,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String

View File

@@ -19,7 +19,7 @@ class SeriesMainRepository(
size: Int, size: Int,
token: String token: String
) = api.getDayOfWeekSeriesList( ) = api.getDayOfWeekSeriesList(
dayOfWeek = dayOfWeek, dayOfWeek = dayOfWeek.queryValue,
page = page - 1, page = page - 1,
size = size, size = size,
authHeader = token authHeader = token

View File

@@ -240,7 +240,7 @@ class AudioContentUploadActivity : BaseActivity<ActivityAudioContentUploadBindin
title = getString(R.string.audio_content_upload_dialog_title), title = getString(R.string.audio_content_upload_dialog_title),
desc = getString(R.string.audio_content_upload_dialog_desc), desc = getString(R.string.audio_content_upload_dialog_desc),
confirmButtonTitle = getString(R.string.confirm), confirmButtonTitle = getString(R.string.confirm),
confirmButtonClick = { finish() }, confirmButtonClick = { finish() }
).show(screenWidth) ).show(screenWidth)
} }
} }

View File

@@ -20,5 +20,5 @@ data class CreateAudioContentRequest(
@SerializedName("isPointAvailable") val isPointAvailable: Boolean, @SerializedName("isPointAvailable") val isPointAvailable: Boolean,
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean, @SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
@SerializedName("previewStartTime") val previewStartTime: String? = null, @SerializedName("previewStartTime") val previewStartTime: String? = null,
@SerializedName("previewEndTime") val previewEndTime: String? = null, @SerializedName("previewEndTime") val previewEndTime: String? = null
) )

View File

@@ -80,13 +80,7 @@ class AuditionApplicantListAdapter(
binding.sbProgress.max = currentTotalDuration binding.sbProgress.max = currentTotalDuration
binding.sbProgress.progress = currentTime binding.sbProgress.progress = currentTime
binding.tvTotalDuration.text = binding.tvTotalDuration.text = "/${Utils.convertDurationToString(currentTotalDuration, showHours = false)}"
"/${
Utils.convertDurationToString(
currentTotalDuration,
showHours = false
)
}"
binding.tvCurrentTime.text = Utils.convertDurationToString( binding.tvCurrentTime.text = Utils.convertDurationToString(
currentTime, currentTime,
showHours = false showHours = false

View File

@@ -17,7 +17,6 @@ import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailActivity
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.ToastMessage
import kr.co.vividnext.sodalive.databinding.ActivityAuditionDetailBinding import kr.co.vividnext.sodalive.databinding.ActivityAuditionDetailBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject

View File

@@ -43,7 +43,8 @@ class CharacterAdapter(
translationY = fm.descent translationY = fm.descent
return true return true
} }
}) }
)
} }
} else { } else {
binding.tvRanking.visibility = View.GONE binding.tvRanking.visibility = View.GONE

View File

@@ -17,7 +17,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.UiText
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentListBinding import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentListBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
@@ -151,8 +150,7 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val lastVisible = (recyclerView.layoutManager as LinearLayoutManager) val lastVisible = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
.findLastCompletelyVisibleItemPosition()
val total = recyclerView.adapter?.itemCount ?: 0 val total = recyclerView.adapter?.itemCount ?: 0
if (!recyclerView.canScrollVertically(1) && lastVisible == total - 1) { if (!recyclerView.canScrollVertically(1) && lastVisible == total - 1) {
viewModel.getCommentList(characterId) viewModel.getCommentList(characterId)
@@ -195,7 +193,6 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
imm.hideSoftInputFromWindow(view?.windowToken, 0) imm.hideSoftInputFromWindow(view?.windowToken, 0)
} }
companion object { companion object {
private const val EXTRA_CHARACTER_ID = "extra_character_id" private const val EXTRA_CHARACTER_ID = "extra_character_id"
fun newInstance(characterId: Long): CharacterCommentListFragment { fun newInstance(characterId: Long): CharacterCommentListFragment {

View File

@@ -15,7 +15,6 @@ import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.UiText
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentReplyBinding import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentReplyBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
@@ -48,15 +47,19 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
characterId = arguments?.getLong(EXTRA_CHARACTER_ID) ?: 0 characterId = arguments?.getLong(EXTRA_CHARACTER_ID) ?: 0
original = arguments?.let { original = arguments?.let {
val cid = it.getLong(EXTRA_ORIGINAL_COMMENT_ID, -1) val cid = it.getLong(EXTRA_ORIGINAL_COMMENT_ID, -1)
if (cid == -1L) null else CharacterCommentResponse( if (cid == -1L) {
commentId = cid, null
memberId = it.getLong(EXTRA_ORIGINAL_MEMBER_ID), } else {
memberProfileImage = it.getString(EXTRA_ORIGINAL_MEMBER_PROFILE_IMAGE) ?: "", CharacterCommentResponse(
memberNickname = it.getString(EXTRA_ORIGINAL_MEMBER_NICKNAME) ?: "", commentId = cid,
createdAt = it.getLong(EXTRA_ORIGINAL_CREATED_AT), memberId = it.getLong(EXTRA_ORIGINAL_MEMBER_ID),
replyCount = it.getInt(EXTRA_ORIGINAL_REPLY_COUNT), memberProfileImage = it.getString(EXTRA_ORIGINAL_MEMBER_PROFILE_IMAGE) ?: "",
comment = it.getString(EXTRA_ORIGINAL_COMMENT_TEXT) ?: "" memberNickname = it.getString(EXTRA_ORIGINAL_MEMBER_NICKNAME) ?: "",
) createdAt = it.getLong(EXTRA_ORIGINAL_CREATED_AT),
replyCount = it.getInt(EXTRA_ORIGINAL_REPLY_COUNT),
comment = it.getString(EXTRA_ORIGINAL_COMMENT_TEXT) ?: ""
)
}
} }
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }
@@ -140,7 +143,7 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
outRect.right = 24f.dpToPx().toInt() outRect.right = 24f.dpToPx().toInt()
when (parent.getChildAdapterPosition(view)) { when (parent.getChildAdapterPosition(view)) {
0 -> { 0 -> {
outRect.top = 24f.dpToPx().toInt(); outRect.top = 24f.dpToPx().toInt()
outRect.bottom = 12f.dpToPx().toInt() outRect.bottom = 12f.dpToPx().toInt()
} }
@@ -196,7 +199,6 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
} }
} }
companion object { companion object {
private const val EXTRA_CHARACTER_ID = "extra_character_id" private const val EXTRA_CHARACTER_ID = "extra_character_id"
private const val EXTRA_ORIGINAL_COMMENT_ID = "extra_original_comment_id" private const val EXTRA_ORIGINAL_COMMENT_ID = "extra_original_comment_id"

View File

@@ -1,6 +1,5 @@
package kr.co.vividnext.sodalive.chat.character.comment package kr.co.vividnext.sodalive.chat.character.comment
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup

View File

@@ -434,5 +434,4 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
binding.tvPersonalityContent.maxLines = 3 binding.tvPersonalityContent.maxLines = 3
binding.tvPersonalityContent.ellipsize = TextUtils.TruncateAt.END binding.tvPersonalityContent.ellipsize = TextUtils.TruncateAt.END
} }
} }

View File

@@ -29,6 +29,7 @@ data class CharacterDetailResponse(
enum class CharacterType { enum class CharacterType {
@SerializedName("Clone") @SerializedName("Clone")
CLONE, CLONE,
@SerializedName("Character") @SerializedName("Character")
CHARACTER CHARACTER
} }

View File

@@ -49,7 +49,6 @@ class OtherCharacterAdapter(
transformations(RoundedCornersTransformation(16f.dpToPx())) transformations(RoundedCornersTransformation(16f.dpToPx()))
} }
binding.root.setOnClickListener { binding.root.setOnClickListener {
onItemClick?.invoke(item) onItemClick?.invoke(item)
} }

View File

@@ -118,9 +118,7 @@ class CharacterGalleryViewModel(
val token = "Bearer ${SharedPreferenceManager.token}" val token = "Bearer ${SharedPreferenceManager.token}"
isPurchasing = true isPurchasing = true
_uiState.value = _uiState.value?.copy( _uiState.value = _uiState.value?.copy(isLoading = isRequesting || isPurchasing)
isLoading = isRequesting || isPurchasing,
)
compositeDisposable.add( compositeDisposable.add(
repository.purchaseCharacterImage(token = token, imageId = imageId) repository.purchaseCharacterImage(token = token, imageId = imageId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())

View File

@@ -1,17 +1,18 @@
package kr.co.vividnext.sodalive.chat.original package kr.co.vividnext.sodalive.chat.original
import androidx.annotation.Keep import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep @Keep
data class OriginalWorkListResponse( data class OriginalWorkListResponse(
val totalCount: Long, @SerializedName("totalCount") val totalCount: Long,
val content: List<OriginalWorkListItemResponse> @SerializedName("content") val content: List<OriginalWorkListItemResponse>
) )
@Keep @Keep
data class OriginalWorkListItemResponse( data class OriginalWorkListItemResponse(
val id: Long, @SerializedName("id") val id: Long,
val imageUrl: String?, @SerializedName("imageUrl") val imageUrl: String?,
val title: String, @SerializedName("title") val title: String,
val contentType: String @SerializedName("contentType") val contentType: String
) )

View File

@@ -85,7 +85,6 @@ class OriginalWorkDetailActivity : BaseActivity<ActivityOriginalWorkDetailBindin
override fun onTabReselected(tab: TabLayout.Tab) { override fun onTabReselected(tab: TabLayout.Tab) {
} }
}) })
} }

View File

@@ -99,7 +99,6 @@ class ChatBackgroundPickerDialogFragment : DialogFragment() {
items.addAll(list.map { BgItem(id = it.id, url = it.imageUrl) }) items.addAll(list.map { BgItem(id = it.id, url = it.imageUrl) })
adapter.submit(items, selectedId) adapter.submit(items, selectedId)
loadingDialog.dismiss() loadingDialog.dismiss()
}, { _ -> }, { _ ->
// 실패 시에도 현재까지의 목록 표시(없으면 빈 목록) // 실패 시에도 현재까지의 목록 표시(없으면 빈 목록)
@@ -161,9 +160,7 @@ class ChatBackgroundPickerDialogFragment : DialogFragment() {
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BgVH { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BgVH {
val binding = ItemChatBackgroundImageBinding.inflate( val binding = ItemChatBackgroundImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
LayoutInflater.from(parent.context), parent, false
)
return BgVH(binding, onClick) return BgVH(binding, onClick)
} }

View File

@@ -655,14 +655,11 @@ class ChatMessageAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// endregion // endregion
/** 쿼터 안내 메시지 뷰홀더: 광고 보기 + 캔 구매 버튼 */
class QuotaNoticeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class QuotaNoticeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val btnRewardedAd: View = itemView.findViewById(R.id.ll_rewarded_ad)
private val btnPurchase10Can: View = itemView.findViewById(R.id.ll_purchase_10_can) private val btnPurchase10Can: View = itemView.findViewById(R.id.ll_purchase_10_can)
private val btnPurchase20Can: View = itemView.findViewById(R.id.ll_purchase_20_can) private val btnPurchase20Can: View = itemView.findViewById(R.id.ll_purchase_20_can)
fun bind(onAction: (ChatQuotaNoticeAction) -> Unit) { fun bind(onAction: (ChatQuotaNoticeAction) -> Unit) {
btnRewardedAd.setOnClickListener { onAction(ChatQuotaNoticeAction.REWARDED_AD) }
btnPurchase10Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_10_CAN) } btnPurchase10Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_10_CAN) }
btnPurchase20Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_20_CAN) } btnPurchase20Can.setOnClickListener { onAction(ChatQuotaNoticeAction.PURCHASE_20_CAN) }
} }

View File

@@ -12,8 +12,10 @@ import androidx.annotation.Keep
enum class MessageStatus { enum class MessageStatus {
/** 전송 중 */ /** 전송 중 */
SENDING, SENDING,
/** 전송 완료 */ /** 전송 완료 */
SENT, SENT,
/** 전송 실패 */ /** 전송 실패 */
FAILED FAILED
} }

View File

@@ -4,7 +4,11 @@
package kr.co.vividnext.sodalive.chat.talk.room.db package kr.co.vividnext.sodalive.chat.talk.room.db
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.room.* import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
@Keep @Keep
@Dao @Dao

View File

@@ -45,15 +45,21 @@ class ImagePickerCropper(
// 13+ : 시스템 Photo Picker // 13+ : 시스템 Photo Picker
private val pickPhoto: ActivityResultLauncher<PickVisualMediaRequest> = private val pickPhoto: ActivityResultLauncher<PickVisualMediaRequest> =
caller.registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> caller.registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다.")) if (uri == null) {
else handlePickedUri(uri) onError(CancellationException("이미지 선택을 취소했습니다."))
} else {
handlePickedUri(uri)
}
} }
// 12- : SAF GetContent // 12- : SAF GetContent
private val pickContent: ActivityResultLauncher<String> = private val pickContent: ActivityResultLauncher<String> =
caller.registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> caller.registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri == null) onError(CancellationException("이미지 선택을 취소했습니다.")) if (uri == null) {
else handlePickedUri(uri) onError(CancellationException("이미지 선택을 취소했습니다."))
} else {
handlePickedUri(uri)
}
} }
// uCrop 결과 수신 // uCrop 결과 수신
@@ -105,7 +111,9 @@ class ImagePickerCropper(
val gifFile = copyUriToCacheAsGif(source) val gifFile = copyUriToCacheAsGif(source)
lastCroppedFile = gifFile lastCroppedFile = gifFile
val fileUri = FileProvider.getUriForFile( val fileUri = FileProvider.getUriForFile(
context, "${BuildConfig.APPLICATION_ID}.fileprovider", gifFile context,
"${BuildConfig.APPLICATION_ID}.fileprovider",
gifFile
) )
// 2) 바로 반환 (크롭 생략) // 2) 바로 반환 (크롭 생략)
@@ -137,13 +145,20 @@ class ImagePickerCropper(
) )
?.use { c -> ?.use { c ->
val idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME) val idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (idx >= 0 && c.moveToFirst()) c.getString(idx) else null if (idx >= 0 && c.moveToFirst()) {
c.getString(idx)
} else {
null
}
} }
} }
private fun copyUriToCacheAsGif(uri: Uri): File { private fun copyUriToCacheAsGif(uri: Uri): File {
val base = if (config.useExternalCache) context.externalCacheDir ?: context.cacheDir val base = if (config.useExternalCache) {
else context.cacheDir context.externalCacheDir ?: context.cacheDir
} else {
context.cacheDir
}
// 원본 이름 유지 시도, 실패하면 타임스탬프 // 원본 이름 유지 시도, 실패하면 타임스탬프
val name = getDisplayName(uri)?.takeIf { it.endsWith(".gif", true) } val name = getDisplayName(uri)?.takeIf { it.endsWith(".gif", true) }
@@ -201,8 +216,11 @@ class ImagePickerCropper(
} }
private fun createTempCropFile(): File { private fun createTempCropFile(): File {
val base = if (config.useExternalCache) context.externalCacheDir ?: context.cacheDir val base = if (config.useExternalCache) {
else context.cacheDir context.externalCacheDir ?: context.cacheDir
} else {
context.cacheDir
}
val ext = when (config.compressFormat) { val ext = when (config.compressFormat) {
Bitmap.CompressFormat.PNG -> "png" Bitmap.CompressFormat.PNG -> "png"

View File

@@ -89,14 +89,17 @@ object RealPathUtil {
} // MediaProvider } // MediaProvider
// DownloadsProvider // DownloadsProvider
} else if ("content".equals(uri.scheme!!, ignoreCase = true)) { } else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
// Return the remote address // Return the remote address
return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn( return if (isGooglePhotosUri(uri)) {
context, uri.lastPathSegment
uri, } else {
null, getDataColumn(
null context,
) uri,
null,
null
)
}
} else if ("file".equals(uri.scheme!!, ignoreCase = true)) { } else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
return uri.path return uri.path
} // File } // File
@@ -122,7 +125,6 @@ object RealPathUtil {
selection: String?, selection: String?,
selectionArgs: Array<String>? selectionArgs: Array<String>?
): String? { ): String? {
var cursor: Cursor? = null var cursor: Cursor? = null
val column = "_data" val column = "_data"
val projection = arrayOf(column) val projection = arrayOf(column)

View File

@@ -2,7 +2,7 @@ package kr.co.vividnext.sodalive.common
import android.content.Context import android.content.Context
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import java.text.ParseException import java.text.ParsePosition
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
@@ -10,8 +10,9 @@ import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
fun formatUtcRelativeTimeText(context: Context, utcText: String?): String { fun formatUtcRelativeTimeText(context: Context, utcText: String?): String {
val pastMillis = parseServerUtcToMillis(utcText) val parsedTime = parseServerUtcToMillis(utcText)
?: return context.getString(R.string.character_comment_time_just_now) ?: return context.getString(R.string.character_comment_time_just_now)
val pastMillis = parsedTime.millis
val nowMillis = System.currentTimeMillis() val nowMillis = System.currentTimeMillis()
var diff = nowMillis - pastMillis var diff = nowMillis - pastMillis
@@ -21,6 +22,10 @@ fun formatUtcRelativeTimeText(context: Context, utcText: String?): String {
val hour = 60 * minute val hour = 60 * minute
val day = 24 * hour val day = 24 * hour
if (parsedTime.isIsoText && diff < RECENT_ISO_TIME_GRACE_MILLIS) {
return context.getString(R.string.character_comment_time_just_now)
}
if (diff < minute) { if (diff < minute) {
return context.getString(R.string.character_comment_time_just_now) return context.getString(R.string.character_comment_time_just_now)
} }
@@ -75,19 +80,25 @@ class AndroidUtcRelativeTimeTextFormatter(context: Context) : UtcRelativeTimeTex
override fun format(utcText: String?): String = formatUtcRelativeTimeText(applicationContext, utcText) override fun format(utcText: String?): String = formatUtcRelativeTimeText(applicationContext, utcText)
} }
private fun parseServerUtcToMillis(utcText: String?): Long? { private fun parseServerUtcToMillis(utcText: String?): ParsedServerTime? {
if (utcText.isNullOrBlank()) return null if (utcText.isNullOrBlank()) return null
val value = utcText.trim() val value = utcText.trim().normalizeIsoFraction()
if (value.all { it.isDigit() }) { if (value.all { it.isDigit() }) {
return try { return try {
value.toLong() val number = value.toLong()
ParsedServerTime(
millis = if (value.length <= EPOCH_SECONDS_MAX_LENGTH) number * 1000 else number,
isIsoText = false
)
} catch (_: NumberFormatException) { } catch (_: NumberFormatException) {
null null
} }
} }
val patterns = listOf( val patterns = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
"yyyy-MM-dd'T'HH:mm:ssXXX",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss",
@@ -99,11 +110,26 @@ private fun parseServerUtcToMillis(utcText: String?): Long? {
try { try {
val dateFormat = SimpleDateFormat(pattern, Locale.US) val dateFormat = SimpleDateFormat(pattern, Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC") dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val parsed: Date? = dateFormat.parse(value) val position = ParsePosition(0)
if (parsed != null) return parsed.time val parsed: Date? = dateFormat.parse(value, position)
} catch (_: ParseException) { if (parsed != null && position.index == value.length) {
return ParsedServerTime(millis = parsed.time, isIsoText = true)
}
} catch (_: RuntimeException) {
} }
} }
return null return null
} }
private fun String.normalizeIsoFraction(): String {
return replace(Regex("""\.(\d{3})\d+"""), ".$1")
}
private data class ParsedServerTime(
val millis: Long,
val isIsoText: Boolean
)
private const val EPOCH_SECONDS_MAX_LENGTH = 10
private const val RECENT_ISO_TIME_GRACE_MILLIS = 10 * 60_000L

View File

@@ -1,10 +1,7 @@
package kr.co.vividnext.sodalive.common package kr.co.vividnext.sodalive.common
import android.content.Context
import android.os.Build
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kr.co.vividnext.sodalive.settings.language.LanguageManager
object Utils { object Utils {
fun convertDurationToString(duration: Int, showHours: Boolean = true): String { fun convertDurationToString(duration: Int, showHours: Boolean = true): String {

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.di
import android.content.Context import android.content.Context
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.orhanobut.logger.Logger
import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.agora.v2v.V2vApi import kr.co.vividnext.sodalive.agora.v2v.V2vApi
import kr.co.vividnext.sodalive.agora.v2v.V2vRepository import kr.co.vividnext.sodalive.agora.v2v.V2vRepository
@@ -181,10 +182,14 @@ import kr.co.vividnext.sodalive.user.signup.SignUpViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModel import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioViewModel import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModel import kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelApi
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail.CreatorChannelFanTalkDetailViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveViewModel
import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesViewModel
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveViewModel import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveViewModel
@@ -345,7 +350,14 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
single { ApiBuilder().build(get(), CharacterCommentApi::class.java) } single { ApiBuilder().build(get(), CharacterCommentApi::class.java) }
single { ApiBuilder().build(get(), OriginalWorkApi::class.java) } single { ApiBuilder().build(get(), OriginalWorkApi::class.java) }
single { ApiBuilder().build(get<Retrofit>(named("agoraRetrofit")), V2vApi::class.java) } single { ApiBuilder().build(get<Retrofit>(named("agoraRetrofit")), V2vApi::class.java) }
single { DmChatSocketClient(okHttpClient = get(), gson = get(), baseUrl = baseUrl) } single {
DmChatSocketClient(
okHttpClient = get(),
gson = get(),
baseUrl = baseUrl,
rawJsonLogger = { rawJson -> Logger.d("DM WebSocket raw JSON: $rawJson") }
)
}
} }
private val viewModelModule = module { private val viewModelModule = module {
@@ -452,7 +464,11 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { CreatorChannelAudioViewModel(get()) } viewModel { CreatorChannelAudioViewModel(get()) }
viewModel { CreatorChannelSeriesViewModel(get()) } viewModel { CreatorChannelSeriesViewModel(get()) }
viewModel { CreatorChannelCommunityViewModel(get(), get()) } viewModel { CreatorChannelCommunityViewModel(get(), get()) }
viewModel { CreatorChannelCommunityDetailViewModel(get(), get(), get()) }
viewModel { CreatorChannelCommunityReplyViewModel(get(), get(), get()) }
viewModel { CreatorChannelFanTalkViewModel(get(), get()) } viewModel { CreatorChannelFanTalkViewModel(get(), get()) }
viewModel { CreatorChannelFanTalkDetailViewModel(get(), get()) }
viewModel { CreatorChannelFanTalkWriteViewModel(get()) }
viewModel { CreatorChannelDonationViewModel(get(), get()) } viewModel { CreatorChannelDonationViewModel(get(), get()) }
viewModel { PushNotificationListViewModel(get()) } viewModel { PushNotificationListViewModel(get()) }
viewModel { CharacterTabViewModel(get()) } viewModel { CharacterTabViewModel(get()) }

View File

@@ -18,7 +18,7 @@ open class LiveDialog(
confirmButtonTitle: String, confirmButtonTitle: String,
confirmButtonClick: () -> Unit, confirmButtonClick: () -> Unit,
cancelButtonTitle: String = "", cancelButtonTitle: String = "",
cancelButtonClick: (() -> Unit)? = null, cancelButtonClick: (() -> Unit)? = null
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog

View File

@@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.explorer.profile.channel_donation.PostChannelDon
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse import kr.co.vividnext.sodalive.explorer.profile.follow.GetFollowerListResponse
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
@@ -73,13 +74,13 @@ interface ExplorerApi {
fun writeCheers( fun writeCheers(
@Body request: PostWriteCheersRequest, @Body request: PostWriteCheersRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>
@PUT("/explorer/profile/cheers") @PUT("/explorer/profile/cheers")
fun modifyCheers( fun modifyCheers(
@Body request: PutModifyCheersRequest, @Body request: PutModifyCheersRequest,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<Any>> ): Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>
@POST("/explorer/profile/channel-donation") @POST("/explorer/profile/channel-donation")
fun postChannelDonation( fun postChannelDonation(

View File

@@ -11,7 +11,7 @@ import kr.co.vividnext.sodalive.R
class CreatorFollowNotifyFragment( class CreatorFollowNotifyFragment(
private val onClickNotifyAll: () -> Unit, private val onClickNotifyAll: () -> Unit,
private val onClickNotifyNone: () -> Unit, private val onClickNotifyNone: () -> Unit,
private val onClickUnFollow: () -> Unit, private val onClickUnFollow: () -> Unit
) : BottomSheetDialogFragment() { ) : BottomSheetDialogFragment() {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View File

@@ -37,12 +37,12 @@ import kr.co.vividnext.sodalive.audio_content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllActivity import kr.co.vividnext.sodalive.audio_content.series.SeriesListAllActivity
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity import kr.co.vividnext.sodalive.audio_content.upload.AudioContentUploadActivity
import kr.co.vividnext.sodalive.common.image.BlurTransformation
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.image.BlurTransformation
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding
import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding
import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.dialog.MemberProfileDialog
@@ -138,7 +138,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
binding.ivMenu.setOnClickListener { binding.ivMenu.setOnClickListener {
showOptionMenu( showOptionMenu(
this, this,
binding.ivMenu, binding.ivMenu
) )
} }
@@ -468,7 +468,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
viewModel.modifyCheers( viewModel.modifyCheers(
cheersId = cheersId, cheersId = cheersId,
creatorId = userId, creatorId = userId,
cheersContent = content, cheersContent = content
) )
} }
}, },

View File

@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
data class PutModifyCheersRequest( data class PutModifyCheersRequest(
@SerializedName("cheersId") val cheersId: Long, @SerializedName("cheersId") val cheersId: Long,
@SerializedName("content") var content: String? = null, @SerializedName("content") var content: String? = null,
@SerializedName("isActive") var isActive: Boolean? = null, @SerializedName("isActive") var isActive: Boolean? = null
) )

View File

@@ -149,7 +149,6 @@ class CreatorCommunityAllViewModel(
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({}, {}) .subscribe({}, {})
) )
} }
fun registerComment( fun registerComment(
@@ -282,49 +281,48 @@ class CreatorCommunityAllViewModel(
) )
) )
} }
} }
fun updateCommunityPostFixed(postId: Long, isFixed: Boolean) { fun updateCommunityPostFixed(postId: Long, isFixed: Boolean) {
if (_isLoading.value == true) return if (_isLoading.value == true) return
_isLoading.value = true _isLoading.value = true
compositeDisposable.add( compositeDisposable.add(
repository.updateCommunityPostFixed( repository.updateCommunityPostFixed(
postId = postId, postId = postId,
isFixed = isFixed, isFixed = isFixed,
token = "Bearer ${SharedPreferenceManager.token}" token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
// 목록을 초기화하고 재조회하여 최신 고정 상태를 반영한다.
page = 1
isLast = false
getCommunityPostList()
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
)
) )
} .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success) {
// 목록을 초기화하고 재조회하여 최신 고정 상태를 반영한다.
page = 1
isLast = false
getCommunityPostList()
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
)
)
}
} }

View File

@@ -39,7 +39,8 @@ class CreatorCommunityReportDialog(
alertDialog.dismiss() alertDialog.dismiss()
confirmButtonClick(reason) confirmButtonClick(reason)
} else { } else {
Toast.makeText(activity, Toast.makeText(
activity,
SodaLiveApplicationHolder.get() SodaLiveApplicationHolder.get()
.getString(R.string.screen_audio_content_detail_report_reason_required), .getString(R.string.screen_audio_content_detail_report_reason_required),
Toast.LENGTH_LONG Toast.LENGTH_LONG

View File

@@ -16,7 +16,7 @@ class PurchaseCommunityPostDialog(
activity: Activity, activity: Activity,
layoutInflater: LayoutInflater, layoutInflater: LayoutInflater,
can: Int, can: Int,
confirmButtonClick: () -> Unit, confirmButtonClick: () -> Unit
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog

View File

@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
data class PurchasePostRequest( data class PurchasePostRequest(
@SerializedName("postId") val postId: Long, @SerializedName("postId") val postId: Long,
@SerializedName("timezone") val timezone: String, @SerializedName("timezone") val timezone: String,
@SerializedName("container") val container: String = "aos", @SerializedName("container") val container: String = "aos"
) )

View File

@@ -127,9 +127,7 @@ class CreatorCommunityCommentReplyItemViewHolder(
private val context: Context, private val context: Context,
private val creatorId: Long, private val creatorId: Long,
private val binding: ItemCommunityPostCommentReplyBinding, private val binding: ItemCommunityPostCommentReplyBinding,
private val showOptionMenu: ( private val showOptionMenu: (Context, View, Long, Long, Long, onClickModify: () -> Unit) -> Unit,
Context, View, Long, Long, Long, onClickModify: () -> Unit
) -> Unit,
private val modifyComment: (Long, String) -> Unit, private val modifyComment: (Long, String) -> Unit,
private val onClickProfile: (Long) -> Unit private val onClickProfile: (Long) -> Unit
) : CreatorCommunityCommentReplyViewHolder(binding) { ) : CreatorCommunityCommentReplyViewHolder(binding) {
@@ -139,7 +137,7 @@ class CreatorCommunityCommentReplyItemViewHolder(
onClickProfile(item.writerId) onClickProfile(item.writerId)
} }
} }
binding.ivCommentProfile.load(item.profileUrl) { binding.ivCommentProfile.load(item.profileUrl) {
crossfade(true) crossfade(true)
placeholder(R.drawable.ic_place_holder) placeholder(R.drawable.ic_place_holder)
@@ -178,5 +176,4 @@ class CreatorCommunityCommentReplyItemViewHolder(
binding.ivMenu.visibility = View.GONE binding.ivMenu.visibility = View.GONE
} }
} }
} }

View File

@@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify package kr.co.vividnext.sodalive.explorer.profile.creator_community.modify
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -76,7 +75,8 @@ class CreatorCommunityModifyActivity : BaseActivity<ActivityCreatorCommunityModi
context = this, context = this,
isEnabledFreeStyleCrop = true, isEnabledFreeStyleCrop = true,
config = ImagePickerCropper.Config( config = ImagePickerCropper.Config(
aspectX = 1f, aspectY = 1f, aspectX = 1f,
aspectY = 1f,
compressFormat = Bitmap.CompressFormat.JPEG, compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90 compressQuality = 90
), ),

View File

@@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName
enum class DonationRankingPeriod { enum class DonationRankingPeriod {
@SerializedName("WEEKLY") @SerializedName("WEEKLY")
WEEKLY, WEEKLY,
@SerializedName("CUMULATIVE") @SerializedName("CUMULATIVE")
CUMULATIVE CUMULATIVE
} }

View File

@@ -19,5 +19,5 @@ data class GetDonationAllResponse(
@SerializedName("donationRankingPeriod") @SerializedName("donationRankingPeriod")
val donationRankingPeriod: DonationRankingPeriod? = null, val donationRankingPeriod: DonationRankingPeriod? = null,
@SerializedName("userDonationRanking") @SerializedName("userDonationRanking")
val userDonationRanking: List<UserDonationRankingResponse>, val userDonationRanking: List<UserDonationRankingResponse>
) )

View File

@@ -103,7 +103,7 @@ class UserProfileFantalkAllViewActivity : BaseActivity<ActivityUserProfileFantal
viewModel.modifyCheers( viewModel.modifyCheers(
cheersId = cheersId, cheersId = cheersId,
creatorId = userId, creatorId = userId,
cheersContent = content, cheersContent = content
) )
} }
}, },

View File

@@ -13,7 +13,6 @@ import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivityUserFollowerListBinding import kr.co.vividnext.sodalive.databinding.ActivityUserFollowerListBinding
import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.dialog.MemberProfileDialog
import kr.co.vividnext.sodalive.explorer.profile.CreatorFollowNotifyFragment import kr.co.vividnext.sodalive.explorer.profile.CreatorFollowNotifyFragment
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.extensions.moneyFormat
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject

View File

@@ -1,8 +1,6 @@
package kr.co.vividnext.sodalive.following package kr.co.vividnext.sodalive.following
import kr.co.vividnext.sodalive.live.recommend.LiveRecommendApi import kr.co.vividnext.sodalive.live.recommend.LiveRecommendApi
import kr.co.vividnext.sodalive.user.CreatorFollowRequestRequest
import kr.co.vividnext.sodalive.user.UserApi
class FollowingCreatorRepository(private val api: LiveRecommendApi) { class FollowingCreatorRepository(private val api: LiveRecommendApi) {
fun getFollowedCreatorAllList( fun getFollowedCreatorAllList(

View File

@@ -1,18 +1,26 @@
package kr.co.vividnext.sodalive.home package kr.co.vividnext.sodalive.home
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
/** /**
* 콘텐츠 랭킹 정렬 기준 * 콘텐츠 랭킹 정렬 기준
*/ */
enum class ContentRankingSortType { @Keep
enum class ContentRankingSortType(val queryValue: String) {
// 매출 // 매출
REVENUE, @SerializedName("REVENUE")
REVENUE("REVENUE"),
// 판매량 // 판매량
SALES_COUNT, @SerializedName("SALES_COUNT")
SALES_COUNT("SALES_COUNT"),
// 댓글 수 // 댓글 수
COMMENT_COUNT, @SerializedName("COMMENT_COUNT")
COMMENT_COUNT("COMMENT_COUNT"),
// 좋아요 수 // 좋아요 수
LIKE_COUNT @SerializedName("LIKE_COUNT")
LIKE_COUNT("LIKE_COUNT")
} }

View File

@@ -66,7 +66,6 @@ class CreatorRankingAdapter(
binding.tvFollow.text = context.getString(R.string.screen_home_following) binding.tvFollow.text = context.getString(R.string.screen_home_following)
binding.tvFollow.setBackgroundResource(R.drawable.bg_round_corner_999_455a64) binding.tvFollow.setBackgroundResource(R.drawable.bg_round_corner_999_455a64)
binding.tvFollow.setTextColor(context.getColor(R.color.white)) binding.tvFollow.setTextColor(context.getColor(R.color.white))
} else { } else {
binding.tvFollow.text = context.getString(R.string.screen_home_follow) binding.tvFollow.text = context.getString(R.string.screen_home_follow)
binding.tvFollow.setBackgroundResource(R.drawable.bg_round_corner_999_white) binding.tvFollow.setBackgroundResource(R.drawable.bg_round_corner_999_white)

View File

@@ -10,8 +10,8 @@ import kr.co.vividnext.sodalive.databinding.ItemDayOfWeekBinding
import java.util.Calendar import java.util.Calendar
class DayOfWeekAdapter( class DayOfWeekAdapter(
private val context: Context, context: Context,
private val onClickItem: (SeriesPublishedDaysOfWeek) -> Unit, private val onClickItem: (SeriesPublishedDaysOfWeek) -> Unit
) : RecyclerView.Adapter<DayOfWeekAdapter.ViewHolder>() { ) : RecyclerView.Adapter<DayOfWeekAdapter.ViewHolder>() {
inner class ViewHolder( inner class ViewHolder(
@@ -40,7 +40,7 @@ class DayOfWeekAdapter(
DayOfWeek(dayOfWeekStr = context.getString(R.string.day_fri_short), dayOfWeek = SeriesPublishedDaysOfWeek.FRI), DayOfWeek(dayOfWeekStr = context.getString(R.string.day_fri_short), dayOfWeek = SeriesPublishedDaysOfWeek.FRI),
DayOfWeek(dayOfWeekStr = context.getString(R.string.day_sat_short), dayOfWeek = SeriesPublishedDaysOfWeek.SAT), DayOfWeek(dayOfWeekStr = context.getString(R.string.day_sat_short), dayOfWeek = SeriesPublishedDaysOfWeek.SAT),
DayOfWeek(dayOfWeekStr = context.getString(R.string.day_sun_short), dayOfWeek = SeriesPublishedDaysOfWeek.SUN), DayOfWeek(dayOfWeekStr = context.getString(R.string.day_sun_short), dayOfWeek = SeriesPublishedDaysOfWeek.SUN),
DayOfWeek(dayOfWeekStr = context.getString(R.string.day_random), dayOfWeek = SeriesPublishedDaysOfWeek.RANDOM), DayOfWeek(dayOfWeekStr = context.getString(R.string.day_random), dayOfWeek = SeriesPublishedDaysOfWeek.RANDOM)
) )
// 요일 숫자에 맞춰 배열 // 요일 숫자에 맞춰 배열

View File

@@ -23,7 +23,7 @@ interface HomeApi {
@GET("/api/home/day-of-week-series") @GET("/api/home/day-of-week-series")
fun getDayOfWeekSeriesList( fun getDayOfWeekSeriesList(
@Query("dayOfWeek") dayOfWeek: SeriesPublishedDaysOfWeek, @Query("dayOfWeek") dayOfWeek: String,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>> ): Single<ApiResponse<List<GetSeriesListResponse.SeriesListItem>>>
@@ -34,7 +34,7 @@ interface HomeApi {
@GET("/api/home/content-ranking") @GET("/api/home/content-ranking")
fun getContentRankingBySort( fun getContentRankingBySort(
@Query("sort") sort: ContentRankingSortType, @Query("sort") sort: String,
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetAudioContentRankingItem>>> ): Single<ApiResponse<List<GetAudioContentRankingItem>>>
} }

View File

@@ -13,7 +13,7 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
class HomeContentAdapter( class HomeContentAdapter(
private val itemSquareSizePx: Int? = null, private val itemSquareSizePx: Int? = null,
private val onClickItem: (Long) -> Unit, private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<HomeContentAdapter.ViewHolder>() { ) : RecyclerView.Adapter<HomeContentAdapter.ViewHolder>() {
private val items = mutableListOf<AudioContentMainItem>() private val items = mutableListOf<AudioContentMainItem>()

View File

@@ -14,9 +14,10 @@ class HomeRepository(private val api: HomeApi) {
) )
fun getDayOfWeekSeriesList( fun getDayOfWeekSeriesList(
dayOfWeek: SeriesPublishedDaysOfWeek, token: String dayOfWeek: SeriesPublishedDaysOfWeek,
token: String
) = api.getDayOfWeekSeriesList( ) = api.getDayOfWeekSeriesList(
dayOfWeek = dayOfWeek, dayOfWeek = dayOfWeek.queryValue,
authHeader = token authHeader = token
) )
@@ -28,7 +29,7 @@ class HomeRepository(private val api: HomeApi) {
sortType: ContentRankingSortType, sortType: ContentRankingSortType,
token: String token: String
) = api.getContentRankingBySort( ) = api.getContentRankingBySort(
sort = sortType, sort = sortType.queryValue,
authHeader = token authHeader = token
) )
} }

View File

@@ -1,5 +1,31 @@
package kr.co.vividnext.sodalive.home package kr.co.vividnext.sodalive.home
enum class SeriesPublishedDaysOfWeek { import androidx.annotation.Keep
SUN, MON, TUE, WED, THU, FRI, SAT, RANDOM import com.google.gson.annotations.SerializedName
@Keep
enum class SeriesPublishedDaysOfWeek(val queryValue: String) {
@SerializedName("SUN")
SUN("SUN"),
@SerializedName("MON")
MON("MON"),
@SerializedName("TUE")
TUE("TUE"),
@SerializedName("WED")
WED("WED"),
@SerializedName("THU")
THU("THU"),
@SerializedName("FRI")
FRI("FRI"),
@SerializedName("SAT")
SAT("SAT"),
@SerializedName("RANDOM")
RANDOM("RANDOM")
} }

View File

@@ -149,7 +149,6 @@ class LiveViewModel(
fun getSummary() { fun getSummary() {
if (!_isLoading.value!!) { if (!_isLoading.value!!) {
if (SharedPreferenceManager.token.isNotBlank()) { if (SharedPreferenceManager.token.isNotBlank()) {
getFollowedChannelList() getFollowedChannelList()
getLatestPostListFromCreatorsYouFollow() getLatestPostListFromCreatorsYouFollow()

View File

@@ -1,38 +0,0 @@
package kr.co.vividnext.sodalive.live.event_banner
import android.content.Context
import android.widget.ImageView
import coil.load
import com.zhpan.bannerview.BaseBannerAdapter
import com.zhpan.bannerview.BaseViewHolder
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.settings.event.EventItem
class EventBannerAdapter(
private val context: Context,
private val itemClick: (EventItem) -> Unit
) : BaseBannerAdapter<EventItem>() {
override fun bindData(
holder: BaseViewHolder<EventItem>,
data: EventItem,
position: Int,
pageSize: Int
) {
val ivThumbnail = holder.findViewById<ImageView>(R.id.iv_thumbnail)
ivThumbnail.load(data.thumbnailImageUrl) {
crossfade(true)
val layoutParams = ivThumbnail.layoutParams
val screenWidth = context.resources.displayMetrics.widthPixels
layoutParams.width = screenWidth
layoutParams.height = (screenWidth * 300) / 1000
ivThumbnail.layoutParams = layoutParams
}
ivThumbnail.setOnClickListener { itemClick(data) }
}
override fun getLayoutId(viewType: Int): Int {
return R.layout.item_event_slider
}
}

View File

@@ -14,7 +14,7 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.GetRoomListResponse import kr.co.vividnext.sodalive.live.GetRoomListResponse
class LiveNowAdapter( class LiveNowAdapter(
private val onClick: (GetRoomListResponse) -> Unit, private val onClick: (GetRoomListResponse) -> Unit
) : RecyclerView.Adapter<LiveNowAdapter.ViewHolder>() { ) : RecyclerView.Adapter<LiveNowAdapter.ViewHolder>() {
var items = mutableListOf<GetRoomListResponse>() var items = mutableListOf<GetRoomListResponse>()

View File

@@ -14,7 +14,7 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
class LiveRecommendChannelAdapter( class LiveRecommendChannelAdapter(
private val onClick: (Long) -> Unit, private val onClick: (Long) -> Unit,
private val onClickMore: () -> Unit, private val onClickMore: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val items = mutableListOf<GetRecommendChannelResponse>() private val items = mutableListOf<GetRecommendChannelResponse>()

View File

@@ -15,5 +15,5 @@ data class GetLiveReservationResponse(
@SerializedName("price") val price: Int, @SerializedName("price") val price: Int,
@SerializedName("masterNickname") val masterNickname: String, @SerializedName("masterNickname") val masterNickname: String,
@SerializedName("beginDateTimeUtc") val beginDateTimeUtc: String, @SerializedName("beginDateTimeUtc") val beginDateTimeUtc: String,
@SerializedName("cancelable") val cancelable: Boolean, @SerializedName("cancelable") val cancelable: Boolean
) : Parcelable ) : Parcelable

View File

@@ -126,9 +126,7 @@ class LiveReservationCancelActivity : BaseActivity<ActivityLiveReservationCancel
binding.tvCancel.setOnClickListener { binding.tvCancel.setOnClickListener {
viewModel.cancelReservation { viewModel.cancelReservation {
if (binding.tvPrice.text == SodaLiveApplicationHolder.get() if (binding.tvPrice.text == SodaLiveApplicationHolder.get().getString(R.string.live_reservation_free)) {
.getString(R.string.live_reservation_free)
) {
binding.tvCancelComplete.visibility = View.GONE binding.tvCancelComplete.visibility = View.GONE
} else { } else {
binding.tvCancelComplete.visibility = View.VISIBLE binding.tvCancelComplete.visibility = View.VISIBLE

View File

@@ -6,10 +6,10 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemLiveReservationStatusBinding import kr.co.vividnext.sodalive.databinding.ItemLiveReservationStatusBinding
import kr.co.vividnext.sodalive.extensions.convertDateFormat import kr.co.vividnext.sodalive.extensions.convertDateFormat
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.settings.language.LanguageManager import kr.co.vividnext.sodalive.settings.language.LanguageManager
import kr.co.vividnext.sodalive.settings.language.LocaleHelper import kr.co.vividnext.sodalive.settings.language.LocaleHelper
import java.util.Locale import java.util.Locale
@@ -22,7 +22,7 @@ class LiveReservationStatusAdapter(
val items = mutableListOf<GetLiveReservationResponse>() val items = mutableListOf<GetLiveReservationResponse>()
inner class ViewHolder( inner class ViewHolder(
private val binding: ItemLiveReservationStatusBinding, private val binding: ItemLiveReservationStatusBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetLiveReservationResponse) { fun bind(item: GetLiveReservationResponse) {

View File

@@ -90,7 +90,6 @@ class LiveReservationStatusViewModel(
_isLoading.value = false _isLoading.value = false
}, },
{ {
_isLoading.value = false _isLoading.value = false
it.message?.let { message -> Logger.e(message) } it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue( _toastLiveData.postValue(

View File

@@ -3,7 +3,12 @@ package kr.co.vividnext.sodalive.live.room
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
enum class GenderRestriction { enum class GenderRestriction {
@SerializedName("ALL") ALL, @SerializedName("ALL")
@SerializedName("MALE_ONLY") MALE_ONLY, ALL,
@SerializedName("FEMALE_ONLY") FEMALE_ONLY
@SerializedName("MALE_ONLY")
MALE_ONLY,
@SerializedName("FEMALE_ONLY")
FEMALE_ONLY
} }

View File

@@ -481,8 +481,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
context = this, context = this,
excludeGif = true, excludeGif = true,
config = ImagePickerCropper.Config( config = ImagePickerCropper.Config(
aspectX = 2f, aspectY = 3.8f, aspectX = 2f,
maxWidth = 1080, maxHeight = 2052, aspectY = 3.8f,
maxWidth = 1080,
maxHeight = 2052,
compressFormat = Bitmap.CompressFormat.JPEG, compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90 compressQuality = 90
), ),
@@ -640,7 +642,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
userId, userId,
nickname, nickname,
isBlock, isBlock,
view, view
) )
} }
) )
@@ -1208,7 +1210,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
) )
roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl) roomInfoEditDialog.setCoverImageUrl(response.coverImageUrl)
roomInfoEditDialog.setMenuPreset(it) roomInfoEditDialog.setMenuPreset(it)
roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageFile, isActivateMenu, menuId, menu, isAdult, isEntryMessageEnabled -> roomInfoEditDialog.setConfirmAction { newTitle, newContent, newCoverImageFile,
isActivateMenu, menuId, menu, isAdult, isEntryMessageEnabled ->
if (isEntryMessageEnabled != null) { if (isEntryMessageEnabled != null) {
this.isEntryMessageEnabled = isEntryMessageEnabled this.isEntryMessageEnabled = isEntryMessageEnabled
} }
@@ -1337,7 +1340,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
hasInvokedJoinChannel = true hasInvokedJoinChannel = true
joinChannel(response) joinChannel(response)
} else { } else {
Logger.e("joinChannel - skip (rtcConnected=$rtcConnected, rtmLoggedIn=$rtmLoggedIn, hasInvokedJoinChannel=$hasInvokedJoinChannel)") Logger.e(
"joinChannel - skip (rtcConnected=$rtcConnected, " +
"rtmLoggedIn=$rtmLoggedIn, hasInvokedJoinChannel=$hasInvokedJoinChannel)"
)
} }
} }
@@ -3027,8 +3033,10 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val elapsed = System.currentTimeMillis() - pressStartTime val elapsed = System.currentTimeMillis() - pressStartTime
// 경과 시간 기준으로 물 채우기(progress) 계산 (0..100) // 경과 시간 기준으로 물 채우기(progress) 계산 (0..100)
val progressFraction = (elapsed.coerceAtMost(longPressDurationMs) val progressFraction = (
.toFloat() / longPressDurationMs.toFloat()) elapsed.coerceAtMost(longPressDurationMs)
.toFloat() / longPressDurationMs.toFloat()
)
val progress = (progressFraction * 100f).toInt() val progress = (progressFraction * 100f).toInt()
longPressCenterHeart?.let { heartView -> longPressCenterHeart?.let { heartView ->
try { try {
@@ -3131,8 +3139,11 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
heart.setFrontWaveColor("#ff959a".toColorInt()) heart.setFrontWaveColor("#ff959a".toColorInt())
heart.setBehindWaveColor("#ff959a".toColorInt()) heart.setBehindWaveColor("#ff959a".toColorInt())
val elapsed = System.currentTimeMillis() - pressStartTime val elapsed = System.currentTimeMillis() - pressStartTime
val progress = ((elapsed.coerceAtMost(longPressDurationMs) val progress = (
.toFloat() / longPressDurationMs.toFloat()) * 100f).toInt() (
elapsed.coerceAtMost(longPressDurationMs).toFloat() / longPressDurationMs.toFloat()
) * 100f
).toInt()
try { try {
heart.progress = progress heart.progress = progress
} catch (_: Throwable) { } catch (_: Throwable) {
@@ -3589,14 +3600,20 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val s = 0.9f val s = 0.9f
moveTo(0f, -0.45f * s) moveTo(0f, -0.45f * s)
cubicTo( cubicTo(
0.62f * s, -1.02f * s, 0.62f * s,
1.22f * s, -0.04f * s, -1.02f * s,
0f, 0.65f * s 1.22f * s,
-0.04f * s,
0f,
0.65f * s
) )
cubicTo( cubicTo(
-1.22f * s, -0.04f * s, -1.22f * s,
-0.62f * s, -1.02f * s, -0.04f * s,
0f, -0.45f * s -0.62f * s,
-1.02f * s,
0f,
-0.45f * s
) )
close() close()
} }
@@ -3784,14 +3801,20 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val s = 0.9f val s = 0.9f
moveTo(0f, -0.45f * s) moveTo(0f, -0.45f * s)
cubicTo( cubicTo(
0.62f * s, -1.02f * s, 0.62f * s,
1.22f * s, -0.04f * s, -1.02f * s,
0f, 0.65f * s 1.22f * s,
-0.04f * s,
0f,
0.65f * s
) )
cubicTo( cubicTo(
-1.22f * s, -0.04f * s, -1.22f * s,
-0.62f * s, -1.02f * s, -0.04f * s,
0f, -0.45f * s -0.62f * s,
-1.02f * s,
0f,
-0.45f * s
) )
close() close()
} }
@@ -3899,14 +3922,20 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val s = 0.9f val s = 0.9f
moveTo(0f, -0.45f * s) moveTo(0f, -0.45f * s)
cubicTo( cubicTo(
0.62f * s, -1.02f * s, 0.62f * s,
1.22f * s, -0.04f * s, -1.02f * s,
0f, 0.65f * s 1.22f * s,
-0.04f * s,
0f,
0.65f * s
) )
cubicTo( cubicTo(
-1.22f * s, -0.04f * s, -1.22f * s,
-0.62f * s, -1.02f * s, -0.04f * s,
0f, -0.45f * s -0.62f * s,
-1.02f * s,
0f,
-0.45f * s
) )
close() close()
} }
@@ -3946,7 +3975,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
if (rotateEnabled) 300f * Math.random().toFloat() else 0f // deg/s if (rotateEnabled) 300f * Math.random().toFloat() else 0f // deg/s
val rotation0 = (-180f + 360f * Math.random().toFloat()) val rotation0 = (-180f + 360f * Math.random().toFloat())
val fadeStartRatio = 0.80f + 0.15f * Math.random().toFloat() // 0.80..0.95 val fadeStartRatio = 0.80f + 0.15f * Math.random().toFloat() // 0.80..0.95
val fadeStartY = h * fadeStartRatio val fadeStartY = h * fadeStartRatio
@@ -3991,7 +4019,9 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
val fadeAlpha = if (y >= d.fadeStartY) { val fadeAlpha = if (y >= d.fadeStartY) {
val denom = (h - d.fadeStartY).coerceAtLeast(1f) val denom = (h - d.fadeStartY).coerceAtLeast(1f)
(1f - (y - d.fadeStartY) / denom).coerceIn(0f, 1f) (1f - (y - d.fadeStartY) / denom).coerceIn(0f, 1f)
} else 1f } else {
1f
}
val alpha = fadeAlpha.coerceIn(0f, 1f) val alpha = fadeAlpha.coerceIn(0f, 1f)
paint.alpha = (alpha * 255).toInt().coerceIn(0, 255) paint.alpha = (alpha * 255).toInt().coerceIn(0, 255)
@@ -4139,7 +4169,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
return false return false
} }
}) })
.into(binding.ivSignature) .into(binding.ivSignature)
} }
@@ -4205,7 +4234,6 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
} }
} else { } else {
binding.flRoulette.visibility = View.GONE binding.flRoulette.visibility = View.GONE
} }
@@ -4221,7 +4249,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
type = LiveRoomChatRawMessageType.ROULETTE_DONATION, type = LiveRoomChatRawMessageType.ROULETTE_DONATION,
message = randomItem, message = randomItem,
can = can, can = can,
donationMessage = "", donationMessage = ""
) )
) )
@@ -4269,7 +4297,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
.subscribe( .subscribe(
{ response -> { response ->
Logger.e("성공: $response") Logger.e("성공: $response")
}, { error -> },
{ error ->
Logger.e("실패: ${error.message}") Logger.e("실패: ${error.message}")
} }
) )

View File

@@ -2,7 +2,6 @@ package kr.co.vividnext.sodalive.live.room
import android.app.Activity import android.app.Activity
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -10,12 +9,13 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.drawable.toDrawable
import kr.co.vividnext.sodalive.databinding.DialogLiveRoomBinding import kr.co.vividnext.sodalive.databinding.DialogLiveRoomBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
class LiveRoomDialog( class LiveRoomDialog(
activity: Activity, activity: Activity,
layoutInflater: LayoutInflater, layoutInflater: LayoutInflater
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
@@ -27,7 +27,7 @@ class LiveRoomDialog(
dialogBuilder.setView(dialogView.root) dialogBuilder.setView(dialogView.root)
alertDialog = dialogBuilder.create() alertDialog = dialogBuilder.create()
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
} }
fun show(width: Int) { fun show(width: Int) {

View File

@@ -2,10 +2,10 @@ package kr.co.vividnext.sodalive.live.room
import android.app.Activity import android.app.Activity
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.drawable.toDrawable
import coil.load import coil.load
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
@@ -17,7 +17,7 @@ class LiveRoomNoChattingDialog(
layoutInflater: LayoutInflater, layoutInflater: LayoutInflater,
nickname: String, nickname: String,
profileUrl: String, profileUrl: String,
confirmButtonClick: () -> Unit, confirmButtonClick: () -> Unit
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
@@ -30,7 +30,7 @@ class LiveRoomNoChattingDialog(
alertDialog = dialogBuilder.create() alertDialog = dialogBuilder.create()
alertDialog.setCancelable(false) alertDialog.setCancelable(false)
alertDialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
dialogView.tvCancel.setOnClickListener { dialogView.tvCancel.setOnClickListener {
alertDialog.dismiss() alertDialog.dismiss()

View File

@@ -3,10 +3,21 @@ package kr.co.vividnext.sodalive.live.room
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
enum class LiveRoomRequestType { enum class LiveRoomRequestType {
@SerializedName("INVITE_SPEAKER") INVITE_SPEAKER, @SerializedName("INVITE_SPEAKER")
@SerializedName("CHANGE_LISTENER") CHANGE_LISTENER, INVITE_SPEAKER,
@SerializedName("KICK_OUT") KICK_OUT,
@SerializedName("SET_MANAGER") SET_MANAGER, @SerializedName("CHANGE_LISTENER")
@SerializedName("RELEASE_MANAGER") RELEASE_MANAGER, CHANGE_LISTENER,
@SerializedName("NO_CHATTING") NO_CHATTING,
@SerializedName("KICK_OUT")
KICK_OUT,
@SerializedName("SET_MANAGER")
SET_MANAGER,
@SerializedName("RELEASE_MANAGER")
RELEASE_MANAGER,
@SerializedName("NO_CHATTING")
NO_CHATTING
} }

View File

@@ -3,6 +3,9 @@ package kr.co.vividnext.sodalive.live.room
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
enum class LiveRoomStatus { enum class LiveRoomStatus {
@SerializedName("NOW") NOW, @SerializedName("NOW")
@SerializedName("RESERVATION") RESERVATION NOW,
@SerializedName("RESERVATION")
RESERVATION
} }

View File

@@ -3,6 +3,9 @@ package kr.co.vividnext.sodalive.live.room
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
enum class LiveRoomType { enum class LiveRoomType {
@SerializedName("OPEN") OPEN, @SerializedName("OPEN")
@SerializedName("PRIVATE") PRIVATE, OPEN,
@SerializedName("PRIVATE")
PRIVATE
} }

View File

@@ -7,5 +7,5 @@ import java.util.TimeZone
@Keep @Keep
data class StartLiveRequest( data class StartLiveRequest(
@SerializedName("roomId") val roomId: Long, @SerializedName("roomId") val roomId: Long,
@SerializedName("timezone") val timezone: String = TimeZone.getDefault().id, @SerializedName("timezone") val timezone: String = TimeZone.getDefault().id
) )

View File

@@ -61,7 +61,7 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
"%d-%02d-%02d", "%d-%02d-%02d",
year, year,
monthOfYear + 1, monthOfYear + 1,
dayOfMonth, dayOfMonth
) )
viewModel.setReservationDate( viewModel.setReservationDate(
String.format( String.format(
@@ -136,8 +136,10 @@ class LiveRoomCreateActivity : BaseActivity<ActivityLiveRoomCreateBinding>(
context = this, context = this,
excludeGif = true, excludeGif = true,
config = ImagePickerCropper.Config( config = ImagePickerCropper.Config(
aspectX = 2f, aspectY = 3.8f, aspectX = 2f,
maxWidth = 1080, maxHeight = 2052, aspectY = 3.8f,
maxWidth = 1080,
maxHeight = 2052,
compressFormat = Bitmap.CompressFormat.JPEG, compressFormat = Bitmap.CompressFormat.JPEG,
compressQuality = 90 compressQuality = 90
), ),

View File

@@ -1,12 +1,12 @@
package kr.co.vividnext.sodalive.live.room.create package kr.co.vividnext.sodalive.live.room.create
import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.UiText import kr.co.vividnext.sodalive.common.UiText
@@ -16,7 +16,6 @@ import kr.co.vividnext.sodalive.live.LiveRepository
import kr.co.vividnext.sodalive.live.room.GenderRestriction import kr.co.vividnext.sodalive.live.room.GenderRestriction
import kr.co.vividnext.sodalive.live.room.LiveRoomType import kr.co.vividnext.sodalive.live.room.LiveRoomType
import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse import kr.co.vividnext.sodalive.live.room.menu.GetMenuPresetResponse
import kr.co.vividnext.sodalive.R
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody

View File

@@ -20,7 +20,7 @@ class LiveCancelDialog(
confirmButtonTitle: String, confirmButtonTitle: String,
confirmButtonClick: (String) -> Unit, confirmButtonClick: (String) -> Unit,
cancelButtonTitle: String = "", cancelButtonTitle: String = "",
cancelButtonClick: (() -> Unit)? = null, cancelButtonClick: (() -> Unit)? = null
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
private val dialogView = DialogLiveInputBinding.inflate(layoutInflater) private val dialogView = DialogLiveInputBinding.inflate(layoutInflater)

View File

@@ -21,7 +21,7 @@ class LivePaymentDialog(
confirmButtonTitle: String, confirmButtonTitle: String,
confirmButtonClick: () -> Unit, confirmButtonClick: () -> Unit,
cancelButtonTitle: String = "", cancelButtonTitle: String = "",
cancelButtonClick: (() -> Unit)? = null, cancelButtonClick: (() -> Unit)? = null
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
private val dialogView = DialogLivePaymentBinding.inflate(layoutInflater) private val dialogView = DialogLivePaymentBinding.inflate(layoutInflater)

View File

@@ -20,7 +20,7 @@ class LiveRoomPasswordDialog(
activity: Activity, activity: Activity,
layoutInflater: LayoutInflater, layoutInflater: LayoutInflater,
can: Int, can: Int,
confirmButtonClick: (String) -> Unit, confirmButtonClick: (String) -> Unit
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog

View File

@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
data class LiveRoomLikeHeartRequest( data class LiveRoomLikeHeartRequest(
@SerializedName("roomId") val roomId: Long, @SerializedName("roomId") val roomId: Long,
@SerializedName("container") val container: String, @SerializedName("container") val container: String,
@SerializedName("heartCount") val heartCount: Int = 1, @SerializedName("heartCount") val heartCount: Int = 1
) )

View File

@@ -15,7 +15,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.ActivityMenuConfigBinding import kr.co.vividnext.sodalive.databinding.ActivityMenuConfigBinding
import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateViewModel

View File

@@ -195,19 +195,19 @@ abstract class LiveRoomProfileViewHolder(binding: ViewBinding) :
} }
class LiveRoomProfileSpeakerTitleViewHolder( class LiveRoomProfileSpeakerTitleViewHolder(
private val binding: ItemLiveRoomProfileHeaderBinding, private val binding: ItemLiveRoomProfileHeaderBinding
) : LiveRoomProfileViewHolder(binding) { ) : LiveRoomProfileViewHolder(binding) {
override fun bind(item: LiveRoomProfileItem) = item.bind(binding) override fun bind(item: LiveRoomProfileItem) = item.bind(binding)
} }
class LiveRoomProfileListenerTitleViewHolder( class LiveRoomProfileListenerTitleViewHolder(
private val binding: ItemLiveRoomProfileHeaderBinding, private val binding: ItemLiveRoomProfileHeaderBinding
) : LiveRoomProfileViewHolder(binding) { ) : LiveRoomProfileViewHolder(binding) {
override fun bind(item: LiveRoomProfileItem) = item.bind(binding) override fun bind(item: LiveRoomProfileItem) = item.bind(binding)
} }
class LiveRoomProfileManagerTitleViewHolder( class LiveRoomProfileManagerTitleViewHolder(
private val binding: ItemLiveRoomProfileHeaderBinding, private val binding: ItemLiveRoomProfileHeaderBinding
) : LiveRoomProfileViewHolder(binding) { ) : LiveRoomProfileViewHolder(binding) {
override fun bind(item: LiveRoomProfileItem) = item.bind(binding) override fun bind(item: LiveRoomProfileItem) = item.bind(binding)
} }

View File

@@ -28,7 +28,7 @@ class LiveRoomUserProfileDialog(
private val onClickChangeListener: (Long) -> Unit, private val onClickChangeListener: (Long) -> Unit,
private val onClickKickOut: (Long) -> Unit, private val onClickKickOut: (Long) -> Unit,
private val onClickNoChatting: (Long, String, String) -> Unit, private val onClickNoChatting: (Long, String, String) -> Unit,
private val onClickPopupMenu: (Long, String, Boolean, View) -> Unit, private val onClickPopupMenu: (Long, String, Boolean, View) -> Unit
) { ) {
private val alertDialog: AlertDialog private val alertDialog: AlertDialog
private val dialogView = DialogLiveRoomUserProfileBinding.inflate(layoutInflater) private val dialogView = DialogLiveRoomUserProfileBinding.inflate(layoutInflater)

View File

@@ -24,8 +24,6 @@ class LiveTagFragment(
private val selectedTags: Set<String>, private val selectedTags: Set<String>,
private val onItemClick: (String, Boolean) -> Boolean private val onItemClick: (String, Boolean) -> Boolean
) : BottomSheetDialogFragment() { ) : BottomSheetDialogFragment() {
private val viewModel: LiveTagViewModel by inject() private val viewModel: LiveTagViewModel by inject()
private lateinit var adapter: LiveTagAdapter private lateinit var adapter: LiveTagAdapter

View File

@@ -190,12 +190,12 @@ class LiveRoomEditViewModel(
beginDate = date.convertDateFormat( beginDate = date.convertDateFormat(
from = "yyyy.MM.dd", from = "yyyy.MM.dd",
to = "yyyy-MM-dd", to = "yyyy-MM-dd"
) )
beginTime = time.convertDateFormat( beginTime = time.convertDateFormat(
from = "a hh:mm", from = "a hh:mm",
to = "HH:mm", to = "HH:mm"
) )
beginDateTimeStr = "$beginDate $beginTime" beginDateTimeStr = "$beginDate $beginTime"

View File

@@ -132,9 +132,12 @@ class RouletteView @JvmOverloads constructor(
val targetAngle = 0 - (getAngleForOption(option) + 360 * 10) val targetAngle = 0 - (getAngleForOption(option) + 360 * 10)
val rotateAnimation = RotateAnimation( val rotateAnimation = RotateAnimation(
0f, targetAngle, 0f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f, targetAngle,
RotateAnimation.RELATIVE_TO_SELF, 0.5f RotateAnimation.RELATIVE_TO_SELF,
0.5f,
RotateAnimation.RELATIVE_TO_SELF,
0.5f
) )
rotateAnimation.duration = 2000 rotateAnimation.duration = 2000
rotateAnimation.fillAfter = true rotateAnimation.fillAfter = true

Some files were not shown because too many files have changed in this diff Show More