diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8bd75f72..351241bc 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -43,6 +43,9 @@ android {
buildConfigField("String", "BACKEND_BASE_URL", "\"$backendBaseUrl\"")
val aiBaseUrl = localProperties.getProperty("AI_BASE_URL")
buildConfigField("String", "AI_BASE_URL", "\"$aiBaseUrl\"")
+
+ val admobAppId = localProperties.getProperty("ADMOB_APP_ID") ?: "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"
+ manifestPlaceholders["ADMOB_APP_ID"] = admobAppId
}
buildTypes {
@@ -162,6 +165,8 @@ dependencies {
//Timber
implementation("com.jakewharton.timber:timber:5.0.1")
+
+ implementation("com.google.android.gms:play-services-ads:23.6.0")
}
ksp {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1a83c497..a3a97f36 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,6 +15,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.Egobook"
android:usesCleartextTraffic="true">
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/egobook/app/MainActivity.kt b/app/src/main/java/com/egobook/app/MainActivity.kt
index 82b0ac6e..30f81503 100644
--- a/app/src/main/java/com/egobook/app/MainActivity.kt
+++ b/app/src/main/java/com/egobook/app/MainActivity.kt
@@ -1,6 +1,7 @@
package com.egobook.app
import android.os.Bundle
+import android.util.Log
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@@ -10,6 +11,12 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.egobook.app.databinding.ActivityMainBinding
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.MobileAds
+import com.google.android.gms.ads.rewarded.RewardedAd
+import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
+import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions
import dagger.hilt.android.AndroidEntryPoint
@@ -19,9 +26,17 @@ class MainActivity : AppCompatActivity(), BlurController, NotificationController
ActivityMainBinding.inflate(layoutInflater)
}
+ private var rewardedAd: RewardedAd? = null
+ private val adUnitId = "ca-app-pub-3940256099942544/5224354917"
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
+
+ MobileAds.initialize(this) {}
+
+ loadAd()
+
applyDefaultInsets(binding.main)
applyDefaultInsets(binding.fcvNotificationDrawer)
setContentView(binding.root)
@@ -75,6 +90,42 @@ class MainActivity : AppCompatActivity(), BlurController, NotificationController
}
}
+ private fun loadAd() {
+ val adRequest = AdRequest.Builder().build()
+ RewardedAd.load(this, adUnitId, adRequest, object : RewardedAdLoadCallback() {
+ override fun onAdFailedToLoad(adError: LoadAdError) {
+ rewardedAd = null
+ Log.d("AdMob", "광고 로드 실패")
+ }
+
+ override fun onAdLoaded(ad: RewardedAd) {
+ rewardedAd = ad
+ Log.d("AdMob", "광고 로드 성공!")
+ }
+ })
+ }
+
+ fun showAd(userId: String, onAdClosed: () -> Unit) {
+ if (rewardedAd != null) {
+
+ val ssvOptions = ServerSideVerificationOptions.Builder()
+ .setUserId(userId)
+
+ rewardedAd?.setServerSideVerificationOptions(ssvOptions.build())
+
+ rewardedAd?.show(this) { rewardItem ->
+ val rewardAmount = rewardItem.amount
+ val rewardType = rewardItem.type
+ Log.d("jang", "보상 지급! (서버로 콜백 날아감), $rewardType, rewardAmout: $rewardAmount")
+ }
+ onAdClosed()
+ rewardedAd = null
+ loadAd()
+ } else {
+ Log.d("jang", "아직 광고가 준비 안 됐어요. 잠시 후 다시 시도해주세요.")
+ }
+ }
+
override fun activateBlur(blurLevel: BlurLevel) {
binding.blurView.apply {
setBlurEnabled(true)
@@ -93,4 +144,5 @@ class MainActivity : AppCompatActivity(), BlurController, NotificationController
override fun closerDrawer() {
binding.root.closeDrawer(binding.fcvNotificationDrawer)
}
+
}
diff --git a/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt
index 6bc50469..cfbc77db 100644
--- a/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt
+++ b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt
@@ -25,6 +25,7 @@ import com.egobook.app.ui.home.repository.NetworkHomeNotificationRepository
import com.egobook.app.ui.home.repository.NetworkTendencyLevelService
import com.egobook.app.ui.home.repository.NetworkUserRepository
import com.egobook.app.ui.home.repository.UserActivityRepository
+import com.egobook.app.ui.home.repository.UserAdRepository
import com.egobook.app.ui.home.repository.UserPsychologyRepository
import com.egobook.app.ui.home.repository.UserRepository
import com.egobook.app.ui.home.repository.UserTendencyRepository
@@ -84,6 +85,10 @@ abstract class RepositoryModule {
@Singleton
abstract fun bindActivityRecordRepository(impl: NetworkUserRepository): UserActivityRepository
+ @Binds
+ @Singleton
+ abstract fun bindAdRepository(impl: NetworkUserRepository): UserAdRepository
+
@Binds
@Singleton
abstract fun bindPsychologyRepository(impl: NetworkUserRepository): UserPsychologyRepository
diff --git a/app/src/main/java/com/egobook/app/ui/home/HomeViewModel.kt b/app/src/main/java/com/egobook/app/ui/home/HomeViewModel.kt
index feddde9d..be5b5c8b 100644
--- a/app/src/main/java/com/egobook/app/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/HomeViewModel.kt
@@ -3,6 +3,8 @@ package com.egobook.app.ui.home
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.egobook.app.ui.home.repository.AdInfoDto
+import com.egobook.app.ui.home.repository.UserAdRepository
import com.egobook.app.ui.home.repository.UserPsychologyRepository
import com.egobook.app.ui.home.repository.UserRepository
import com.egobook.app.ui.home.user.Ink
@@ -11,8 +13,10 @@ import com.egobook.app.ui.home.user.User
import com.egobook.app.ui.shop.CustomItem
import com.egobook.app.ui.shop.StoreRepository
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -21,20 +25,26 @@ import javax.inject.Inject
class HomeViewModel @Inject constructor(
private val userRepository: UserRepository,
private val storeRepository: StoreRepository,
+ private val userAdRepository: UserAdRepository,
private val psychologyRepository: UserPsychologyRepository
) : ViewModel() {
+ private val _uiState = MutableStateFlow(User(id=-1, Level(1), Ink(0)))
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private val _adState = MutableStateFlow(AdInfoDto(0, 0, false, 0, ""))
+ val adState: StateFlow = _adState.asStateFlow()
+
+ private val _equippedItems = MutableStateFlow>(emptyList())
+ val equippedItems: StateFlow> = _equippedItems
+
+ private val _dailyPhycologyReadState = MutableStateFlow(false)
+ val dailyPhycologyReadState = _dailyPhycologyReadState.asStateFlow()
init {
fetchUser()
fetchEquipItems()
fetchDailyPhycologyReadState()
}
- private val _uiState = MutableStateFlow(User(Level(1), Ink(0)))
- val uiState: StateFlow = _uiState.asStateFlow()
- private val _dailyPhycologyReadState = MutableStateFlow(false)
- val dailyPhycologyReadState = _dailyPhycologyReadState.asStateFlow()
- private val _equippedItems = MutableStateFlow>(emptyList())
- val equippedItems: StateFlow> = _equippedItems
fun fetchEquipItems() {
viewModelScope.launch {
@@ -58,4 +68,19 @@ class HomeViewModel @Inject constructor(
}
}
}
+
+ fun loadCurrentAdInfo() {
+ viewModelScope.launch {
+ val adInfo = userAdRepository.loadAdInfo()
+ _adState.value = adInfo
+ }
+ }
+
+ fun watchAd() {
+ viewModelScope.launch {
+ val message = userAdRepository.watchAd()
+ fetchUser()
+ Log.d("HomeViewModel", "Ad watched: $message")
+ }
+ }
}
diff --git a/app/src/main/java/com/egobook/app/ui/home/repository/UserDto.kt b/app/src/main/java/com/egobook/app/ui/home/repository/UserDto.kt
index 4903f326..640493f6 100644
--- a/app/src/main/java/com/egobook/app/ui/home/repository/UserDto.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/repository/UserDto.kt
@@ -5,6 +5,7 @@ import com.egobook.app.ui.home.user.Level
import com.egobook.app.ui.home.user.User
data class UserDto(
+ val userId: Int,
val nickname: String,
val level: Int,
val ink: Int,
@@ -13,5 +14,5 @@ data class UserDto(
val isFirstAttendanceToday: Boolean,
val attendanceRewardInk: Int
) {
- fun toDomain(): User = User(Level(level), Ink(ink))
+ fun toDomain(): User = User(id = userId, Level(level), Ink(ink))
}
diff --git a/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt b/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt
index f99ceb37..145705e1 100644
--- a/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/repository/UserRepository.kt
@@ -5,7 +5,9 @@ import com.egobook.app.di.qualifier.BackendApi
import com.egobook.app.ui.home.user.Tendency
import com.egobook.app.ui.home.user.User
import retrofit2.Retrofit
+import retrofit2.http.Body
import retrofit2.http.GET
+import retrofit2.http.POST
import javax.inject.Inject
import javax.inject.Singleton
@@ -13,6 +15,12 @@ interface UserRepository {
suspend fun load(): User
}
+interface UserAdRepository {
+ suspend fun loadAdInfo(): AdInfoDto
+
+ suspend fun watchAd(): String
+}
+
interface UserTendencyRepository {
suspend fun loadTendencies(): List
}
@@ -32,6 +40,36 @@ interface NetworkTendencyLevelService {
suspend fun loadTendencyLevels(): BaseResponse
}
+data class AdRequestDto(
+ val rewardType: String,
+ val targetId: Int?,
+ val adUnitId: String = "ca-app-pub-test/12345"
+)
+
+data class AdResponseDto(
+ val code: String,
+ val message: String,
+ val status: Int
+)
+
+interface NetworkAdService {
+ @GET("/ads/info")
+ suspend fun loadAdInfo(): BaseResponse
+
+ @POST("/ads/testReward")
+ suspend fun watchAd(
+ @Body adRequest: AdRequestDto
+ ): AdResponseDto
+}
+
+data class AdInfoDto(
+ val currentViewCount: Int,
+ val maxLimit: Int,
+ val isAvailable: Boolean,
+ val rewardPerAd: Int,
+ val message: String
+)
+
data class PsychologyStateDto(
val isBottleVisible: Boolean
)
@@ -68,11 +106,13 @@ interface NetworkPsychologyService {
@Singleton
class NetworkUserRepository @Inject constructor(
@BackendApi private val retrofit: Retrofit
-) : UserRepository, UserTendencyRepository, UserActivityRepository, UserPsychologyRepository {
+) : UserRepository, UserTendencyRepository, UserActivityRepository, UserPsychologyRepository, UserAdRepository {
private val userService by lazy { retrofit.create(NetworkUserService::class.java) }
private val tendencyLevelService by lazy { retrofit.create(NetworkTendencyLevelService::class.java) }
private val activityRecordService by lazy { retrofit.create(NetworkActivityRecordService::class.java) }
+ private val networkAdService by lazy { retrofit.create(NetworkAdService::class.java) }
+
private val psychologyService by lazy { retrofit.create(NetworkPsychologyService::class.java) }
override suspend fun load(): User {
@@ -92,6 +132,20 @@ class NetworkUserRepository @Inject constructor(
return activityRecordResponse.data.toDomain()
}
+ override suspend fun loadAdInfo(): AdInfoDto {
+ val adInfoResponse: BaseResponse = networkAdService.loadAdInfo()
+ return adInfoResponse.data
+ }
+
+ override suspend fun watchAd(): String {
+ val watchingAdResponse = networkAdService.watchAd(
+ AdRequestDto(
+ "INK", null
+ )
+ )
+ return watchingAdResponse.message
+ }
+
override suspend fun isReadDailyPsychology(): Boolean {
val psychologyResponse: BaseResponse =
psychologyService.isReadDailyPsychology()
@@ -104,4 +158,5 @@ class NetworkUserRepository @Inject constructor(
psychologyService.loadDailyPsychology()
return psychologyResponse.data
}
+
}
diff --git a/app/src/main/java/com/egobook/app/ui/home/ui/AdDialog.kt b/app/src/main/java/com/egobook/app/ui/home/ui/AdDialog.kt
index 4b7cb5fe..ab8d710f 100644
--- a/app/src/main/java/com/egobook/app/ui/home/ui/AdDialog.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/ui/AdDialog.kt
@@ -6,15 +6,24 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.egobook.app.MainActivity
import com.egobook.app.databinding.DialogAdBinding
import com.egobook.app.removeScreenBlur
+import com.egobook.app.ui.home.HomeViewModel
+import kotlinx.coroutines.launch
class AdDialog() : DialogFragment() {
private var _binding: DialogAdBinding? = null
private val binding get() = checkNotNull(_binding) { "Fragment가 제거되었습니다." }
-
+ private val viewModel: HomeViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -27,14 +36,52 @@ class AdDialog() : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ val userId = arguments?.getString(ARG_USER_ID) ?: "사용자가 없습니다"
+
+ viewModel.loadCurrentAdInfo()
+
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.adState.collect { adState ->
+ if (adState.isAvailable) {
+ binding.btnWatch.isEnabled = true
+ } else {
+ binding.btnWatch.isEnabled = false
+ }
+ binding.btnWatch.text = "광고보기 ${adState.currentViewCount}/${adState.maxLimit}"
+ binding.tvAdReward.text = adState.rewardPerAd.toString()
+ }
+ }
+
binding.btnBack.setOnClickListener {
removeScreenBlur()
dismiss()
}
+
+ binding.btnWatch.setOnClickListener {
+ (activity as MainActivity).showAd(userId) {
+ viewModel.watchAd()
+ }
+ removeScreenBlur()
+ dismiss()
+ }
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
+
+ companion object {
+ private const val ARG_USER_ID = "arg_user_id"
+
+ fun newInstance(userId: String): AdDialog {
+ val args = Bundle().apply {
+ putString(ARG_USER_ID, userId)
+ }
+ return AdDialog().apply {
+ arguments = args
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/egobook/app/ui/home/ui/HomeFragment.kt b/app/src/main/java/com/egobook/app/ui/home/ui/HomeFragment.kt
index ccd3362e..74c85690 100644
--- a/app/src/main/java/com/egobook/app/ui/home/ui/HomeFragment.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/ui/HomeFragment.kt
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
@@ -42,7 +43,7 @@ class HomeFragment(): Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- val viewModel: HomeViewModel by viewModels()
+ val viewModel: HomeViewModel by activityViewModels()
lifecycleScope.launch {
viewModel.uiState.collect { userState ->
binding.tvLevel.text = "Lv ${userState.level.number}"
@@ -102,8 +103,9 @@ class HomeFragment(): Fragment() {
}
binding.ivAd.setOnClickListener {
+ val currentUserId = viewModel.uiState.value.id
applyScreenBlur(BlurLevel.BASE)
- val dialog = AdDialog()
+ val dialog = AdDialog.newInstance(currentUserId.toString())
dialog.isCancelable = false
dialog.show(parentFragmentManager, "ConfirmDialog")
}
diff --git a/app/src/main/java/com/egobook/app/ui/home/user/User.kt b/app/src/main/java/com/egobook/app/ui/home/user/User.kt
index 6e23263d..470765c4 100644
--- a/app/src/main/java/com/egobook/app/ui/home/user/User.kt
+++ b/app/src/main/java/com/egobook/app/ui/home/user/User.kt
@@ -1,3 +1,3 @@
package com.egobook.app.ui.home.user
-data class User(val level: Level, val ink: Ink)
+data class User(val id: Int, val level: Level, val ink: Ink)
diff --git a/app/src/main/java/com/egobook/app/ui/shop/StoreFragment.kt b/app/src/main/java/com/egobook/app/ui/shop/StoreFragment.kt
index 0dedca7c..a8659935 100644
--- a/app/src/main/java/com/egobook/app/ui/shop/StoreFragment.kt
+++ b/app/src/main/java/com/egobook/app/ui/shop/StoreFragment.kt
@@ -51,6 +51,7 @@ class StoreFragment: Fragment() {
insets
}
val viewModel: StoreViewModel by activityViewModels()
+ viewModel.loadInk()
viewPager = binding.vp2StoreCollectionContainer
viewPager.adapter = StoreCollectionAdapter(this)
val tabLayout = binding.tlTabs
diff --git a/app/src/main/res/layout/dialog_ad.xml b/app/src/main/res/layout/dialog_ad.xml
index 015472be..03693423 100644
--- a/app/src/main/res/layout/dialog_ad.xml
+++ b/app/src/main/res/layout/dialog_ad.xml
@@ -63,13 +63,13 @@
android:src="@drawable/ink_icon" />
+ android:layout_marginStart="4dp" />
@@ -82,7 +82,7 @@
+ android:text="광고 보기 10/10" />