Display adventure guide status in menu

This commit is contained in:
Phillip Thelen 2020-06-05 11:25:42 +02:00
parent 864101288b
commit 7d60b338e4
38 changed files with 276 additions and 40 deletions

View file

@ -18,7 +18,7 @@ buildscript {
}
dependencies {
classpath 'io.fabric.tools:gradle:1.+'
classpath 'com.android.tools.build:gradle:3.6.2'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath('com.noveogroup.android:check:1.2.5') {
exclude module: 'checkstyle'
exclude module: 'pmd-java'
@ -129,7 +129,7 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.nex3z:flow-layout:1.2.2'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
@ -162,8 +162,8 @@ android {
multiDexEnabled true
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 2412
versionName "2.6.1"
versionCode 2421
versionName "2.6.2"
}
viewBinding {

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="@color/brand_200" />
</shape>
</item>
<item>
<bitmap
android:gravity="left|top"
android:src="@drawable/gold_coins_left" />
</item>
<item>
<bitmap
android:gravity="right|bottom"
android:src="@drawable/gold_coins_right" />
</item>
</layer-list>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="8dp" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="24dp" android:height="80dp" />
<solid android:color="@color/gray_700" />
<corners android:topRightRadius="8dp" android:bottomRightRadius="8dp" />
</shape>

View file

@ -220,8 +220,7 @@
android:layout_height="@dimen/diamond_button_height"
android:text="@string/login_btn_apple"
android:drawableStart="@drawable/apple_icon"
style="@style/LoginButton"
android:visibility="gone"/>
style="@style/LoginButton"/>
<Button
android:id="@+id/forgot_password"

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/adventure_guide_menu_bg"
tools:parentTag="android.widget.FrameLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:background="@drawable/adventure_guide_menu_card_bg">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/Caption1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/onboarding_tasks" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/complete_for_gold"
android:textColor="@color/gray_50"
android:textSize="12sp" />
<TextView
android:id="@+id/count_view"
style="@style/Subheader1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/yellow_5"
tools:text="2/5" />
</LinearLayout>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="4dp"
android:progressTint="@color/yellow_50"
style="@android:style/Widget.ProgressBar.Horizontal"
android:progressBackgroundTint="@color/gray_600"
tools:progress="2"
android:max="5"/>
</LinearLayout>
<FrameLayout
android:layout_width="24dp"
android:layout_height="match_parent"
android:background="@drawable/adventure_guide_menu_card_gray">
<ImageView
android:layout_width="24dp"
android:layout_height="match_parent"
android:background="@drawable/adventure_guide_menu_card_gray"
android:src="@drawable/ic_keyboard_arrow_right"
android:scaleType="center"
android:alpha="0.25"
/>
</FrameLayout>
</LinearLayout>
</merge>

View file

@ -1015,4 +1015,6 @@
<string name="purchase_amount_error">You are unable to buy that amount.</string>
<string name="still_questions">Still have a question?</string>
<string name="task_display">Task list display</string>
<string name="onboarding_tasks">Onboarding Tasks</string>
<string name="complete_for_gold">Complete to earn 100 Gold!</string>
</resources>

View file

@ -112,13 +112,6 @@
android:shouldDisableView="false"
android:summary="@string/SP_APIToken_summary"
android:layout="@layout/preference_child_summary"/>
<ListPreference
android:entries="@array/weekdays"
android:entryValues="@array/weekdayValues"
android:key="@string/pref_first_day_of_the_week_key"
android:layout="@layout/preference_child_summary"
android:summary="@string/pref_first_day_of_the_week_summary"
android:title="@string/pref_first_day_of_the_week_title" />
</PreferenceScreen>
<Preference
@ -167,6 +160,13 @@
android:summary="@string/dailyDueDefaultViewDescription"
android:layout="@layout/preference_child_summary"
/>
<ListPreference
android:entries="@array/weekdays"
android:entryValues="@array/weekdayValues"
android:key="@string/pref_first_day_of_the_week_key"
android:layout="@layout/preference_child_summary"
android:summary="@string/pref_first_day_of_the_week_summary"
android:title="@string/pref_first_day_of_the_week_title" />
</PreferenceCategory>
<PreferenceCategory

View file

@ -1,9 +1,8 @@
package com.habitrpg.android.habitica.helpers
enum class AppTestingLevel(identifier: String) {
STAFF("staff"),
ALPHA("alpha"),
BETA("beta"),
PRODUCTION("production")
}

View file

@ -85,6 +85,10 @@ class AppConfigManager {
return remoteConfig.getBoolean("raiseShops")
}
fun enableTaskDisplayMode(): Boolean {
return remoteConfig.getBoolean("enableTaskDisplayMode")
}
fun taskDisplayMode(context: Context): String {
return if (remoteConfig.getBoolean("enableTaskDisplayMode")) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)

View file

@ -53,6 +53,9 @@ open class User : RealmObject(), Avatar, VersionedObject {
for (test in abTests ?: emptyList<ABTest>()) {
test.userID = id
}
for (achievement in achievements ?: emptyList<UserAchievement>()) {
achievement.userId = id
}
}
@SerializedName("_v")
@ -127,6 +130,7 @@ open class User : RealmObject(), Avatar, VersionedObject {
}
var tags = RealmList<Tag>()
var achievements = RealmList<UserAchievement>()
var questAchievements = RealmList<QuestAchievement>()
set(value) {
field = value
@ -238,4 +242,30 @@ open class User : RealmObject(), Avatar, VersionedObject {
}
return isSubscribed
}
val onboardingAchievements: List<UserAchievement>
get() {
val onboarding = mutableMapOf<String, UserAchievement>()
for (key in ONBOARDING_ACHIEVEMENT_KEYS) {
val achievement = UserAchievement()
achievement.key = key
onboarding[key] = achievement
}
for (achievement in achievements) {
if (achievement.key in ONBOARDING_ACHIEVEMENT_KEYS) {
onboarding[achievement.key ?: ""] = achievement
}
}
return onboarding.values.toList()
}
val hasCompletedOnboarding: Boolean
get() {
val onboarding = onboardingAchievements
return onboarding.count { it.earned } == onboarding.size
}
companion object {
val ONBOARDING_ACHIEVEMENT_KEYS = listOf("createdTask", "completedTask", "hatchedPet", "fedPet", "purchasedEquipment")
}
}

View file

@ -0,0 +1,24 @@
package com.habitrpg.android.habitica.models.user
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class UserAchievement : RealmObject {
@PrimaryKey
var combinedKey: String? = null
var userId: String? = null
set(value) {
field = value
combinedKey = field + key
}
var key: String? = null
set(value) {
field = value
combinedKey = userId + field
}
var earned: Boolean = false
constructor()
}

View file

@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.ui.helpers.bindOptionalView
import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem
import com.habitrpg.android.habitica.ui.viewHolders.GiftOneGetOnePromoMenuView
import com.habitrpg.android.habitica.ui.views.adventureGuide.AdventureGuideMenuBanner
import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoView
import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoViewHolder
import io.reactivex.BackpressureStrategy
@ -78,6 +79,8 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int): Recycle
} else if (getItemViewType(position) == 1) {
(holder as? SectionHeaderViewHolder)?.backgroundTintColor = backgroundTintColor
(holder as? SectionHeaderViewHolder)?.bind(drawerItem)
} else if (getItemViewType(position) == 4) {
drawerItem.user?.let { (holder.itemView as? AdventureGuideMenuBanner)?.updateData(it) }
}
}
@ -111,6 +114,14 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int): Recycle
)
SubscriptionBuyGemsPromoViewHolder(itemView)
}
4 -> {
val itemView = AdventureGuideMenuBanner(parent.context)
itemView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
104.dpToPx(parent.context)
)
SubscriptionBuyGemsPromoViewHolder(itemView)
}
1 -> SectionHeaderViewHolder(parent.inflate(R.layout.drawer_main_section_header))
else -> DrawerItemViewHolder(parent.inflate(R.layout.drawer_main_item))
}

View file

@ -282,6 +282,10 @@ class NavigationDrawerFragment : DialogFragment() {
partyMenuItem?.transitionId = R.id.noPartyFragment
partyMenuItem?.bundle = null
}
val adventureGuideItem = getItemWithIdentifier(SIDEBAR_ADVENTURE_GUIDE)
adventureGuideItem?.isVisible = !user.hasCompletedOnboarding
adventureGuideItem?.user = user
}
override fun onDestroy() {
@ -295,6 +299,9 @@ class NavigationDrawerFragment : DialogFragment() {
private fun initializeMenuItems() {
val items = ArrayList<HabiticaDrawerItem>()
context?.let {context ->
val adventureItem = HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_ADVENTURE_GUIDE)
adventureItem.itemViewType = 4
items.add(adventureItem)
items.add(HabiticaDrawerItem(R.id.tasksFragment, SIDEBAR_TASKS, context.getString(R.string.sidebar_tasks)))
items.add(HabiticaDrawerItem(R.id.skillsFragment, SIDEBAR_SKILLS, context.getString(R.string.sidebar_skills)))
items.add(HabiticaDrawerItem(R.id.statsFragment, SIDEBAR_STATS, context.getString(R.string.sidebar_stats)))
@ -304,17 +311,13 @@ class NavigationDrawerFragment : DialogFragment() {
items.add(HabiticaDrawerItem(R.id.partyFragment, SIDEBAR_PARTY, context.getString(R.string.sidebar_party)))
items.add(HabiticaDrawerItem(R.id.guildsOverviewFragment, SIDEBAR_GUILDS, context.getString(R.string.sidebar_guilds)))
items.add(HabiticaDrawerItem(R.id.challengesOverviewFragment, SIDEBAR_CHALLENGES, context.getString(R.string.sidebar_challenges)))
if (configManager.raiseShops()) {
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), true))
items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market)))
items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop)))
items.add(HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop)))
items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers)))
}
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_shops), true))
items.add(HabiticaDrawerItem(R.id.marketFragment, SIDEBAR_SHOPS_MARKET, context.getString(R.string.market)))
items.add(HabiticaDrawerItem(R.id.questShopFragment, SIDEBAR_SHOPS_QUEST, context.getString(R.string.questShop)))
items.add(HabiticaDrawerItem(R.id.seasonalShopFragment, SIDEBAR_SHOPS_SEASONAL, context.getString(R.string.seasonalShop)))
items.add(HabiticaDrawerItem(R.id.timeTravelersShopFragment, SIDEBAR_SHOPS_TIMETRAVEL, context.getString(R.string.timeTravelers)))
items.add(HabiticaDrawerItem(0, SIDEBAR_INVENTORY, context.getString(R.string.sidebar_section_inventory), true))
if (!configManager.raiseShops()) {
items.add(HabiticaDrawerItem(R.id.shopsFragment, SIDEBAR_SHOPS, context.getString(R.string.sidebar_shops)))
}
items.add(HabiticaDrawerItem(R.id.avatarOverviewFragment, SIDEBAR_AVATAR, context.getString(R.string.sidebar_avatar)))
items.add(HabiticaDrawerItem(R.id.equipmentOverviewFragment, SIDEBAR_EQUIPMENT, context.getString(R.string.sidebar_equipment)))
items.add(HabiticaDrawerItem(R.id.itemsFragment, SIDEBAR_ITEMS, context.getString(R.string.sidebar_items)))
@ -498,6 +501,7 @@ class NavigationDrawerFragment : DialogFragment() {
const val SIDEBAR_SUBSCRIPTION = "subscription"
const val SIDEBAR_SUBSCRIPTION_PROMO = "subscriptionpromo"
const val SIDEBAR_G1G1_PROMO = "g1g1promo"
const val SIDEBAR_ADVENTURE_GUIDE = "adventureguide"
const val SIDEBAR_ABOUT_HEADER = "about_header"
const val SIDEBAR_NEWS = "news"
const val SIDEBAR_HELP = "help"

View file

@ -81,7 +81,11 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
val taskDisplayPreference = findPreference("task_display") as? ListPreference
taskDisplayPreference?.summary = taskDisplayPreference?.entry
if (configManager.enableTaskDisplayMode()) {
taskDisplayPreference?.summary = taskDisplayPreference?.entry
} else {
taskDisplayPreference?.isVisible = false
}
}
override fun onResume() {

View file

@ -77,25 +77,25 @@ class BugFixFragment: BaseMainFragment() {
val deviceName = deviceInfo?.name ?: DeviceName.getDeviceName()
val manufacturer = deviceInfo?.manufacturer ?: Build.MANUFACTURER
var bodyOfEmail = "Device: $manufacturer $deviceName" +
" \nAndroid Version: $version"+
" \nAppVersion: " + getString(R.string.version_info, versionName, versionCode)
" %0AAndroid Version: $version"+
" %0AAppVersion: " + getString(R.string.version_info, versionName, versionCode)
if (appConfigManager.testingLevel().name != AppTestingLevel.PRODUCTION.name) {
bodyOfEmail += " ${appConfigManager.testingLevel().name}"
}
bodyOfEmail += " \nUser ID: $userId"
bodyOfEmail += " %0AUser ID: $userId"
val user = this.user
if (user != null) {
bodyOfEmail += " \nLevel: " + (user.stats?.lvl ?: 0) +
" \nClass: " + (if (user.preferences?.disableClasses == true) "Disabled" else (user.stats?.habitClass ?: "None")) +
" \nIs in Inn: " + (user.preferences?.sleep ?: false) +
" \nUses Costume: " + (user.preferences?.costume ?: false) +
" \nCustom Day Start: " + (user.preferences?.dayStart ?: 0) +
" \nTimezone Offset: " + (user.preferences?.timezoneOffset ?: 0)
bodyOfEmail += " %0ALevel: " + (user.stats?.lvl ?: 0) +
" %0AClass: " + (if (user.preferences?.disableClasses == true) "Disabled" else (user.stats?.habitClass ?: "None")) +
" %0AIs in Inn: " + (user.preferences?.sleep ?: false) +
" %0AUses Costume: " + (user.preferences?.costume ?: false) +
" %0ACustom Day Start: " + (user.preferences?.dayStart ?: 0) +
" %0ATimezone Offset: " + (user.preferences?.timezoneOffset ?: 0)
}
bodyOfEmail += " \nDetails:\n"
bodyOfEmail += " %0ADetails:%0A"
activity?.let {
val emailIntent = Intent(Intent.ACTION_SENDTO)

View file

@ -2,6 +2,7 @@ package com.habitrpg.android.habitica.ui.menu
import android.graphics.drawable.Drawable
import android.os.Bundle
import com.habitrpg.android.habitica.models.user.User
data class HabiticaDrawerItem(var transitionId: Int, val identifier: String, val text: String, val isHeader: Boolean = false) {
constructor(transitionId: Int, identifier: String) : this(transitionId, identifier, "")
@ -15,4 +16,6 @@ data class HabiticaDrawerItem(var transitionId: Int, val identifier: String, val
var showBubble: Boolean = false
var isVisible: Boolean = true
var isEnabled: Boolean = true
var user: User? = null
}

View file

@ -0,0 +1,30 @@
package com.habitrpg.android.habitica.ui.views.adventureGuide
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.AdventureGuideMenuBannerBinding
import com.habitrpg.android.habitica.databinding.EquipmentOverviewItemBinding
import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.models.user.User
class AdventureGuideMenuBanner @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private var binding = AdventureGuideMenuBannerBinding.inflate(context.layoutInflater, this)
init {
background = ContextCompat.getDrawable(context, R.drawable.adventure_guide_menu_bg)
}
fun updateData(user: User) {
val achievements = user.onboardingAchievements
val completed = achievements.count { it.earned }
binding.progressBar.max = achievements.size
binding.progressBar.progress = completed / achievements.size
binding.countView.text = "${completed} / ${achievements.size}"
}
}

View file

@ -120,6 +120,22 @@ class UserDeserializer : JsonDeserializer<User> {
tag.userId = user.id
}
}
if (obj.has("achievements")) {
val achievements = RealmList<UserAchievement>()
for (entry in obj.getAsJsonObject("achievements").entrySet()) {
if (!entry.value.isJsonPrimitive) {
continue
}
val achievement = UserAchievement()
achievement.key = entry.key
achievement.earned = entry.value.asBoolean
achievements.add(achievement)
}
user.achievements = achievements
for (achievement in user.achievements) {
achievement.userId = user.id
}
}
if (obj.has("tasksOrder")) {
user.tasksOrder = context.deserialize(obj.get("tasksOrder"), TasksOrder::class.java)
}

View file

@ -11,7 +11,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.3.3'
classpath "io.realm:realm-gradle-plugin:6.0.2"

View file

@ -2,7 +2,6 @@ android.enableJetifier=true
android.useAndroidX=true
android.enableR8=true
android.debug.obsoleteApi=true
android.enableUnitTestBinaryResources=true
org.gradle.configureondemand=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx6656M

View file

@ -1,6 +1,6 @@
#Sun Feb 02 18:26:34 CET 2020
#Wed Jun 03 15:04:01 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-rc-1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip