Implement secret feature

This commit is contained in:
Phillip Thelen 2021-03-12 13:20:58 +01:00
parent 07dfdd3f36
commit abc930d217
10 changed files with 108 additions and 30 deletions

View file

@ -150,8 +150,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 2868
versionName "3.2.1"
versionCode 2871
versionName "3.2.2"
}
buildFeatures {

View file

@ -5,6 +5,7 @@ import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.data.local.ContentLocalRepository
import com.habitrpg.android.habitica.helpers.AprilFoolsHandler
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.SpecialItem
@ -43,6 +44,11 @@ abstract class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository
lastWorldStateSync = now
apiClient.worldState.doOnNext {
localRepository.saveWorldState(it)
for (event in it.events) {
if (event.aprilFools != null && event.isCurrentlyActive) {
AprilFoolsHandler.handle(event.aprilFools)
}
}
}
} else {
Flowable.empty()

View file

@ -0,0 +1,19 @@
package com.habitrpg.android.habitica.helpers
import com.habitrpg.android.habitica.helpers.postProcessors.InvertPostProcessor
import com.habitrpg.android.habitica.ui.AvatarView
class AprilFoolsHandler {
companion object {
fun handle(name: String?) {
when(name) {
"invert" -> invertFools()
}
}
private fun invertFools() {
AvatarView.postProcessors[AvatarView.LayerType.PET] = { InvertPostProcessor() }
}
}
}

View file

@ -0,0 +1,41 @@
package com.habitrpg.android.habitica.helpers.postProcessors
import android.graphics.*
import com.facebook.cache.common.CacheKey
import com.facebook.cache.common.SimpleCacheKey
import com.facebook.imagepipeline.request.BasePostprocessor
class InvertPostProcessor: BasePostprocessor() {
override fun getName(): String {
return "invertPostProcessor"
}
override fun process(bitmap: Bitmap) {
val canvas = Canvas(bitmap)
val paint = Paint()
val matrixGrayscale = ColorMatrix()
matrixGrayscale.setSaturation(0f)
val matrixInvert = ColorMatrix()
matrixInvert.set(floatArrayOf(
-1.0f, 0.0f, 0.0f, 0.0f, 255.0f,
0.0f, -1.0f, 0.0f, 0.0f, 255.0f,
0.0f, 0.0f, -1.0f, 0.0f, 255.0f,
0.0f, 0.0f, 0.0f, 1.0f, 0.0f
))
matrixInvert.preConcat(matrixGrayscale)
val filter = ColorMatrixColorFilter(matrixInvert)
paint.colorFilter = filter
canvas.drawBitmap(bitmap, 0f, 0f, paint)
}
override fun getPostprocessorCacheKey(): CacheKey {
return SimpleCacheKey("0")
}
}

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.models
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.models.inventory.QuestProgress
import com.habitrpg.android.habitica.models.inventory.QuestRageStrike
import io.realm.RealmList
@ -16,5 +17,6 @@ open class WorldState: RealmObject() {
var npcImageSuffix: String? = null
var currentEvent: WorldStateEvent? = null
@SerializedName("currentEventList")
var events: RealmList<WorldStateEvent> = RealmList()
}

View file

@ -7,6 +7,12 @@ import io.realm.annotations.PrimaryKey
import java.util.*
open class WorldStateEvent: RealmObject(), BaseObject {
val isCurrentlyActive: Boolean
get() {
val now = Date()
return (start?.before(now) == true) && (end?.after(now) == true)
}
@PrimaryKey
@SerializedName("event")
var eventKey: String? = null

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.*
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
@ -15,6 +16,8 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
import com.facebook.drawee.view.DraweeHolder
import com.facebook.drawee.view.MultiDraweeHolder
import com.facebook.imagepipeline.image.ImageInfo
import com.facebook.imagepipeline.request.BasePostprocessor
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.AppConfigManager
@ -60,7 +63,6 @@ class AvatarView : View {
}
private var lastSubstitutionCheck: Date? = null
private val originalRect: Rect
get() = if (showMount || showPet) FULL_HERO_RECT else if (showBackground) COMPACT_HERO_RECT else HERO_ONLY_RECT
@ -132,8 +134,14 @@ class AvatarView : View {
draweeHolder.topLevelDrawable?.callback = this
multiDraweeHolder.add(draweeHolder)
val uri = Uri.parse(IMAGE_URI_ROOT + DataBindingUtils.getFullFilename(layerName, null))
var request = ImageRequestBuilder.newBuilderWithSource(uri)
postProcessors[layerKey]?.let {
request = request.setPostprocessor(it())
}
val controller = Fresco.newDraweeControllerBuilder()
.setUri(IMAGE_URI_ROOT + DataBindingUtils.getFullFilename(layerName, null))
.setImageRequest(request.build())
.setControllerListener(object : BaseControllerListener<ImageInfo>() {
override fun onFinalImageSet(
id: String?,
@ -252,6 +260,7 @@ class AvatarView : View {
hasVisualBuffs = true
}
val hair = prefs.hair
if (!hasVisualBuffs) {
if (!TextUtils.isEmpty(prefs.chair)) {
layerMap[LayerType.CHAIR] = prefs.chair
@ -288,7 +297,6 @@ class AvatarView : View {
layerMap[LayerType.SHIRT] = prefs.size + "_shirt_" + prefs.shirt
layerMap[LayerType.HEAD_0] = "head_0"
val hair = prefs.hair
if (hair != null) {
val hairColor = hair.color
@ -304,17 +312,11 @@ class AvatarView : View {
if (hair.isAvailable(hair.beard)) {
layerMap[LayerType.HAIR_BEARD] = "hair_beard_" + hair.beard + "_" + hairColor
}
if (hair.isAvailable(hair.flower)) {
layerMap[LayerType.HAIR_FLOWER] = "hair_flower_" + hair.flower
}
}
} else {
val hair = prefs.hair
}
// Show flower all the time!
if (hair != null && hair.isAvailable(hair.flower)) {
layerMap[LayerType.HAIR_FLOWER] = "hair_flower_" + hair.flower
}
if (hair != null && hair.isAvailable(hair.flower)) {
layerMap[LayerType.HAIR_FLOWER] = "hair_flower_" + hair.flower
}
return layerMap
@ -418,7 +420,7 @@ class AvatarView : View {
val equals = currentLayers != null && currentLayers == newLayerMap
if (!equals) {
if (!equals || postProcessors.isNotEmpty()) {
multiDraweeHolder.clear()
numberLayersInProcess.set(0)
}
@ -530,5 +532,6 @@ class AvatarView : View {
private val COMPACT_HERO_RECT = Rect(0, 0, 114, 114)
private val HERO_ONLY_RECT = Rect(0, 0, 90, 90)
val postProcessors: MutableMap<LayerType, (() -> BasePostprocessor)> = mutableMapOf()
}
}

View file

@ -634,17 +634,18 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
protected fun retrieveUser(forced: Boolean = false) {
if (hostConfig.hasAuthentication()) {
compositeSubscription.add(this.userRepository.retrieveUser(true, forced)
.doOnNext { user1 ->
FirebaseAnalytics.getInstance(this).setUserProperty("has_party", if (user1.party?.id?.isNotEmpty() == true) "true" else "false")
FirebaseAnalytics.getInstance(this).setUserProperty("is_subscribed", if (user1.isSubscribed) "true" else "false")
pushNotificationManager.setUser(user1)
pushNotificationManager.addPushDeviceUsingStoredToken()
}
.flatMap { userRepository.retrieveTeamPlans() }
.flatMap { contentRepository.retrieveContent(this,false) }
.flatMap { contentRepository.retrieveWorldState(this) }
.subscribe({ }, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(
contentRepository.retrieveWorldState(this)
.flatMap { userRepository.retrieveUser(true, forced) }
.doOnNext { user1 ->
FirebaseAnalytics.getInstance(this).setUserProperty("has_party", if (user1.party?.id?.isNotEmpty() == true) "true" else "false")
FirebaseAnalytics.getInstance(this).setUserProperty("is_subscribed", if (user1.isSubscribed) "true" else "false")
pushNotificationManager.setUser(user1)
pushNotificationManager.addPushDeviceUsingStoredToken()
}
.flatMap { userRepository.retrieveTeamPlans() }
.flatMap { contentRepository.retrieveContent(this) }
.subscribe({ }, RxErrorHandler.handleEmptyError()))
}
}

View file

@ -56,9 +56,9 @@ class WorldStateSerialization: JsonDeserializer<WorldState> {
if (event != null) {
state.currentEvent = context?.deserialize(event, WorldStateEvent::class.java)
}
if (json.asJsonObject.has("events")) {
if (json.asJsonObject.has("currentEventList")) {
val events = RealmList<WorldStateEvent>()
for (element in json.asJsonObject.getAsJsonArray("events")) {
for (element in json.asJsonObject.getAsJsonArray("currentEventList")) {
context?.deserialize<WorldStateEvent>(element, WorldStateEvent::class.java)?.let { events.add(it) }
}
state.events = events

View file

@ -14,12 +14,12 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
classpath "io.realm:realm-gradle-plugin:10.3.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.1.0"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
classpath 'com.google.firebase:perf-plugin:1.3.4'
classpath 'com.google.firebase:perf-plugin:1.3.5'
}
}