Show world boss in menu

This commit is contained in:
Phillip Thelen 2018-02-08 20:27:05 +01:00
parent 27d290d963
commit 52fe568d29
29 changed files with 647 additions and 193 deletions

View file

@ -12,6 +12,7 @@
tools:context="com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment">
<LinearLayout
android:id="@+id/menuHeaderView"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@color/brand_200"
@ -21,6 +22,17 @@
android:gravity="center_vertical"
android:orientation="horizontal">
<com.habitrpg.android.habitica.ui.views.RoundedCornerLayout
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="@dimen/spacing_large">
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatarView"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"/>
</com.habitrpg.android.habitica.ui.views.RoundedCornerLayout>
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="0dp"
@ -47,8 +59,12 @@
android:background="@color/transparent"
android:src="@drawable/menu_settings"
android:layout_marginLeft="20dp"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.social.QuestMenuView
android:id="@+id/questMenuView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/topView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_medium">
<TextView
android:id="@+id/bossNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:textColor="@color/white"
android:textSize="14sp"
android:textStyle="bold"
/>
<TextView
android:id="@+id/typeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/world_boss"
android:layout_gravity="right|center_vertical"
android:textColor="@color/white"
android:textSize="12sp"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.ValueBar
android:id="@+id/healthBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/spacing_medium"
android:paddingRight="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_small"
app:barForegroundColor="@color/red_100"
app:barBackgroundColor="@color/white_15_alpha"/>
</LinearLayout>

View file

@ -7,6 +7,7 @@ import com.habitrpg.android.habitica.models.PurchaseValidationRequest;
import com.habitrpg.android.habitica.models.PurchaseValidationResult;
import com.habitrpg.android.habitica.models.SubscriptionValidationRequest;
import com.habitrpg.android.habitica.models.Tag;
import com.habitrpg.android.habitica.models.WorldState;
import com.habitrpg.android.habitica.models.auth.UserAuth;
import com.habitrpg.android.habitica.models.auth.UserAuthResponse;
import com.habitrpg.android.habitica.models.auth.UserAuthSocial;
@ -355,4 +356,7 @@ public interface ApiService {
@POST("user/allocate-bulk")
Observable<HabitResponse<Stats>> bulkAllocatePoints(@Body Map<String, Map<String, Integer>> stats);
@POST("world-state")
Observable<HabitResponse<WorldState>> getWorldState();
}

View file

@ -40,6 +40,7 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.fragments.GemsPurchaseFragment;
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment;
import com.habitrpg.android.habitica.ui.fragments.NewsFragment;
import com.habitrpg.android.habitica.ui.fragments.StatsFragment;
import com.habitrpg.android.habitica.ui.fragments.SubscriptionFragment;
@ -296,4 +297,6 @@ public interface AppComponent {
void inject(@NotNull PushNotificationsPreferencesFragment pushNotificationsPreferencesFragment);
void inject(WelcomeFragment welcomeFragment);
void inject(@NotNull NavigationDrawerFragment navigationDrawerFragment);
}

View file

@ -9,6 +9,7 @@ import com.habitrpg.android.habitica.models.PurchaseValidationRequest;
import com.habitrpg.android.habitica.models.PurchaseValidationResult;
import com.habitrpg.android.habitica.models.SubscriptionValidationRequest;
import com.habitrpg.android.habitica.models.Tag;
import com.habitrpg.android.habitica.models.WorldState;
import com.habitrpg.android.habitica.models.auth.UserAuthResponse;
import com.habitrpg.android.habitica.models.inventory.Equipment;
import com.habitrpg.android.habitica.models.inventory.Quest;
@ -253,4 +254,6 @@ public interface ApiClient {
Observable<Stats> bulkAllocatePoints(int strength, int intelligence, int constitution, int perception);
Observable<Shop> retrieveMarketGear();
Observable<WorldState> getWorldState();
}

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.data;
import com.habitrpg.android.habitica.models.ContentResult;
import com.habitrpg.android.habitica.models.WorldState;
import rx.Observable;
@ -8,4 +9,6 @@ public interface ContentRepository extends BaseRepository {
Observable<ContentResult> retrieveContent();
Observable<ContentResult> retrieveContent(boolean forced);
Observable<WorldState> retrieveWorldState();
}

View file

@ -28,6 +28,7 @@ import com.habitrpg.android.habitica.models.Skill;
import com.habitrpg.android.habitica.models.SubscriptionValidationRequest;
import com.habitrpg.android.habitica.models.Tag;
import com.habitrpg.android.habitica.models.TutorialStep;
import com.habitrpg.android.habitica.models.WorldState;
import com.habitrpg.android.habitica.models.auth.UserAuth;
import com.habitrpg.android.habitica.models.auth.UserAuthResponse;
import com.habitrpg.android.habitica.models.auth.UserAuthSocial;
@ -197,6 +198,9 @@ public class ApiClientImpl implements Action1<Throwable>, ApiClient {
if (userAgent != null) {
builder = builder.header("user-agent", userAgent);
}
if (!BuildConfig.STAGING_KEY.isEmpty()) {
builder = builder.header("Authorization", "Basic " + BuildConfig.STAGING_KEY);
}
Request request = builder.method(original.method(), original.body())
.build();
lastAPICallURL = original.url().toString();
@ -1011,4 +1015,9 @@ public class ApiClientImpl implements Action1<Throwable>, ApiClient {
public Observable<Shop> retrieveMarketGear() {
return apiService.retrieveMarketGear().compose(configureApiCallObserver());
}
@Override
public Observable<WorldState> getWorldState() {
return apiService.getWorldState().compose(configureApiCallObserver());
}
}

View file

@ -1,34 +0,0 @@
package com.habitrpg.android.habitica.data.implementation;
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.models.ContentResult;
import java.util.Date;
import rx.Observable;
abstract class ContentRepositoryImpl<T extends ContentLocalRepository> extends BaseRepositoryImpl<T> implements ContentRepository {
private Date lastSync = null;
public ContentRepositoryImpl(T localRepository, ApiClient apiClient) {
super(localRepository, apiClient);
}
@Override
public Observable<ContentResult> retrieveContent() {
return retrieveContent(false);
}
@Override
public Observable<ContentResult> retrieveContent(boolean forced) {
if (forced || this.lastSync == null || (new Date().getTime() - this.lastSync.getTime()) > 3600000) {
lastSync = new Date();
return apiClient.getContent().doOnNext(localRepository::saveContent);
} else {
return Observable.just(null);
}
}
}

View file

@ -0,0 +1,40 @@
package com.habitrpg.android.habitica.data.implementation
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.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import java.util.Date
import rx.Observable
import rx.functions.Action1
internal abstract class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository: T, apiClient: ApiClient) : BaseRepositoryImpl<T>(localRepository, apiClient), ContentRepository {
private var lastContentSync = Date()
private var lastWorldStateSync = Date()
override fun retrieveContent(): Observable<ContentResult> {
return retrieveContent(false)
}
override fun retrieveContent(forced: Boolean): Observable<ContentResult> {
return if (forced || Date().time - this.lastContentSync.time > 3600000) {
lastContentSync = Date()
apiClient.content.doOnNext({ localRepository.saveContent(it) })
} else {
Observable.just(null)
}
}
override fun retrieveWorldState(): Observable<WorldState> {
return if (Date().time - this.lastWorldStateSync.time > 3600000) {
lastWorldStateSync = Date()
apiClient.worldState.doOnNext({ localRepository.saveWorldState(it) })
} else {
Observable.just(null)
}
}
}

View file

@ -1,7 +0,0 @@
package com.habitrpg.android.habitica.data.local;
import com.habitrpg.android.habitica.models.ContentResult;
public interface ContentLocalRepository extends BaseLocalRepository {
void saveContent(ContentResult contentResult);
}

View file

@ -0,0 +1,9 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
interface ContentLocalRepository : BaseLocalRepository {
fun saveContent(contentResult: ContentResult)
fun saveWorldState(worldState: WorldState)
}

View file

@ -1,36 +0,0 @@
package com.habitrpg.android.habitica.data.local.implementation;
import com.habitrpg.android.habitica.data.local.ContentLocalRepository;
import com.habitrpg.android.habitica.models.ContentResult;
import io.realm.Realm;
class RealmContentLocalRepository extends RealmBaseLocalRepository implements ContentLocalRepository {
RealmContentLocalRepository(Realm realm) {
super(realm);
}
@Override
public void saveContent(ContentResult result) {
realm.executeTransactionAsync(realm1 -> {
realm1.insertOrUpdate(result.potion);
realm1.insertOrUpdate(result.armoire);
realm1.insertOrUpdate(result.gear.flat);
realm1.insertOrUpdate(result.quests);
realm1.insertOrUpdate(result.eggs);
realm1.insertOrUpdate(result.food);
realm1.insertOrUpdate(result.hatchingPotions);
realm1.insertOrUpdate(result.pets);
realm1.insertOrUpdate(result.mounts);
realm1.insertOrUpdate(result.spells);
realm1.insertOrUpdate(result.appearances);
realm1.insertOrUpdate(result.backgrounds);
realm1.insertOrUpdate(result.faq);
});
}
}

View file

@ -0,0 +1,44 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.ContentLocalRepository
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.social.Group
import io.realm.Realm
internal open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ContentLocalRepository {
override fun saveContent(contentResult: ContentResult) {
realm.executeTransactionAsync { realm1 ->
realm1.insertOrUpdate(contentResult.potion)
realm1.insertOrUpdate(contentResult.armoire)
realm1.insertOrUpdate(contentResult.gear.flat)
realm1.insertOrUpdate(contentResult.quests)
realm1.insertOrUpdate(contentResult.eggs)
realm1.insertOrUpdate(contentResult.food)
realm1.insertOrUpdate(contentResult.hatchingPotions)
realm1.insertOrUpdate(contentResult.pets)
realm1.insertOrUpdate(contentResult.mounts)
realm1.insertOrUpdate(contentResult.spells)
realm1.insertOrUpdate(contentResult.appearances)
realm1.insertOrUpdate(contentResult.backgrounds)
realm1.insertOrUpdate(contentResult.faq)
}
}
override fun saveWorldState(worldState: WorldState) {
val tavern = realm.where(Group::class.java)
.equalTo("id", Group.TAVERN_ID)
.findFirst() ?: Group()
tavern.id = Group.TAVERN_ID
tavern.quest?.active = worldState.worldBossActive
tavern.quest?.key = worldState.worldBossKey
tavern.quest?.progress = worldState.progress
save(tavern)
}
}

View file

@ -0,0 +1,14 @@
package com.habitrpg.android.habitica.models
import com.facebook.internal.Mutable
import com.habitrpg.android.habitica.models.inventory.QuestProgress
import com.habitrpg.android.habitica.models.inventory.QuestRageStrike
class WorldState {
var worldBossKey: String = ""
var worldBossActive: Boolean = false
var progress: QuestProgress? = null
var rageStrikes: MutableList<QuestRageStrike>? = null
}

View file

@ -44,26 +44,26 @@ open class Group : RealmObject() {
var leaderMessage: String? = null
override fun equals(o: Any?): Boolean {
if (this === o) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (o == null || javaClass != o.javaClass) {
if (other == null || javaClass != other.javaClass) {
return false
}
val group = o as Group?
val group = other as Group
return if (id != null) id == group!!.id else group!!.id == null
return id == group.id
}
override fun hashCode(): Int {
return if (id != null) id!!.hashCode() else 0
return id.hashCode()
}
companion object {
val TAVERN_ID = "00000000-0000-4000-A000-000000000000"
const val TAVERN_ID = "00000000-0000-4000-A000-000000000000"
}
val hasActiveQuest: Boolean

View file

@ -309,13 +309,17 @@ public class MainActivity extends BaseActivity implements TutorialView.OnTutoria
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState();
if (drawerToggle != null) {
drawerToggle.syncState();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
if (drawerToggle != null) {
drawerToggle.onConfigurationChanged(newConfig);
}
}
@Override
@ -794,6 +798,7 @@ public class MainActivity extends BaseActivity implements TutorialView.OnTutoria
pushNotificationManager.addPushDeviceUsingStoredToken();
})
.flatMap(user1 -> inventoryRepository.retrieveContent(false))
.flatMap(contentResult -> inventoryRepository.retrieveWorldState())
.subscribe(user1 -> {}, RxErrorHandler.handleEmptyError());
}
}

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.adapter
import android.graphics.PorterDuff
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import android.view.View
@ -11,8 +12,26 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem
import rx.Observable
import rx.subjects.PublishSubject
import android.support.v4.graphics.drawable.DrawableCompat.setTint
import android.graphics.drawable.Drawable
import android.support.v4.graphics.drawable.DrawableCompat
import com.habitrpg.android.habitica.extensions.backgroundCompat
class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var tintColor: Int = tintColor
set(value) {
field = value
notifyDataSetChanged()
}
var backgroundTintColor: Int = backgroundTintColor
set(value) {
field = value
notifyDataSetChanged()
}
class NavigationDrawerAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
internal val items: MutableList<HabiticaDrawerItem> = ArrayList()
var selectedItem: String? = null
@ -48,9 +67,12 @@ class NavigationDrawerAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val drawerItem = getItem(position)
if (getItemViewType(position) == 0) {
(holder as DrawerItemViewHolder?)?.bind(drawerItem, drawerItem.identifier == selectedItem)
holder?.tintColor = tintColor
holder?.backgroundTintColor = backgroundTintColor
holder?.itemView?.setOnClickListener { itemSelectedEvents.onNext(drawerItem.identifier) }
} else {
(holder as SectionHeaderViewHolder?)?.bind(drawerItem)
holder?.backgroundTintColor = backgroundTintColor
}
}
@ -70,6 +92,9 @@ class NavigationDrawerAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class DrawerItemViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
var tintColor: Int = 0
var backgroundTintColor: Int = 0
private val titleTextView: TextView? by bindOptionalView(itemView, R.id.titleTextView)
private val additionalInfoView: TextView? by bindOptionalView(itemView, R.id.additionalInfoView)
@ -78,25 +103,43 @@ class NavigationDrawerAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
if (isSelected) {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.gray_600))
titleTextView?.setTextColor(ContextCompat.getColor(itemView.context, R.color.brand_300))
titleTextView?.setTextColor(tintColor)
} else {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.white))
titleTextView?.setTextColor(ContextCompat.getColor(itemView.context, R.color.gray_50))
}
if (drawerItem.additionalInfo != null) {
additionalInfoView?.visibility = View.VISIBLE
additionalInfoView?.text = drawerItem.additionalInfo
} else {
additionalInfoView?.visibility = View.GONE
val additionalInfoView = this.additionalInfoView
if (additionalInfoView != null) {
if (drawerItem.additionalInfo != null) {
additionalInfoView.visibility = View.VISIBLE
additionalInfoView.text = drawerItem.additionalInfo
val drawable = ContextCompat.getDrawable(itemView.context, R.drawable.pill_bg)
if (drawable != null) {
DrawableCompat.setTint(drawable, backgroundTintColor)
val pL = additionalInfoView.paddingLeft
val pT = additionalInfoView.paddingTop
val pR = additionalInfoView.paddingRight
val pB = additionalInfoView.paddingBottom
additionalInfoView.backgroundCompat = drawable
additionalInfoView.setPadding(pL, pT, pR, pB)
}
} else {
additionalInfoView.visibility = View.GONE
}
}
}
}
class SectionHeaderViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
var backgroundTintColor: Int = 0
fun bind(drawerItem: HabiticaDrawerItem) {
(itemView as TextView).text = drawerItem.text
itemView.setBackgroundColor(backgroundTintColor)
}
}
}

View file

@ -3,19 +3,25 @@ package com.habitrpg.android.habitica.ui.fragments
import android.app.ActionBar
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.content.ContextCompat
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
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.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.activities.AboutActivity
import com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
@ -37,8 +43,9 @@ import com.habitrpg.android.habitica.ui.fragments.tasks.TasksFragment
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
import com.habitrpg.android.habitica.ui.menu.HabiticaDrawerItem
import kotlinx.android.synthetic.main.drawer_main.*
import rx.Subscription
import rx.functions.Action1
import rx.subscriptions.CompositeSubscription
import javax.inject.Inject
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
@ -47,13 +54,22 @@ import rx.functions.Action1
*/
class NavigationDrawerFragment : DialogFragment() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var userRepository: UserRepository
private var drawerLayout: DrawerLayout? = null
private var fragmentContainerView: View? = null
private var mCurrentSelectedPosition = 0
private var mFromSavedInstanceState: Boolean = false
private var adapter: NavigationDrawerAdapter = NavigationDrawerAdapter()
private lateinit var adapter: NavigationDrawerAdapter
private var subscriptions: CompositeSubscription? = null
val isDrawerOpen: Boolean
get() = drawerLayout?.isDrawerOpen(fragmentContainerView!!) ?: false
@ -61,7 +77,48 @@ class NavigationDrawerFragment : DialogFragment() {
private val actionBar: ActionBar?
get() = activity?.actionBar
private var questContent: QuestContent? = null
set(value) {
field = value
updateQuestDisplay()
}
private var quest: Quest? = null
set(value) {
field = value
updateQuestDisplay()
}
private fun updateQuestDisplay() {
val quest = this.quest
val questContent = this.questContent
if (quest == null || questContent == null || !quest.active) {
questMenuView.visibility = View.GONE
context.notNull {
adapter.tintColor = ContextCompat.getColor(it, R.color.brand_300)
adapter.backgroundTintColor = ContextCompat.getColor(it, R.color.brand_200)
}
return
}
questMenuView.visibility = View.VISIBLE
menuHeaderView.setBackgroundColor(questContent.colors?.darkColor ?: 0)
questMenuView.configure(quest)
questMenuView.configure(questContent)
adapter.tintColor = questContent.colors?.lightColor ?: 0
adapter.backgroundTintColor = questContent.colors?.darkColor ?: 0
questMenuView.hideBossArt()
}
override fun onCreate(savedInstanceState: Bundle?) {
val context = context
adapter = if (context != null) {
NavigationDrawerAdapter(ContextCompat.getColor(context, R.color.brand_300), ContextCompat.getColor(context, R.color.brand_200))
} else {
NavigationDrawerAdapter(0, 0)
}
subscriptions = CompositeSubscription()
HabiticaBaseApplication.getComponent().inject(this)
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
@ -75,14 +132,11 @@ class NavigationDrawerFragment : DialogFragment() {
// Indicate that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true)
context?.let {recyclerView.setPadding(0, 0, 0, NavbarUtils.getNavbarHeight(it)) }
context?.notNull {recyclerView.setPadding(0, 0, 0, NavbarUtils.getNavbarHeight(it)) }
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.drawer_main, container, false) as ViewGroup
private var selectionSubscription: Subscription? = null
savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.drawer_main, container, false) as ViewGroup
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -90,14 +144,35 @@ class NavigationDrawerFragment : DialogFragment() {
recyclerView.layoutManager = LinearLayoutManager(context)
initializeMenuItems()
selectionSubscription = adapter.getItemSelectionEvents().subscribe(Action1 {
subscriptions?.add(adapter.getItemSelectionEvents().subscribe(Action1 {
setSelection(it, true)
}, RxErrorHandler.handleEmptyError())
}, RxErrorHandler.handleEmptyError()))
subscriptions?.add(socialRepository.getGroup(Group.TAVERN_ID)
.doOnNext({ quest = it.quest })
.filter { it.hasActiveQuest }
.flatMap { inventoryRepository.getQuestContent(it.quest?.key).first() }
.subscribe(Action1 {
questContent = it
}, RxErrorHandler.handleEmptyError()))
subscriptions?.add(userRepository.getUser().subscribe(Action1 {
setUsername(it.profile.name)
avatarView.setAvatar(it)
}, RxErrorHandler.handleEmptyError()))
messagesButton.setOnClickListener { setSelection(SIDEBAR_INBOX) }
settingsButton.setOnClickListener { setSelection(SIDEBAR_SETTINGS) }
}
override fun onDestroy() {
subscriptions?.clear()
socialRepository.close()
inventoryRepository.close()
userRepository.close()
super.onDestroy()
}
private fun initializeMenuItems() {
val items = ArrayList<HabiticaDrawerItem>()
context.notNull {context ->

View file

@ -109,7 +109,7 @@ class TavernDetailFragment : BaseFragment() {
.doOnNext({ if (!it.hasActiveQuest) worldBossSection.visibility = View.GONE })
.filter { it.hasActiveQuest }
.doOnNext({ questProgressView.progress = it.quest})
.flatMap { inventoryRepository.getQuestContent(it.quest?.key) }
.flatMap { inventoryRepository.getQuestContent(it.quest?.key).first() }
.subscribe(Action1 {
questProgressView.quest = it
worldBossSection.visibility = View.VISIBLE

View file

@ -1,78 +0,0 @@
package com.habitrpg.android.habitica.ui.helpers;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import com.habitrpg.android.habitica.R;
public class NavbarUtils {
private static final int RESOURCE_NOT_FOUND = 0;
@IntRange(from = 0)
public static int getNavbarHeight(@NonNull Context context) {
Resources res = context.getResources();
int navBarIdentifier = res.getIdentifier("navigation_bar_height", "dimen", "android");
return navBarIdentifier != RESOURCE_NOT_FOUND
? res.getDimensionPixelSize(navBarIdentifier) : 0;
}
static boolean shouldDrawBehindNavbar(@NonNull Context context) {
return isPortrait(context)
&& hasSoftKeys(context);
}
private static boolean isPortrait(@NonNull Context context) {
Resources res = context.getResources();
return res.getBoolean(R.bool.bb_bottom_bar_is_portrait_mode);
}
/**
* http://stackoverflow.com/a/14871974
*/
public static boolean hasSoftKeys(@NonNull Context context) {
boolean hasSoftwareKeys = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Display d = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
hasSoftwareKeys = !hasMenuKey && !hasBackKey;
}
return hasSoftwareKeys;
}
public static boolean isBehindNavbar(int[] parentLocation, Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return parentLocation[1] > (size.y - getNavbarHeight(context));
}
}

View file

@ -0,0 +1,78 @@
package com.habitrpg.android.habitica.ui.helpers
import android.content.Context
import android.content.res.Resources
import android.graphics.Point
import android.os.Build
import android.support.annotation.IntRange
import android.util.DisplayMetrics
import android.view.Display
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.ViewConfiguration
import android.view.WindowManager
import com.habitrpg.android.habitica.R
object NavbarUtils {
private const val RESOURCE_NOT_FOUND = 0
@IntRange(from = 0)
fun getNavbarHeight(context: Context): Int {
val res = context.resources
val navBarIdentifier = res.getIdentifier("navigation_bar_height", "dimen", "android")
return if (navBarIdentifier != RESOURCE_NOT_FOUND)
res.getDimensionPixelSize(navBarIdentifier)
else
0
}
internal fun shouldDrawBehindNavbar(context: Context): Boolean {
return isPortrait(context) && hasSoftKeys(context)
}
private fun isPortrait(context: Context): Boolean {
val res = context.resources
return res.getBoolean(R.bool.bb_bottom_bar_is_portrait_mode)
}
/**
* http://stackoverflow.com/a/14871974
*/
fun hasSoftKeys(context: Context): Boolean {
var hasSoftwareKeys = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val d = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
val realDisplayMetrics = DisplayMetrics()
d.getRealMetrics(realDisplayMetrics)
val realHeight = realDisplayMetrics.heightPixels
val realWidth = realDisplayMetrics.widthPixels
val displayMetrics = DisplayMetrics()
d.getMetrics(displayMetrics)
val displayHeight = displayMetrics.heightPixels
val displayWidth = displayMetrics.widthPixels
hasSoftwareKeys = realWidth - displayWidth > 0 || realHeight - displayHeight > 0
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
val hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey()
val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
hasSoftwareKeys = !hasMenuKey && !hasBackKey
}
return hasSoftwareKeys
}
fun isBehindNavbar(parentLocation: IntArray, context: Context): Boolean {
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
val size = Point()
display.getSize(size)
return parentLocation[1] > size.y - getNavbarHeight(context)
}
}

View file

@ -33,11 +33,11 @@ public class HabiticaSnackbar extends BaseTransientBottomBar<HabiticaSnackbar> {
private static HabiticaSnackbar make(@NonNull ViewGroup parent, int duration) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View content = inflater.inflate(R.layout.snackbar_view, parent, false);
if (NavbarUtils.hasSoftKeys(parent.getContext())) {
if (NavbarUtils.INSTANCE.hasSoftKeys(parent.getContext())) {
int[] parentLocation = new int[2];
parent.getLocationInWindow(parentLocation);
if (NavbarUtils.isBehindNavbar(parentLocation, parent.getContext())) {
content.setPadding(0, 0, 0, NavbarUtils.getNavbarHeight(parent.getContext()));
if (NavbarUtils.INSTANCE.isBehindNavbar(parentLocation, parent.getContext())) {
content.setPadding(0, 0, 0, NavbarUtils.INSTANCE.getNavbarHeight(parent.getContext()));
}
}
final ContentViewCallback viewCallback = new ContentViewCallback(content);

View file

@ -0,0 +1,80 @@
package com.habitrpg.android.habitica.ui.views
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.RectF
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.TypedValue
import android.widget.FrameLayout
// https://stackoverflow.com/a/26201117
class RoundedCornerLayout : FrameLayout {
private var maskBitmap: Bitmap? = null
private var paint: Paint? = null
private var maskPaint: Paint? = null
private var cornerRadius: Float = CORNER_RADIUS
constructor(context: Context) : super(context) {
init(context, null, 0)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs, 0)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
init(context, attrs, defStyle)
}
private fun init(context: Context, attrs: AttributeSet?, defStyle: Int) {
val metrics = context.resources.displayMetrics
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics)
paint = Paint(Paint.ANTI_ALIAS_FLAG)
maskPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
maskPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
setWillNotDraw(false)
}
override fun draw(canvas: Canvas) {
val offscreenBitmap = Bitmap.createBitmap(canvas.width, canvas.height, Bitmap.Config.ARGB_8888)
val offscreenCanvas = Canvas(offscreenBitmap)
super.draw(offscreenCanvas)
if (maskBitmap == null) {
maskBitmap = createMask(canvas.width, canvas.height)
}
offscreenCanvas.drawBitmap(maskBitmap, 0f, 0f, maskPaint)
canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint)
}
private fun createMask(width: Int, height: Int): Bitmap {
val mask = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
val canvas = Canvas(mask)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = Color.WHITE
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
canvas.drawRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), cornerRadius, cornerRadius, paint)
return mask
}
companion object {
private const val CORNER_RADIUS = 40.0f
}
}

View file

@ -24,6 +24,24 @@ class ValueBar(context: Context, attrs: AttributeSet) : FrameLayout(context, att
private val barView: View by bindView(R.id.bar)
private val barEmptySpace: View by bindView(R.id.empty_bar_space)
var currentValue: Double = 0.0
set(value) {
field = value
updateBar()
}
var maxValue: Double = 0.0
set(value) {
field = value
updateBar()
}
private fun updateBar() {
val percent = Math.min(1.0, currentValue / maxValue)
this.setBarWeight(percent)
this.setValueText(currentValue.toInt().toString() + "/" + maxValue.toInt())
}
init {
View.inflate(context, R.layout.value_bar, this)
@ -88,10 +106,8 @@ class ValueBar(context: Context, attrs: AttributeSet) : FrameLayout(context, att
}
fun set(value: Double, valueMax: Double) {
val percent = Math.min(1.0, value / valueMax)
this.setBarWeight(percent)
this.setValueText(value.toInt().toString() + "/" + valueMax.toInt())
currentValue = value
maxValue = valueMax
}
companion object {
@ -109,4 +125,9 @@ class ValueBar(context: Context, attrs: AttributeSet) : FrameLayout(context, att
}
}
}
fun setLabelVisibility(visibility: Int) {
valueTextView.visibility = visibility
descriptionTextView.visibility = visibility
}
}

View file

@ -0,0 +1,79 @@
package com.habitrpg.android.habitica.ui.views.social
import android.content.Context
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.bindView
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.ValueBar
class QuestMenuView : LinearLayout {
private val bossNameView: TextView by bindView(R.id.bossNameView)
private val typeTextView: TextView by bindView(R.id.typeTextView)
private val healthBarView: ValueBar by bindView(R.id.healthBarView)
private val topView: LinearLayout by bindView(R.id.topView)
private var questContent: QuestContent? = null
var collapsed = false
set(value) {
field = value
if (field) {
showBossArt()
} else {
hideBossArt()
}
}
constructor(context: Context) : super(context) {
setupView(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
setupView(context)
}
private fun setupView(context: Context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.quest_menu_view, this)
healthBarView.setIcon(HabiticaIconsHelper.imageOfHeartDarkBg())
healthBarView.setLabelVisibility(View.GONE)
}
fun configure(quest: Quest) {
healthBarView.currentValue = quest.progress?.hp ?: 0.0
}
fun configure(questContent: QuestContent) {
this.questContent = questContent
healthBarView.maxValue = questContent.boss.hp.toDouble()
healthBarView.setBackgroundColor(questContent.colors?.darkColor ?: 0)
topView.setBackgroundColor(questContent.colors?.mediumColor ?: 0)
bossNameView.text = questContent.boss.name
}
fun hideBossArt() {
topView.orientation = LinearLayout.HORIZONTAL
bossNameView.gravity = Gravity.LEFT
bossNameView.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1F)
typeTextView.setTextColor(questContent?.colors?.extraLightColor ?: 0)
}
fun showBossArt() {
topView.orientation = LinearLayout.VERTICAL
bossNameView.gravity = Gravity.RIGHT
bossNameView.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
typeTextView.setTextColor(ContextCompat.getColor(context, R.color.white))
}
}

View file

@ -98,8 +98,8 @@ class GroupSerialization : JsonDeserializer<Group>, JsonSerializer<Group> {
group.quest?.participants = newMembers
}
if (questObject.has("extra")) {
val worldDamageObject = questObject.getAsJsonObject("extra")
if (questObject.has("extra") && questObject["extra"].asJsonObject.has("worldDmg")) {
val worldDamageObject = questObject.getAsJsonObject("extra").getAsJsonObject("worldDmg")
worldDamageObject.entrySet().forEach { (key, value) ->
val rageStrike = QuestRageStrike(key, value.asBoolean)
group.quest?.addRageStrike(rageStrike)

View file

@ -0,0 +1,36 @@
package com.habitrpg.android.habitica.utils
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.habitrpg.android.habitica.models.WorldState
import com.habitrpg.android.habitica.models.inventory.QuestProgress
import com.habitrpg.android.habitica.models.inventory.QuestRageStrike
import java.lang.reflect.Type
class WorldStateSerialization: JsonDeserializer<WorldState> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): WorldState {
val worldBossObject = json?.asJsonObject?.get("worldBoss")?.asJsonObject
val state = WorldState()
if (worldBossObject == null) {
return state
}
state.worldBossActive = worldBossObject["active"].asBoolean
state.worldBossKey = worldBossObject["key"].asString
state.progress = context?.deserialize(worldBossObject["progress"], QuestProgress::class.java)
if (worldBossObject.has("extra")) {
val extra = worldBossObject["extra"].asJsonObject
if (extra.has("worldDmg")) {
val worldDmg = extra["worldDmg"].asJsonObject
state.rageStrikes = mutableListOf()
worldDmg.entrySet().forEach { (key, value) ->
val strike = QuestRageStrike(key, value.asBoolean)
state.rageStrikes?.add(strike)
}
}
}
return state
}
}

View file

@ -12,3 +12,5 @@ PORT=80
# Production
BASE_URL=https://habitica.com
STAGING_KEY=

View file

@ -1,2 +1,3 @@
PORT=3000
BASE_URL=http://localhost:3000
STAGING_KEY=