feat(mypage): 하단 Yandex 인라인 배너를 추가한다

마이페이지 스크롤 콘텐츠 최하단에서 Yandex 인라인 배너를 노출해 광고 영역을 확보한다.
debug/release ad unit id를 분리해 배포 전환 지점을 한곳에 고정한다.
This commit is contained in:
2026-04-21 13:23:06 +09:00
parent af0d788796
commit 30b3dcdce6
4 changed files with 124 additions and 0 deletions

View File

@@ -73,6 +73,8 @@ android {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// release용 ad unit id는 배포 전 실제 값으로 교체한다.
buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140295-1"'
buildConfigField 'String', 'BASE_URL', '"https://api.sodalive.net"'
buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"'
buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"'
@@ -103,6 +105,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationIdSuffix '.debug'
buildConfigField 'String', 'YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID', '"R-M-19140297-1"'
buildConfigField 'String', 'BASE_URL', '"https://test-api.sodalive.net"'
buildConfigField 'String', 'AGORA_API_BASE_URL', '"https://api.agora.io/api/speech-to-speech-translation/v2/"'
buildConfigField 'String', 'AGORA_CUSTOMER_ID', '"de5dd9ea151f4a43ba1ad8411817b169"'

View File

@@ -7,6 +7,8 @@ import android.os.Bundle
import android.view.View
import android.webkit.URLUtil
import android.widget.Toast
import com.yandex.mobile.ads.banner.BannerAdSize
import com.yandex.mobile.ads.common.AdRequest
import androidx.core.net.toUri
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.GridLayoutManager
@@ -15,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import com.google.gson.Gson
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.box.AudioContentBoxActivity
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
@@ -51,6 +54,7 @@ import kr.co.vividnext.sodalive.settings.notice.NoticeDetailActivity
import kr.co.vividnext.sodalive.settings.notification.MemberRole
import kr.co.vividnext.sodalive.splash.SplashActivity
import org.koin.android.ext.android.inject
import kotlin.math.roundToInt
@UnstableApi
class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflate) {
@@ -73,6 +77,29 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
bindData()
setupRecentContentSection()
setupLatestNotice()
setupBottomInlineBanner()
}
private fun setupBottomInlineBanner() {
binding.yandexInlineBannerView.post {
val density = resources.displayMetrics.density
val screenHeightDp = (screenHeight / density).roundToInt()
val adWidthPixels = binding.yandexInlineBannerView.width.takeIf { it > 0 } ?: screenWidth
val adWidthDp = (adWidthPixels / density).roundToInt()
val maxAdHeightDp = (screenHeightDp / 2).coerceAtLeast(1)
binding.yandexInlineBannerView.apply {
setAdUnitId(BuildConfig.YANDEX_INLINE_BANNER_MYPAGE_AD_UNIT_ID)
setAdSize(
BannerAdSize.inlineSize(
requireContext(),
adWidthDp,
maxAdHeightDp
)
)
loadAd(AdRequest.Builder().build())
}
}
}
private fun setupLatestNotice() {
@@ -498,4 +525,9 @@ class MyPageFragment : BaseFragment<FragmentMyBinding>(FragmentMyBinding::inflat
}
}
}
override fun onDestroyView() {
binding.yandexInlineBannerView.destroy()
super.onDestroyView()
}
}

View File

@@ -347,6 +347,13 @@
android:paddingHorizontal="24dp" />
</LinearLayout>
<com.yandex.mobile.ads.banner.BannerAdView
android:id="@+id/yandex_inline_banner_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,82 @@
# 20260421 마이페이지 Yandex 인라인 배너 추가
## 작업 체크리스트
- [x] Yandex adaptive inline banner 공식 요구사항과 MyPage 화면 삽입 위치를 확정한다.
QA: `app/src/main/res/layout/fragment_my.xml`, `app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt`, Yandex adaptive inline banner 문서를 근거로 스크롤 콘텐츠 최하단 삽입으로 설명할 수 있어야 한다.
- [x] 하단 인라인 배너 구현 계획과 ad unit id 교체 위치를 문서에 먼저 고정한다.
QA: 변경 파일과 ad unit id 수정 지점이 문서에 명시되어 있어야 한다.
- [x] `fragment_my.xml` 하단에 Yandex 배너 뷰를 추가한다.
QA: `NestedScrollView` 내부 콘텐츠 맨 끝에 배너 뷰가 추가되고 기존 MyPage 여백 스타일과 크게 어긋나지 않아야 한다.
- [x] `MyPageFragment.kt`에서 adaptive inline banner 크기 계산과 광고 로드를 구현한다.
QA: 측정된 너비를 기준으로 `BannerAdSize.inlineSize(...)`를 설정하고, ad unit id를 한 곳에서 교체할 수 있어야 한다.
- [x] 프래그먼트 뷰 종료 시 배너 리소스를 정리한다.
QA: `onDestroyView()`에서 배너 뷰 정리 코드가 실행되어 뷰 생명주기 종료 후 누수 가능성을 줄여야 한다.
- [x] 검증 결과를 문서 하단에 누적 기록한다.
QA: 최소 진단/빌드/수동 확인 결과와 실행 명령이 남아 있어야 한다.
## 범위 메모
- 요청 해석은 `MyPageFragment`의 스크롤 콘텐츠 최하단에 Yandex adaptive inline banner를 추가하는 것으로 한정한다.
- 화면 하단 고정 배너(sticky)가 아니라, 마이페이지를 끝까지 스크롤했을 때 보이는 inline 배너로 구현한다.
- 현재 프로젝트에는 `productFlavors`가 없어, 이번 변경에서는 기존 변형(`debug`/`release`)별 `buildConfigField`로 ad unit id를 분기한다.
- 현재 사용 중인 ad unit id는 `debug`에서 유지하고, `release`는 별도 값으로 교체할 수 있게 `app/build.gradle`의 각 변형 블록에 분리해 둔다.
- 기존 SDK 의존성과 앱 초기화 코드는 이미 존재하므로 이번 작업에서 `app/build.gradle`, `SodaLiveApp.kt`는 수정하지 않는다.
## 검증 계획
- `lsp_diagnostics`로 변경 파일의 신규 오류 여부를 확인한다.
- `./gradlew :app:assembleDebug`로 Android 리소스 병합과 컴파일 통과 여부를 확인한다.
- 가능하면 앱에서 MyPage를 열고 최하단까지 스크롤하는 수동 확인 절차를 기준으로 결과를 남긴다.
## 검증 기록
- 2026-04-21
- 무엇: 마이페이지 Yandex 인라인 배너 추가 작업의 범위와 구현 위치를 문서화했다.
- 왜: 저장소 규칙에 따라 `docs` 계획 문서를 먼저 만들고, 그 문서를 기준으로 구현과 검증 이력을 누적해야 하기 때문이다.
- 어떻게:
- 생성 파일: `docs/20260421_마이페이지Yandex인라인배너추가.md`
- 근거 파일: `app/src/main/res/layout/fragment_my.xml`, `app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt`, `app/src/main/java/kr/co/vividnext/sodalive/app/SodaLiveApp.kt`
- 근거 문서: `https://ads.yandex.com/helpcenter/ko/dev/android/adaptive-inline-banner`
- 결과: 구현 전 체크리스트, 범위, ad unit id 교체 위치, 검증 계획을 먼저 고정했다.
- 2026-04-21
- 무엇: MyPage 스크롤 콘텐츠 최하단에 Yandex inline banner 뷰와 배너 로드 코드를 추가했다.
- 왜: 요청 범위를 넓히지 않고 `MyPageFragment` 최하단에 adaptive inline banner를 붙이기 위해서다.
- 어떻게:
- 수정 파일: `app/src/main/res/layout/fragment_my.xml`, `app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt`
- 레이아웃 반영: `fragment_my.xml``NestedScrollView` 콘텐츠 마지막에 `@id/yandex_inline_banner_view` 추가
- 코드 반영: `setupBottomInlineBanner()`에서 측정된 너비 기준 `BannerAdSize.inlineSize(...)` 적용 후 `loadAd(...)` 호출
- ad unit id 교체 위치: `MyPageFragment.kt` companion object의 `YANDEX_INLINE_BANNER_AD_UNIT_ID`
- 정리 코드: `onDestroyView()`에서 `binding.yandexInlineBannerView.destroy()` 호출
- 2026-04-21
- 무엇: 변경 사항의 진단, 빌드, 설치, 수동 확인 가능 여부를 점검했다.
- 왜: Kotlin/XML LSP 미지원 환경에서도 실제 Android 리소스 병합과 컴파일, 기기 설치까지 통과해야 안전하게 반영됐다고 볼 수 있기 때문이다.
- 어떻게:
- 진단 도구: `lsp_diagnostics``.kt`, `.xml` 서버 미설정으로 사용 불가
- 실행 명령: `./gradlew :app:assembleDebug :app:testDebugUnitTest`
- 실행 결과: `BUILD SUCCESSFUL`
- 추가 실행 명령: `adb devices`, `./gradlew :app:installDebug`, `adb shell am start -n kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.splash.SplashActivity`
- 추가 결과: 연결 기기 1대(`2cec640c34017ece`)에 debug 앱 설치 성공
- 수동 확인 결과: 앱 실행 후 캡처 화면이 검은 스플래시 상태로만 남아 MyPage 진입 및 배너 실노출 확인까지는 진행하지 못했다.
- 비고: `MainActivity` 직접 실행은 non-exported Activity라 `SecurityException`으로 불가했고, 로그상 이번 변경으로 인한 신규 크래시는 확인되지 않았다.
- 2026-04-21
- 무엇: Yandex inline banner ad unit id를 변형별로 나누도록 수정했다.
- 왜: 현재 저장소는 `productFlavors` 없이 `debug`/`release` 변형만 사용하므로, 배포/테스트 환경에 따라 다른 ad unit id를 안전하게 적용할 수 있어야 하기 때문이다.
- 어떻게:
- 수정 파일: `app/build.gradle`, `app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt`
- Gradle 반영: `debug`/`release` 각각에 `buildConfigField 'String', 'YANDEX_INLINE_BANNER_AD_UNIT_ID', '"..."'` 추가
- 코드 반영: `MyPageFragment`가 companion object 상수 대신 `BuildConfig.YANDEX_INLINE_BANNER_AD_UNIT_ID`를 읽도록 변경
- 현재 기본값: `debug``R-M-19140297-1`, `release``REPLACE_WITH_RELEASE_YANDEX_INLINE_BANNER_AD_UNIT_ID`
- 추후 수정 위치: `app/build.gradle``debug`/`release` 블록
- 2026-04-21
- 무엇: 현재 사용 중인 ad unit id를 `debug`로 옮기고 `release`는 별도 값으로 분리했다.
- 왜: 요청대로 기존 ad unit id는 디버그 환경에서 유지하고, 릴리스 환경에서는 독립적으로 설정할 수 있어야 하기 때문이다.
- 어떻게:
- 수정 파일: `app/build.gradle`
- 값 조정: `debug``YANDEX_INLINE_BANNER_AD_UNIT_ID``R-M-19140297-1`로 변경
- 값 조정: `release``YANDEX_INLINE_BANNER_AD_UNIT_ID``REPLACE_WITH_RELEASE_YANDEX_INLINE_BANNER_AD_UNIT_ID`로 분리
- 릴리스 수정 위치: `app/build.gradle``release` 블록
- 2026-04-21
- 무엇: debug/release 분기 변경 후 빌드와 설정값 반영 상태를 다시 확인했다.
- 왜: 값만 바꾼 작업이라도 두 변형 모두 실제 Gradle 해석과 컴파일을 통과해야 하고, 현재 설정된 ad unit id가 어느 변형에 들어가는지 근거를 남겨야 하기 때문이다.
- 어떻게:
- 실행 명령: `./gradlew :app:assembleDebug :app:assembleRelease :app:testDebugUnitTest`
- 실행 결과: `BUILD SUCCESSFUL`
- 설정 확인: `app/build.gradle` 재확인 결과 `debug``R-M-19140297-1`, `release``REPLACE_WITH_RELEASE_YANDEX_INLINE_BANNER_AD_UNIT_ID`
- 비고: `BuildConfig` 생성 파일 경로는 이 환경에서 직접 조회되지 않았지만, 두 변형 빌드가 모두 성공해 `buildConfigField` 값 주입 자체는 통과한 것으로 확인했다.