mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-18 11:49:01 +00:00
Implement ad cooldown
This commit is contained in:
parent
c1b29a547a
commit
ce76c40eec
18 changed files with 408 additions and 84 deletions
|
|
@ -129,8 +129,8 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.1"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
|
||||
|
|
@ -145,7 +145,7 @@ dependencies {
|
|||
attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
|
||||
}
|
||||
}
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.6.10"
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.6.20"
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -166,8 +166,8 @@ android {
|
|||
buildConfigField "String", "TESTING_LEVEL", "\"production\""
|
||||
resConfigs 'en', 'bg', 'de', 'en-rGB', 'es', 'fr', 'hr-rHR', 'in', 'it', 'iw', 'ja', 'ko', 'lt', 'nl', 'pl', 'pt-rBR', 'pt-rPT', 'ru', 'tr', 'zh', 'zh-rTW'
|
||||
|
||||
versionCode 3274
|
||||
versionName "3.5.1.3"
|
||||
versionCode 3300
|
||||
versionName "3.6"
|
||||
|
||||
targetSdkVersion 32
|
||||
|
||||
|
|
@ -179,7 +179,6 @@ android {
|
|||
viewBinding true
|
||||
}
|
||||
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
}
|
||||
|
|
@ -387,14 +386,6 @@ jacoco {
|
|||
toolVersion = "0.8.7"
|
||||
}
|
||||
|
||||
// packages to exclude for example generated classes, R class and models package, add all packages that you wish to exclude from test coverage
|
||||
def fileFilter = [
|
||||
'**/*$ViewInjector*.*','**/*$ViewBinder*.*', '**/HabiticaIcons*.*', '**/DeviceName.*', '**/databinding/*Binding.*',
|
||||
'**/R.class', '**/R.styleable', '**/R$*.class', '**/BuildConfig.*', '**/EmojiMap.*',
|
||||
'**/Manifest*.*', 'android/**/*.*', '**/*RealmProxy*.*', '**/io/realm/*']
|
||||
def debugTree = fileTree(dir: "${buildDir}/intermediates/asm_instrumented_project_classes/prodDebug", excludes: fileFilter)
|
||||
def mainSrc = "${project.projectDir}/src/main/java"
|
||||
|
||||
task ktlint(type: JavaExec, group: "verification") {
|
||||
description = "Check Kotlin code style."
|
||||
classpath = configurations.ktlint
|
||||
|
|
|
|||
23
Habitica/res/drawable/ad_button_background.xml
Normal file
23
Habitica/res/drawable/ad_button_background.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item>
|
||||
<!-- create gradient you want to use with the angle you want to use -->
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:startColor="@color/green_100"
|
||||
android:endColor="@color/green_500" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="3dp"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="3dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<solid android:color="@color/brand_400" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
7
Habitica/res/drawable/ad_button_background_disabled.xml
Normal file
7
Habitica/res/drawable/ad_button_background_disabled.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:color="@color/brand_500" android:width="3dp" />
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
</shape>
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/transparent" />
|
||||
<corners android:radius="@dimen/rounded_button_radius" />
|
||||
<stroke
|
||||
android:width="0.5dip"
|
||||
android:color="#1f000000"/>
|
||||
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@
|
|||
android:background="@drawable/armoire_background"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="12dp">
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingTop="24dp">
|
||||
<TextView
|
||||
android:id="@+id/equipment_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
@ -96,11 +97,8 @@
|
|||
android:layout_height="60dp"
|
||||
android:text="@string/equip"
|
||||
android:textStyle="bold"
|
||||
style="@style/HabiticaButton.White"/>
|
||||
<Space
|
||||
android:id="@+id/button_spacer"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="wrap_content" />
|
||||
style="@style/HabiticaButton.White"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
<Button
|
||||
android:id="@+id/close_button"
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -110,7 +108,16 @@
|
|||
android:textStyle="bold"
|
||||
style="@style/HabiticaButton.White"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.ads.AdButton
|
||||
android:id="@+id/ad_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
app:text="@string/watch_ad_to_open"
|
||||
android:layout_marginTop="4dp"
|
||||
app:currency="gold" />
|
||||
<TextView
|
||||
android:id="@+id/drop_rate_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/armoire_drop_rates"
|
||||
|
|
|
|||
20
Habitica/res/layout/ad_button.xml
Normal file
20
Habitica/res/layout/ad_button.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp" />
|
||||
<com.habitrpg.android.habitica.ui.views.CurrencyView
|
||||
android:id="@+id/currency_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:currency="gold"
|
||||
android:layout_marginStart="24dp" />
|
||||
</merge>
|
||||
|
|
@ -27,6 +27,8 @@
|
|||
<attr name="headerOffsetColor" format="color" />
|
||||
<attr name="headerTextColor" format="color" />
|
||||
<attr name="widgetBackgroundRadius" format="dimension" />
|
||||
<attr name="currency" format="string" />
|
||||
|
||||
<declare-styleable name="AvatarView">
|
||||
<attr name="showBackground" format="boolean" />
|
||||
<attr name="showMount" format="boolean" />
|
||||
|
|
@ -77,7 +79,7 @@
|
|||
<attr name="barBackgroundColor" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="CurrencyView">
|
||||
<attr name="currency" format="string" />
|
||||
<attr name="currency" />
|
||||
<attr name="hasLightBackground" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="CurrencyViews">
|
||||
|
|
@ -153,4 +155,8 @@
|
|||
<attr name="android:inputType" />
|
||||
<attr name="android:maxLines" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="AdButton">
|
||||
<attr name="text" />
|
||||
<attr name="currency" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1230,4 +1230,7 @@
|
|||
<string name="equipment_remaining">Equipment Remaining: %d</string>
|
||||
<string name="new_pieces_added_every_month">New pieces added every month</string>
|
||||
<string name="watch_ad">Watch Ad</string>
|
||||
<string name="available_in">Available in %s</string>
|
||||
<string name="watch_ad_to_open">Watch ad to open again</string>
|
||||
<string name="watch_ad_to_revive">Watch Ad to revive</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -104,5 +104,22 @@
|
|||
<key>enableTeamBoards</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
|
||||
<entry>
|
||||
<key>enableNewArmoire</key>
|
||||
<value>true</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableArmoireAds</key>
|
||||
<value>true</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableFaintAds</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
<entry>
|
||||
<key>enableSpellAds</key>
|
||||
<value>false</value>
|
||||
</entry>
|
||||
</defaultsMap>
|
||||
<!-- END xml_defaults -->
|
||||
|
|
@ -34,6 +34,7 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
|
|||
import com.habitrpg.android.habitica.components.AppComponent
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.LanguageHelper
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
|
||||
|
|
@ -73,6 +74,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
setLocale()
|
||||
setupRemoteConfig()
|
||||
setupNotifications()
|
||||
setupAdHandler()
|
||||
HabiticaIconsHelper.init(this)
|
||||
MarkdownParser.setup(this)
|
||||
|
||||
|
|
@ -110,6 +112,10 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
|
|||
checkIfNewVersion()
|
||||
}
|
||||
|
||||
private fun setupAdHandler() {
|
||||
AdHandler.setup(sharedPrefs, analyticsManager)
|
||||
}
|
||||
|
||||
private fun setLocale() {
|
||||
val resources = resources
|
||||
val configuration: Configuration = resources.configuration
|
||||
|
|
|
|||
|
|
@ -4,16 +4,12 @@ import android.content.res.Resources
|
|||
import com.habitrpg.android.habitica.R
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlin.math.round
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.milliseconds
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class DateUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
fun createDate(year: Int, month: Int, day: Int): Date {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.set(Calendar.YEAR, year)
|
||||
|
|
@ -33,11 +29,11 @@ fun Date.getAgoString(res: Resources): String {
|
|||
}
|
||||
|
||||
fun Long.getAgoString(res: Resources): String {
|
||||
val diff = Date().time - this
|
||||
val diff = (Date().time - this).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffMinutes = diff / (60 * 1000) % 60
|
||||
val diffHours = diff / (60 * 60 * 1000) % 24
|
||||
val diffDays = diff / (24 * 60 * 60 * 1000)
|
||||
val diffMinutes = diff.inWholeMinutes
|
||||
val diffHours = diff.inWholeHours
|
||||
val diffDays = diff.inWholeDays
|
||||
val diffWeeks = diffDays / 7
|
||||
val diffMonths = diffDays / 30
|
||||
|
||||
|
|
@ -63,30 +59,29 @@ fun Date.getRemainingString(res: Resources): String {
|
|||
return this.time.getRemainingString(res)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun Long.getRemainingString(res: Resources): String {
|
||||
val diff = (this - Date().time).milliseconds
|
||||
val diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffMinutes = diff.inMinutes
|
||||
val diffHours = diff.inHours
|
||||
val diffDays = diff.inDays
|
||||
val diffWeeks = diffDays / 7f
|
||||
val diffMonths = diffDays / 30f
|
||||
val diffMinutes = diff.inWholeMinutes
|
||||
val diffHours = diff.inWholeHours
|
||||
val diffDays = diff.inWholeDays
|
||||
val diffWeeks = diffDays / 7
|
||||
val diffMonths = diffDays / 30
|
||||
|
||||
return when {
|
||||
diffMonths != 0.0 -> if (round(diffMonths) == 1.0) {
|
||||
diffMonths != 0L -> if (diffMonths == 1L) {
|
||||
res.getString(R.string.remaining_1month)
|
||||
} else res.getString(R.string.remaining_months, round(diffMonths).toInt())
|
||||
diffWeeks != 0.0 -> if (round(diffWeeks) == 1.0) {
|
||||
} else res.getString(R.string.remaining_months, diffMonths)
|
||||
diffWeeks != 0L -> if (diffWeeks == 1L) {
|
||||
res.getString(R.string.remaining_1week)
|
||||
} else res.getString(R.string.remaining_weeks, round(diffWeeks).toInt())
|
||||
diffDays != 0.0 -> if (diffDays == 1.0) {
|
||||
} else res.getString(R.string.remaining_weeks, diffWeeks)
|
||||
diffDays != 0L -> if (diffDays == 1L) {
|
||||
res.getString(R.string.remaining_1day)
|
||||
} else res.getString(R.string.remaining_days, diffDays)
|
||||
diffHours != 0.0 -> if (diffHours == 1.0) {
|
||||
diffHours != 0L -> if (diffHours == 1L) {
|
||||
res.getString(R.string.remaining_1hour)
|
||||
} else res.getString(R.string.remaining_hours, diffHours)
|
||||
diffMinutes == 1.0 -> res.getString(R.string.remaining_1Minute)
|
||||
diffMinutes == 1L -> res.getString(R.string.remaining_1Minute)
|
||||
else -> res.getString(R.string.remaining_minutes, diffMinutes)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,16 +90,15 @@ fun Date.getShortRemainingString(): String {
|
|||
return time.getShortRemainingString()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun Long.getShortRemainingString(): String {
|
||||
var diff = Duration.milliseconds((this - Date().time))
|
||||
var diff = (this - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
|
||||
val diffDays = diff.toInt(DurationUnit.DAYS)
|
||||
diff -= Duration.days(diffDays)
|
||||
diff -= diffDays.toDuration(DurationUnit.DAYS)
|
||||
val diffHours = diff.toInt(DurationUnit.HOURS)
|
||||
diff -= Duration.hours(diffHours)
|
||||
diff -= diffDays.toDuration(DurationUnit.HOURS)
|
||||
val diffMinutes = diff.toInt(DurationUnit.MINUTES)
|
||||
diff -= Duration.minutes(diffMinutes)
|
||||
diff -= diffMinutes.toDuration(DurationUnit.MINUTES)
|
||||
val diffSeconds = diff.toInt(DurationUnit.SECONDS)
|
||||
|
||||
var str = "${diffMinutes}m"
|
||||
|
|
@ -119,3 +113,7 @@ fun Long.getShortRemainingString(): String {
|
|||
}
|
||||
return str
|
||||
}
|
||||
|
||||
fun Duration.getMinuteOrSeconds(): DurationUnit {
|
||||
return if (this.inWholeHours < 1) DurationUnit.SECONDS else DurationUnit.MINUTES
|
||||
}
|
||||
|
|
@ -2,18 +2,63 @@ package com.habitrpg.android.habitica.helpers
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.android.gms.ads.AdRequest
|
||||
import com.google.android.gms.ads.FullScreenContentCallback
|
||||
import com.google.android.gms.ads.LoadAdError
|
||||
import com.google.android.gms.ads.MobileAds
|
||||
import com.google.android.gms.ads.OnUserEarnedRewardListener
|
||||
import com.google.android.gms.ads.RequestConfiguration
|
||||
import com.google.android.gms.ads.rewarded.RewardItem
|
||||
import com.google.android.gms.ads.rewarded.RewardedAd
|
||||
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.MessageDigest
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
enum class AdType {
|
||||
ARMOIRE,
|
||||
SPELL,
|
||||
FAINT;
|
||||
|
||||
class AdHandler(var activity: Activity, var rewardAction: (Boolean) -> Unit): OnUserEarnedRewardListener {
|
||||
val adUnitID: String
|
||||
get() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return "ca-app-pub-3940256099942544/5224354917"
|
||||
}
|
||||
return when (this) {
|
||||
ARMOIRE -> "ca-app-pub-5911973472413421/9392092486\n"
|
||||
SPELL -> "ca-app-pub-5911973472413421/1738504765"
|
||||
FAINT -> "ca-app-pub-5911973472413421/1738504765"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.md5(): String? {
|
||||
try {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val array = md.digest(this.toByteArray())
|
||||
val sb = StringBuffer()
|
||||
for (i in array.indices) {
|
||||
sb.append(Integer.toHexString(array[i].toInt() and 0xFF or 0x100).substring(1, 3))
|
||||
}
|
||||
return sb.toString()
|
||||
} catch (e: java.security.NoSuchAlgorithmException) {
|
||||
} catch (ex: UnsupportedEncodingException) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boolean) -> Unit): OnUserEarnedRewardListener {
|
||||
private var rewardedAd: RewardedAd? = null
|
||||
|
||||
companion object {
|
||||
|
|
@ -24,13 +69,40 @@ class AdHandler(var activity: Activity, var rewardAction: (Boolean) -> Unit): On
|
|||
DISABLED
|
||||
}
|
||||
|
||||
private lateinit var analyticsManager: AnalyticsManager
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
const val TAG = "AdHandler"
|
||||
const val adUnitID = "ca-app-pub-3940256099942544/5224354917"
|
||||
|
||||
private var currentAdStatus = AdStatus.UNINITIALIZED
|
||||
|
||||
private var nextAdAllowed: MutableMap<AdType, Date> = mutableMapOf()
|
||||
|
||||
fun nextAdAllowedDate(type: AdType): Date? {
|
||||
return nextAdAllowed[type]
|
||||
}
|
||||
|
||||
fun isAllowed(type: AdType): Boolean {
|
||||
return nextAdAllowedDate(type)?.after(Date()) == true
|
||||
}
|
||||
|
||||
fun setNextAllowedDate(type: AdType, date: Date) {
|
||||
nextAdAllowed[type] = date
|
||||
sharedPreferences.edit {
|
||||
putLong("nextAd${type.name}", date.time)
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize(context: Context, onComplete: () -> Unit) {
|
||||
if (currentAdStatus != AdStatus.UNINITIALIZED) return
|
||||
|
||||
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
|
||||
val android_id: String =
|
||||
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
val deviceId: String = android_id.md5()?.uppercase() ?: ""
|
||||
val configuration = RequestConfiguration.Builder().setTestDeviceIds(listOf(deviceId)).build()
|
||||
MobileAds.setRequestConfiguration(configuration)
|
||||
}
|
||||
|
||||
currentAdStatus = AdStatus.INITIALIZING
|
||||
MobileAds.initialize(context) {
|
||||
currentAdStatus = AdStatus.READY
|
||||
|
|
@ -56,13 +128,34 @@ class AdHandler(var activity: Activity, var rewardAction: (Boolean) -> Unit): On
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setup(sharedPrefs: SharedPreferences, analyticsManager: AnalyticsManager) {
|
||||
this.sharedPreferences = sharedPrefs
|
||||
this.analyticsManager = analyticsManager
|
||||
|
||||
for (type in AdType.values()) {
|
||||
val time = sharedPrefs.getLong("nextAd${type.name}", 0)
|
||||
if (time > 0) {
|
||||
nextAdAllowed[type] = Date(time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun prepare() {
|
||||
whenAdsInitialized(activity) {
|
||||
val adRequest = AdRequest.Builder().build()
|
||||
val adRequest = AdRequest.Builder()
|
||||
.build()
|
||||
|
||||
RewardedAd.load(activity, adUnitID, adRequest, object : RewardedAdLoadCallback() {
|
||||
if (BuildConfig.DEBUG || BuildConfig.TESTING_LEVEL == "staff" || BuildConfig.TESTING_LEVEL == "alpha") {
|
||||
if (!adRequest.isTestDevice(activity)) {
|
||||
// users in this group need to be configured as Test device. better to fail if they aren't
|
||||
currentAdStatus = AdStatus.DISABLED
|
||||
return@whenAdsInitialized
|
||||
}
|
||||
}
|
||||
|
||||
RewardedAd.load(activity, type.adUnitID, adRequest, object : RewardedAdLoadCallback() {
|
||||
override fun onAdFailedToLoad(adError: LoadAdError) {
|
||||
rewardAction(false)
|
||||
}
|
||||
|
|
@ -103,17 +196,24 @@ class AdHandler(var activity: Activity, var rewardAction: (Boolean) -> Unit): On
|
|||
}
|
||||
|
||||
private fun showRewardedAd() {
|
||||
if (nextAdAllowedDate(type)?.after(Date()) == true) {
|
||||
return
|
||||
}
|
||||
if (rewardedAd != null) {
|
||||
rewardedAd?.show(activity, this)
|
||||
setNextAllowedDate(type, Date(Date().time + 1.toDuration(DurationUnit.HOURS).inWholeMilliseconds))
|
||||
} else {
|
||||
Log.d(TAG, "The rewarded ad wasn't ready yet.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUserEarnedReward(rewardItem: RewardItem) {
|
||||
val rewardAmount = rewardItem.amount
|
||||
val rewardType = rewardItem.type
|
||||
Log.d(TAG, "User earned the reward. ${rewardAmount}, ${rewardType}")
|
||||
analyticsManager.logEvent("adRewardEarned", bundleOf(
|
||||
Pair("type", type.name)
|
||||
))
|
||||
FirebaseAnalytics.getInstance(activity).logEvent("adRewardEarned", bundleOf(
|
||||
Pair("type", type.name)
|
||||
))
|
||||
rewardAction(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,4 +138,20 @@ class AppConfigManager(contentRepository: ContentRepository?) {
|
|||
fun enableTeamBoards(): Boolean {
|
||||
return remoteConfig.getBoolean("enableTeamBoards")
|
||||
}
|
||||
|
||||
fun enableArmoireAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableArmoireAds")
|
||||
}
|
||||
|
||||
fun enableFaintAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableFaintAds")
|
||||
}
|
||||
|
||||
fun enableSpellAds(): Boolean {
|
||||
return remoteConfig.getBoolean("enableSpellAds")
|
||||
}
|
||||
|
||||
fun enableNewArmoire(): Boolean {
|
||||
return remoteConfig.getBoolean("enableNewArmoire")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
package com.habitrpg.android.habitica.ui.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding
|
||||
import com.habitrpg.android.habitica.extensions.observeOnce
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.ui.helpers.loadImage
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.plattysoft.leonids.ParticleSystem
|
||||
|
|
@ -58,12 +63,40 @@ class ArmoireActivity: BaseActivity() {
|
|||
binding.noEquipmentView.visibility = if (remaining > 0) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
if (appConfigManager.enableArmoireAds()) {
|
||||
val handler = AdHandler(this, AdType.ARMOIRE) {
|
||||
Log.d("AdHandler", "Giving Armoire")
|
||||
val user = userViewModel.user.value ?: return@AdHandler
|
||||
val currentGold = user.stats?.gp ?: return@AdHandler
|
||||
compositeSubscription.add(userRepository.updateUser("stats.gp", currentGold + 100)
|
||||
.flatMap { inventoryRepository.buyItem(user, "armoire", 100.0, 1) }
|
||||
.subscribe({
|
||||
configure(it.armoire["type"] ?: "",
|
||||
it.armoire["dropKey"] ?: "",
|
||||
it.armoire["dropText"] ?: "")
|
||||
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
|
||||
hasAnimatedChanges = false
|
||||
gold = null
|
||||
}, RxErrorHandler.handleEmptyError()))
|
||||
}
|
||||
handler.prepare()
|
||||
binding.adButton.updateForAdType(AdType.ARMOIRE, lifecycleScope)
|
||||
binding.adButton.setOnClickListener {
|
||||
handler.show()
|
||||
}
|
||||
} else {
|
||||
binding.adButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.closeButton.setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
binding.equipButton.setOnClickListener {
|
||||
equipmentKey?.let { it1 -> inventoryRepository.equip("gear", it1).subscribe() }
|
||||
finish()
|
||||
}
|
||||
binding.dropRateButton.setOnClickListener {
|
||||
|
||||
}
|
||||
intent.extras?.let {
|
||||
val args = ArmoireActivityArgs.fromBundle(it)
|
||||
|
|
@ -79,9 +112,11 @@ class ArmoireActivity: BaseActivity() {
|
|||
|
||||
private fun startAnimation() {
|
||||
val gold = gold?.toInt()
|
||||
if (hasAnimatedChanges || gold == null) return
|
||||
binding.goldView.value = (gold).toDouble()
|
||||
binding.goldView.value = (gold - 100).toDouble()
|
||||
if (hasAnimatedChanges) return
|
||||
if (gold != null) {
|
||||
binding.goldView.value = (gold).toDouble()
|
||||
binding.goldView.value = (gold - 100).toDouble()
|
||||
}
|
||||
|
||||
val container = binding.confettiAnchor
|
||||
container.postDelayed(
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.habitrpg.android.habitica.extensions.isUsingNightModeResources
|
|||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
|
|
@ -203,9 +204,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
setupBottomnavigationLayoutListener()
|
||||
|
||||
viewModel.onCreate()
|
||||
|
||||
val args = ArmoireActivityDirections.openArmoireActivity("experience", "", "")
|
||||
MainNavigationController.navigate(R.id.armoireActivity, args.arguments)
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
|
|
@ -450,11 +448,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
}
|
||||
|
||||
if (this.faintDialog == null && !this.isFinishing) {
|
||||
val handler = AdHandler(this) {
|
||||
Log.d("AdHandler", "Reviving user")
|
||||
compositeSubscription.add(userRepository.updateUser("stats.hp", 50).subscribe({}, RxErrorHandler.handleEmptyError()))
|
||||
}
|
||||
handler.prepare()
|
||||
|
||||
val binding = DialogFaintBinding.inflate(this.layoutInflater)
|
||||
binding.hpBar.setLightBackground(true)
|
||||
binding.hpBar.setIcon(HabiticaIconsHelper.imageOfHeartLightBg())
|
||||
|
|
@ -467,9 +461,19 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
faintDialog = null
|
||||
userRepository.revive().subscribe({ }, RxErrorHandler.handleEmptyError())
|
||||
}
|
||||
faintDialog?.addButton(R.string.watch_ad, true) { _, _ ->
|
||||
faintDialog = null
|
||||
handler.show()
|
||||
if (AdHandler.isAllowed(AdType.FAINT)) {
|
||||
val handler = AdHandler(this, AdType.FAINT) {
|
||||
Log.d("AdHandler", "Reviving user")
|
||||
compositeSubscription.add(
|
||||
userRepository.updateUser("stats.hp", 50)
|
||||
.subscribe({}, RxErrorHandler.handleEmptyError())
|
||||
)
|
||||
}
|
||||
handler.prepare()
|
||||
faintDialog?.addButton(R.string.watch_ad_to_revive, true) { _, _ ->
|
||||
faintDialog = null
|
||||
handler.show()
|
||||
}
|
||||
}
|
||||
soundManager.loadAndPlayAudio(SoundManager.SoundDeath)
|
||||
this.faintDialog?.enqueue()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
|
|||
import com.habitrpg.android.habitica.data.SocialRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.databinding.DrawerMainBinding
|
||||
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
||||
import com.habitrpg.android.habitica.extensions.getRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.getThemeColor
|
||||
|
|
@ -49,16 +50,18 @@ import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
|||
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class NavigationDrawerFragment : DialogFragment() {
|
||||
|
||||
|
|
@ -227,11 +230,9 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
|
||||
subscriptions?.add(
|
||||
Flowable.combineLatest(
|
||||
contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems(),
|
||||
{ state, items ->
|
||||
return@combineLatest Pair(state, items)
|
||||
}
|
||||
).subscribe(
|
||||
contentRepository.getWorldState(), inventoryRepository.getAvailableLimitedItems()) { state, items ->
|
||||
return@combineLatest Pair(state, items)
|
||||
}.subscribe(
|
||||
{ pair ->
|
||||
val gearEvent = pair.first.events.firstOrNull { it.gear }
|
||||
createUpdatingJob("seasonal", {
|
||||
|
|
@ -675,8 +676,8 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
createUpdatingJob(activePromo.promoType.name, {
|
||||
activePromo.isActive
|
||||
}, {
|
||||
val diff = activePromo.endDate.time - Date().time
|
||||
if (diff < (Duration.hours(1).inWholeMilliseconds)) Duration.seconds(1) else Duration.minutes(1)
|
||||
val diff = (activePromo.endDate.time - Date().time).toDuration(DurationUnit.SECONDS)
|
||||
1.toDuration(diff.getMinuteOrSeconds())
|
||||
}) {
|
||||
if (activePromo.isActive) {
|
||||
promotedItem.subtitle = context?.getString(R.string.sale_ends_in, activePromo.endDate.getShortRemainingString())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.habitrpg.android.habitica.ui.views.ads
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.databinding.AdButtonBinding
|
||||
import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
|
||||
import com.habitrpg.android.habitica.extensions.getShortRemainingString
|
||||
import com.habitrpg.android.habitica.extensions.layoutInflater
|
||||
import com.habitrpg.android.habitica.helpers.AdHandler
|
||||
import com.habitrpg.android.habitica.helpers.AdType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class AdButton @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : LinearLayout(context, attrs) {
|
||||
private var updateJob: Job? = null
|
||||
private var nextAdDate: Date? = null
|
||||
private val binding = AdButtonBinding.inflate(context.layoutInflater, this)
|
||||
|
||||
var text: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
updateViews()
|
||||
}
|
||||
|
||||
var isAvailable: Boolean = true
|
||||
set(value) {
|
||||
field = value
|
||||
updateViews()
|
||||
}
|
||||
|
||||
init {
|
||||
context.theme?.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.AdButton,
|
||||
0, 0
|
||||
)?.let { attributes ->
|
||||
text = attributes.getString(R.styleable.AdButton_text) ?: ""
|
||||
binding.currencyView.currency = attributes.getString(R.styleable.AdButton_currency)
|
||||
}
|
||||
binding.textView.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
binding.currencyView.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
binding.currencyView.value = 0.0
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
|
||||
private fun updateViews() {
|
||||
if (isAvailable) {
|
||||
binding.textView.text = text
|
||||
binding.textView.alpha = 1.0f
|
||||
binding.currencyView.visibility = View.VISIBLE
|
||||
setBackgroundResource(R.drawable.ad_button_background)
|
||||
} else {
|
||||
binding.textView.text = context.getString(R.string.available_in, nextAdDate?.getShortRemainingString() ?: "")
|
||||
binding.textView.alpha = 0.75f
|
||||
binding.currencyView.visibility = View.GONE
|
||||
setBackgroundResource(R.drawable.ad_button_background_disabled)
|
||||
}
|
||||
isEnabled = isAvailable
|
||||
}
|
||||
|
||||
fun updateForAdType(type: AdType, lifecycleScope: LifecycleCoroutineScope) {
|
||||
if (updateJob?.isActive == true) {
|
||||
updateJob?.cancel()
|
||||
}
|
||||
nextAdDate = AdHandler.nextAdAllowedDate(type)
|
||||
if (nextAdDate?.after(Date()) == true) {
|
||||
updateJob = lifecycleScope.launch(Dispatchers.Main) {
|
||||
while (nextAdDate?.after(Date()) == true) {
|
||||
val remaining = ((nextAdDate?.time ?: 0L) - Date().time).toDuration(DurationUnit.MILLISECONDS)
|
||||
isAvailable = remaining.isNegative()
|
||||
updateViews()
|
||||
delay(1.toDuration(remaining.getMinuteOrSeconds()))
|
||||
}
|
||||
isAvailable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -359,7 +359,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
|
|||
return
|
||||
} else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
|
||||
observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble(), quantity).map { buyResponse ->
|
||||
if (shopItem.key == "armoire") {
|
||||
if (shopItem.key == "armoire" && configManager.enableNewArmoire()) {
|
||||
MainNavigationController.navigate(R.id.armoireActivity, ArmoireActivityDirections.openArmoireActivity(buyResponse.armoire["type"] ?: "",
|
||||
buyResponse.armoire["dropText"] ?: "",
|
||||
buyResponse.armoire["dropKey"] ?: "").arguments)
|
||||
|
|
|
|||
Loading…
Reference in a new issue