mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-14 18:21:57 +00:00
correctly display contributor gear. Fixes #485
This commit is contained in:
parent
e58cb400b4
commit
27dfb7e342
21 changed files with 779 additions and 1082 deletions
|
|
@ -24,7 +24,7 @@ abstract class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository
|
|||
val now = Date().time
|
||||
return if (forced || now - this.lastContentSync > 3600000) {
|
||||
lastContentSync = now
|
||||
apiClient.content.doOnNext({ localRepository.saveContent(it) })
|
||||
apiClient.getContent().doOnNext({ localRepository.saveContent(it) })
|
||||
} else {
|
||||
Observable.just(null)
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ abstract class ContentRepositoryImpl<T : ContentLocalRepository>(localRepository
|
|||
val now = Date().time
|
||||
return if (now - this.lastWorldStateSync > 3600000) {
|
||||
lastWorldStateSync = now
|
||||
apiClient.worldState.doOnNext({ localRepository.saveWorldState(it) })
|
||||
apiClient.getWorldState().doOnNext({ localRepository.saveWorldState(it) })
|
||||
} else {
|
||||
Observable.just(null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ class InventoryRepositoryImpl(localRepository: InventoryLocalRepository, apiClie
|
|||
override fun togglePinnedItem(item: ShopItem): Observable<List<ShopItem>> {
|
||||
return if (!item.isValid) {
|
||||
Observable.just(null)
|
||||
} else apiClient.togglePinnedItem(item.pinType, item.path)
|
||||
} else apiClient.togglePinnedItem(item.pinType ?: "", item.path ?: "")
|
||||
.flatMap { retrieveInAppRewards() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
|
|||
override fun flagMessage(chatMessage: ChatMessage): Observable<Void> {
|
||||
return if (chatMessage.id == "") {
|
||||
Observable.just(null)
|
||||
} else apiClient.flagMessage(chatMessage.groupId, chatMessage.id)
|
||||
} else apiClient.flagMessage(chatMessage.groupId ?: "", chatMessage.id)
|
||||
}
|
||||
|
||||
override fun likeMessage(chatMessage: ChatMessage): Observable<ChatMessage> {
|
||||
|
|
@ -52,12 +52,12 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
|
|||
}
|
||||
val liked = chatMessage.userLikesMessage(userId)
|
||||
localRepository.likeMessage(chatMessage, userId, !liked)
|
||||
return apiClient.likeMessage(chatMessage.groupId, chatMessage.id)
|
||||
return apiClient.likeMessage(chatMessage.groupId ?: "", chatMessage.id)
|
||||
.doOnError { localRepository.likeMessage(chatMessage, userId, liked) }
|
||||
}
|
||||
|
||||
override fun deleteMessage(chatMessage: ChatMessage): Observable<Void> {
|
||||
return apiClient.deleteMessage(chatMessage.groupId, chatMessage.id)
|
||||
return apiClient.deleteMessage(chatMessage.groupId ?: "", chatMessage.id)
|
||||
.doOnNext { localRepository.deleteMessage(chatMessage.id) }
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
|
|||
|
||||
override fun inviteToGroup(id: String, inviteData: Map<String, Any>): Observable<List<String>> = apiClient.inviteToGroup(id, inviteData)
|
||||
|
||||
override fun getUserChallenges(): Observable<List<Challenge>> = apiClient.userChallenges
|
||||
override fun getUserChallenges(): Observable<List<Challenge>> = apiClient.getUserChallenges()
|
||||
|
||||
override fun getMember(userId: String?): Observable<Member> {
|
||||
return if (userId == null) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
|
|||
}
|
||||
|
||||
override fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Observable<TaskList> {
|
||||
return this.apiClient.tasks
|
||||
return this.apiClient.getTasks()
|
||||
.doOnNext { res -> this.localRepository.saveTasks(userId, tasksOrder, res) }
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +58,15 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
|
|||
|
||||
override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean): Observable<TaskScoringResult?> {
|
||||
val now = Date().time
|
||||
val id = task.id
|
||||
if (lastTaskAction > now - 500 && !force) {
|
||||
return Observable.just(null)
|
||||
}
|
||||
if (id == null) {
|
||||
return Observable.just(null)
|
||||
}
|
||||
lastTaskAction = now
|
||||
return this.apiClient.postTaskDirection(task.id, (if (up) TaskDirection.up else TaskDirection.down).toString())
|
||||
return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.up else TaskDirection.down).toString())
|
||||
.map { res ->
|
||||
// save local task changes
|
||||
val result = TaskScoringResult()
|
||||
|
|
@ -152,8 +156,12 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
|
|||
return Observable.just(task)
|
||||
}
|
||||
lastTaskAction = now
|
||||
return localRepository.getTaskCopy(task.id ?: "").first()
|
||||
.flatMap { task1 -> apiClient.updateTask(task1.id, task1) }
|
||||
val id = task.id
|
||||
if (id == null) {
|
||||
return Observable.just(task)
|
||||
}
|
||||
return localRepository.getTaskCopy(id).first()
|
||||
.flatMap { task1 -> apiClient.updateTask(id, task1) }
|
||||
.map { task1 ->
|
||||
task1.position = task.position
|
||||
task1
|
||||
|
|
@ -193,7 +201,7 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
|
|||
.first()
|
||||
.flatMap { task ->
|
||||
if (task.isValid) {
|
||||
return@flatMap apiClient.postTaskNewPosition(task.id, newPosition)
|
||||
return@flatMap apiClient.postTaskNewPosition(task.id ?: "", newPosition)
|
||||
}
|
||||
return@flatMap Observable.just<List<String>>(ArrayList())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
package com.habitrpg.android.habitica.models.user;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import io.realm.RealmObject;
|
||||
import io.realm.annotations.PrimaryKey;
|
||||
|
||||
public class Outfit extends RealmObject {
|
||||
|
||||
@PrimaryKey
|
||||
private String userId;
|
||||
|
||||
Gear gear;
|
||||
String armor, back, body, head, shield, weapon;
|
||||
@SerializedName("eyewear")
|
||||
String eyeWear;
|
||||
String headAccessory;
|
||||
|
||||
public String getArmor() {
|
||||
return armor;
|
||||
}
|
||||
|
||||
public void setArmor(String armor) {
|
||||
this.armor = armor;
|
||||
}
|
||||
|
||||
public String getBack() {
|
||||
return back;
|
||||
}
|
||||
|
||||
public void setBack(String back) {
|
||||
this.back = back;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getEyeWear() {
|
||||
return eyeWear;
|
||||
}
|
||||
|
||||
public void setEyeWear(String eyeWear) {
|
||||
this.eyeWear = eyeWear;
|
||||
}
|
||||
|
||||
public String getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
public void setHead(String head) {
|
||||
this.head = head;
|
||||
}
|
||||
|
||||
public String getHeadAccessory() {
|
||||
return headAccessory;
|
||||
}
|
||||
|
||||
public void setHeadAccessory(String headAccessory) {
|
||||
this.headAccessory = headAccessory;
|
||||
}
|
||||
|
||||
public String getShield() {
|
||||
return shield;
|
||||
}
|
||||
|
||||
public void setShield(String shield) {
|
||||
this.shield = shield;
|
||||
}
|
||||
|
||||
public String getWeapon() {
|
||||
return weapon;
|
||||
}
|
||||
|
||||
public void setWeapon(String weapon) {
|
||||
this.weapon = weapon;
|
||||
}
|
||||
|
||||
public boolean isAvailable(String outfit) {
|
||||
return !TextUtils.isEmpty(outfit) && !outfit.endsWith("base_0");
|
||||
}
|
||||
|
||||
public void updateWith(Outfit newOutfit) {
|
||||
this.setArmor(newOutfit.getArmor());
|
||||
this.setBack(newOutfit.getBack());
|
||||
this.setBody(newOutfit.getBody());
|
||||
this.setEyeWear(newOutfit.getEyeWear());
|
||||
this.setHead(newOutfit.getHead());
|
||||
this.setHeadAccessory(newOutfit.getHeadAccessory());
|
||||
this.setShield(newOutfit.getShield());
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.habitrpg.android.habitica.models.user
|
||||
|
||||
import android.text.TextUtils
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
open class Outfit : RealmObject() {
|
||||
|
||||
@PrimaryKey
|
||||
var userId: String? = null
|
||||
|
||||
internal var gear: Gear? = null
|
||||
var armor: String = ""
|
||||
var back: String = ""
|
||||
var body: String = ""
|
||||
var head: String = ""
|
||||
var shield: String = ""
|
||||
var weapon: String = ""
|
||||
@SerializedName("eyewear")
|
||||
var eyeWear: String = ""
|
||||
var headAccessory: String = ""
|
||||
|
||||
fun isAvailable(outfit: String): Boolean {
|
||||
return !TextUtils.isEmpty(outfit) && !outfit.endsWith("base_0")
|
||||
}
|
||||
|
||||
fun updateWith(newOutfit: Outfit) {
|
||||
this.armor = newOutfit.armor
|
||||
this.back = newOutfit.back
|
||||
this.body = newOutfit.body
|
||||
this.eyeWear = newOutfit.eyeWear
|
||||
this.head = newOutfit.head
|
||||
this.headAccessory = newOutfit.headAccessory
|
||||
this.shield = newOutfit.shield
|
||||
}
|
||||
}
|
||||
|
|
@ -1,599 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.drawee.view.DraweeHolder;
|
||||
import com.facebook.drawee.view.MultiDraweeHolder;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.habitrpg.android.habitica.R;
|
||||
import com.habitrpg.android.habitica.models.Avatar;
|
||||
import com.habitrpg.android.habitica.models.AvatarPreferences;
|
||||
import com.habitrpg.android.habitica.models.user.Buffs;
|
||||
import com.habitrpg.android.habitica.models.user.Hair;
|
||||
import com.habitrpg.android.habitica.models.user.Outfit;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AvatarView extends View {
|
||||
public static final String IMAGE_URI_ROOT = "https://habitica-assets.s3.amazonaws.com/mobileApp/images/";
|
||||
private static final String TAG = "AvatarView";
|
||||
private static final Map<String, String> FILENAME_MAP;
|
||||
private static final Rect FULL_HERO_RECT = new Rect(0, 0, 140, 147);
|
||||
private static final Rect COMPACT_HERO_RECT = new Rect(0, 0, 114, 114);
|
||||
private static final Rect HERO_ONLY_RECT = new Rect(0, 0, 90, 90);
|
||||
|
||||
static {
|
||||
Map<String, String> tempMap = new HashMap<>();
|
||||
tempMap.put("head_special_1", "ContributorOnly-Equip-CrystalHelmet.gif");
|
||||
tempMap.put("armor_special_1", "ContributorOnly-Equip-CrystalArmor.gif");
|
||||
tempMap.put("weapon_special_critical", "weapon_special_critical.gif");
|
||||
tempMap.put("Pet-Wolf-Cerberus", "Pet-Wolf-Cerberus.gif");
|
||||
FILENAME_MAP = Collections.unmodifiableMap(tempMap);
|
||||
}
|
||||
|
||||
private boolean showBackground = true;
|
||||
private boolean showMount = true;
|
||||
private boolean showPet = true;
|
||||
private boolean showSleeping = true;
|
||||
private boolean hasBackground;
|
||||
private boolean hasMount;
|
||||
private boolean hasPet;
|
||||
private boolean isOrphan;
|
||||
private MultiDraweeHolder<GenericDraweeHierarchy> multiDraweeHolder = new MultiDraweeHolder<>();
|
||||
private Avatar avatar;
|
||||
private RectF avatarRectF;
|
||||
private Matrix matrix = new Matrix();
|
||||
private AtomicInteger numberLayersInProcess = new AtomicInteger(0);
|
||||
private Consumer<Bitmap> avatarImageConsumer;
|
||||
private Bitmap avatarBitmap;
|
||||
private Canvas avatarCanvas;
|
||||
private Map<LayerType, String> currentLayers;
|
||||
|
||||
public AvatarView(Context context) {
|
||||
super(context);
|
||||
init(null, 0);
|
||||
}
|
||||
|
||||
public AvatarView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(attrs, 0);
|
||||
}
|
||||
|
||||
public AvatarView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(attrs, defStyle);
|
||||
}
|
||||
|
||||
public AvatarView(Context context, boolean showBackground, boolean showMount, boolean showPet) {
|
||||
super(context);
|
||||
|
||||
this.showBackground = showBackground;
|
||||
this.showMount = showMount;
|
||||
this.showPet = showPet;
|
||||
isOrphan = true;
|
||||
}
|
||||
|
||||
public void configureView(boolean showBackground, boolean showMount, boolean showPet) {
|
||||
this.showBackground = showBackground;
|
||||
this.showMount = showMount;
|
||||
this.showPet = showPet;
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs, int defStyle) {
|
||||
// Load attributes
|
||||
final TypedArray a = getContext().obtainStyledAttributes(
|
||||
attrs, R.styleable.AvatarView, defStyle, 0);
|
||||
|
||||
try {
|
||||
showBackground = a.getBoolean(R.styleable.AvatarView_showBackground, true);
|
||||
showMount = a.getBoolean(R.styleable.AvatarView_showMount, true);
|
||||
showPet = a.getBoolean(R.styleable.AvatarView_showPet, true);
|
||||
showSleeping = a.getBoolean(R.styleable.AvatarView_showSleeping, true);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void showLayers(@NonNull Map<LayerType, String> layerMap) {
|
||||
if (multiDraweeHolder.size() > 0) return;
|
||||
int i = 0;
|
||||
|
||||
currentLayers = layerMap;
|
||||
|
||||
numberLayersInProcess.set(layerMap.size());
|
||||
|
||||
for (Map.Entry<LayerType, String> entry : layerMap.entrySet()) {
|
||||
final LayerType layerKey = entry.getKey();
|
||||
final String layerName = entry.getValue();
|
||||
final int layerNumber = i++;
|
||||
|
||||
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
|
||||
.setFadeDuration(0)
|
||||
.build();
|
||||
|
||||
DraweeHolder<GenericDraweeHierarchy> draweeHolder = DraweeHolder.create(hierarchy, getContext());
|
||||
draweeHolder.getTopLevelDrawable().setCallback(this);
|
||||
multiDraweeHolder.add(draweeHolder);
|
||||
|
||||
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setUri(IMAGE_URI_ROOT + getFileName(layerName))
|
||||
.setControllerListener(new BaseControllerListener<ImageInfo>() {
|
||||
@Override
|
||||
public void onFinalImageSet(
|
||||
String id,
|
||||
ImageInfo imageInfo,
|
||||
Animatable anim) {
|
||||
if (imageInfo != null) {
|
||||
if (multiDraweeHolder.size() > layerNumber) {
|
||||
multiDraweeHolder.get(layerNumber).getTopLevelDrawable().setBounds(getLayerBounds(layerKey, layerName, imageInfo));
|
||||
}
|
||||
onLayerComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String id, Throwable throwable) {
|
||||
Log.e(TAG, "Error loading layer: " + layerName, throwable);
|
||||
onLayerComplete();
|
||||
}
|
||||
})
|
||||
.setAutoPlayAnimations(!isOrphan)
|
||||
.build();
|
||||
draweeHolder.setController(controller);
|
||||
}
|
||||
|
||||
if (isOrphan) multiDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
private Map<LayerType, String> getLayerMap() {
|
||||
assert avatar != null;
|
||||
return getLayerMap(avatar, true);
|
||||
}
|
||||
|
||||
private Map<LayerType, String> getLayerMap(@NonNull Avatar avatar, boolean resetHasAttributes) {
|
||||
EnumMap<LayerType, String> layerMap = getAvatarLayerMap(avatar);
|
||||
|
||||
if (resetHasAttributes) hasBackground = hasMount = hasPet = false;
|
||||
|
||||
String mountName = avatar.getCurrentMount();
|
||||
if (showMount && !TextUtils.isEmpty(mountName)) {
|
||||
layerMap.put(LayerType.MOUNT_BODY, "Mount_Body_" + mountName);
|
||||
layerMap.put(LayerType.MOUNT_HEAD, "Mount_Head_" + mountName);
|
||||
if (resetHasAttributes) hasMount = true;
|
||||
}
|
||||
|
||||
String petName = avatar.getCurrentPet();
|
||||
if (showPet && !TextUtils.isEmpty(petName)) {
|
||||
layerMap.put(LayerType.PET, "Pet-" + petName);
|
||||
if (resetHasAttributes) hasPet = true;
|
||||
}
|
||||
|
||||
String backgroundName = avatar.getBackground();
|
||||
if (showBackground && !TextUtils.isEmpty(backgroundName)) {
|
||||
layerMap.put(LayerType.BACKGROUND, "background_" + backgroundName);
|
||||
if (resetHasAttributes) hasBackground = true;
|
||||
}
|
||||
|
||||
if (showSleeping && avatar.getSleep()) {
|
||||
layerMap.put(AvatarView.LayerType.ZZZ, "zzz");
|
||||
}
|
||||
|
||||
return layerMap;
|
||||
}
|
||||
|
||||
public EnumMap<AvatarView.LayerType, String> getAvatarLayerMap(Avatar avatar) {
|
||||
EnumMap<AvatarView.LayerType, String> layerMap = new EnumMap<>(AvatarView.LayerType.class);
|
||||
|
||||
if (!avatar.isValid()) {
|
||||
return layerMap;
|
||||
}
|
||||
|
||||
AvatarPreferences prefs = avatar.getPreferences();
|
||||
if (prefs == null) {
|
||||
return layerMap;
|
||||
}
|
||||
Outfit outfit;
|
||||
if (prefs.getCostume()) {
|
||||
outfit = avatar.getCostume();
|
||||
} else {
|
||||
outfit = avatar.getEquipped();
|
||||
}
|
||||
|
||||
boolean hasVisualBuffs = false;
|
||||
|
||||
if (avatar.getStats() != null && avatar.getStats().getBuffs() != null) {
|
||||
Buffs buffs = avatar.getStats().getBuffs();
|
||||
|
||||
if (buffs.getSnowball()) {
|
||||
layerMap.put(AvatarView.LayerType.VISUAL_BUFF, "snowman");
|
||||
hasVisualBuffs = true;
|
||||
}
|
||||
|
||||
if (buffs.getSeafoam()) {
|
||||
layerMap.put(AvatarView.LayerType.VISUAL_BUFF, "seafoam_star");
|
||||
hasVisualBuffs = true;
|
||||
}
|
||||
|
||||
if (buffs.getShinySeed()) {
|
||||
layerMap.put(AvatarView.LayerType.VISUAL_BUFF, "avatar_floral_" + avatar.getStats().getHabitClass());
|
||||
hasVisualBuffs = true;
|
||||
}
|
||||
|
||||
if (buffs.getSpookySparkles()) {
|
||||
layerMap.put(AvatarView.LayerType.VISUAL_BUFF, "ghost");
|
||||
hasVisualBuffs = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasVisualBuffs) {
|
||||
if (!TextUtils.isEmpty(prefs.getChair())) {
|
||||
layerMap.put(AvatarView.LayerType.CHAIR, prefs.getChair());
|
||||
}
|
||||
|
||||
if (outfit != null) {
|
||||
if (!TextUtils.isEmpty(outfit.getBack()) && !"back_base_0".equals(outfit.getBack())) {
|
||||
layerMap.put(AvatarView.LayerType.BACK, outfit.getBack());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getArmor())) {
|
||||
layerMap.put(AvatarView.LayerType.ARMOR, prefs.getSize() + "_" + outfit.getArmor());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getBody())) {
|
||||
layerMap.put(AvatarView.LayerType.BODY, outfit.getBody());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getEyeWear())) {
|
||||
layerMap.put(AvatarView.LayerType.EYEWEAR, outfit.getEyeWear());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getHead())) {
|
||||
layerMap.put(AvatarView.LayerType.HEAD, outfit.getHead());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getHeadAccessory())) {
|
||||
layerMap.put(AvatarView.LayerType.HEAD_ACCESSORY, outfit.getHeadAccessory());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getShield())) {
|
||||
layerMap.put(AvatarView.LayerType.SHIELD, outfit.getShield());
|
||||
}
|
||||
if (outfit.isAvailable(outfit.getWeapon())) {
|
||||
layerMap.put(AvatarView.LayerType.WEAPON, outfit.getWeapon());
|
||||
}
|
||||
}
|
||||
|
||||
layerMap.put(AvatarView.LayerType.SKIN, "skin_" + prefs.getSkin() + ((prefs.getSleep()) ? "_sleep" : ""));
|
||||
layerMap.put(AvatarView.LayerType.SHIRT, prefs.getSize() + "_shirt_" + prefs.getShirt());
|
||||
layerMap.put(AvatarView.LayerType.HEAD_0, "head_0");
|
||||
|
||||
Hair hair = prefs.getHair();
|
||||
if (hair != null) {
|
||||
String hairColor = hair.getColor();
|
||||
|
||||
if (hair.isAvailable(hair.getBase())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_BASE, "hair_base_" + hair.getBase() + "_" + hairColor);
|
||||
}
|
||||
if (hair.isAvailable(hair.getBangs())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_BANGS, "hair_bangs_" + hair.getBangs() + "_" + hairColor);
|
||||
}
|
||||
if (hair.isAvailable(hair.getMustache())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_MUSTACHE, "hair_mustache_" + hair.getMustache() + "_" + hairColor);
|
||||
}
|
||||
if (hair.isAvailable(hair.getBeard())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_BEARD, "hair_beard_" + hair.getBeard() + "_" + hairColor);
|
||||
}
|
||||
if (hair.isAvailable(hair.getFlower())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_FLOWER, "hair_flower_" + hair.getFlower());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Hair hair = prefs.getHair();
|
||||
|
||||
// Show flower all the time!
|
||||
if (hair != null && hair.isAvailable(hair.getFlower())) {
|
||||
layerMap.put(AvatarView.LayerType.HAIR_FLOWER, "hair_flower_" + hair.getFlower());
|
||||
}
|
||||
}
|
||||
|
||||
return layerMap;
|
||||
}
|
||||
|
||||
private Rect getLayerBounds(@NonNull LayerType layerType, @NonNull String layerName, @NonNull ImageInfo layerImageInfo) {
|
||||
PointF offset = null;
|
||||
Rect bounds = new Rect(0, 0, layerImageInfo.getWidth(), layerImageInfo.getHeight());
|
||||
RectF boundsF = new RectF(bounds);
|
||||
|
||||
// lookup layer specific offset
|
||||
switch (layerName) {
|
||||
case "weapon_special_critical":
|
||||
if (showMount || showPet) {
|
||||
// full hero box
|
||||
if (hasMount) {
|
||||
offset = new PointF(13.0f, 12.0f);
|
||||
} else if (hasPet) {
|
||||
offset = new PointF(13.0f, 24.5f + 12.0f);
|
||||
} else {
|
||||
offset = new PointF(13.0f, 28.0f + 12.0f);
|
||||
}
|
||||
} else if (showBackground) {
|
||||
// compact hero box
|
||||
offset = new PointF(-12.0f, 18.0f + 12.0f);
|
||||
} else {
|
||||
// hero only box
|
||||
offset = new PointF(-12.0f, 12.0f);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise lookup default layer type based offset
|
||||
if (offset == null) {
|
||||
switch (layerType) {
|
||||
case BACKGROUND:
|
||||
if (!(showMount || showPet)) {
|
||||
offset = new PointF(-25.0f, 0.0f); // compact hero box
|
||||
}
|
||||
break;
|
||||
case MOUNT_BODY:
|
||||
case MOUNT_HEAD:
|
||||
offset = new PointF(25.0f, 18.0f); // full hero box
|
||||
break;
|
||||
case CHAIR:
|
||||
case BACK:
|
||||
case SKIN:
|
||||
case SHIRT:
|
||||
case ARMOR:
|
||||
case BODY:
|
||||
case HEAD_0:
|
||||
case HAIR_BASE:
|
||||
case HAIR_BANGS:
|
||||
case HAIR_MUSTACHE:
|
||||
case HAIR_BEARD:
|
||||
case EYEWEAR:
|
||||
case VISUAL_BUFF:
|
||||
case HEAD:
|
||||
case HEAD_ACCESSORY:
|
||||
case HAIR_FLOWER:
|
||||
case SHIELD:
|
||||
case WEAPON:
|
||||
case ZZZ:
|
||||
if (showMount || showPet) {
|
||||
// full hero box
|
||||
if (hasMount) {
|
||||
offset = new PointF(25.0f, 0);
|
||||
} else if (hasPet) {
|
||||
offset = new PointF(25.0f, 24.5f);
|
||||
} else {
|
||||
offset = new PointF(25.0f, 28.0f);
|
||||
}
|
||||
} else if (showBackground) {
|
||||
// compact hero box
|
||||
offset = new PointF(0.0f, 18.0f);
|
||||
}
|
||||
break;
|
||||
case PET:
|
||||
offset = new PointF(0, FULL_HERO_RECT.height() - layerImageInfo.getHeight());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset != null) {
|
||||
Matrix translateMatrix = new Matrix();
|
||||
translateMatrix.setTranslate(offset.x, offset.y);
|
||||
translateMatrix.mapRect(boundsF);
|
||||
}
|
||||
|
||||
// resize bounds to fit and keep original aspect ratio
|
||||
matrix.mapRect(boundsF);
|
||||
boundsF.round(bounds);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private String getFileName(@NonNull String imageName) {
|
||||
if (FILENAME_MAP.containsKey(imageName)) {
|
||||
return FILENAME_MAP.get(imageName);
|
||||
}
|
||||
|
||||
return imageName + ".png";
|
||||
}
|
||||
|
||||
private void onLayerComplete() {
|
||||
if (numberLayersInProcess.decrementAndGet() == 0) {
|
||||
if (avatarImageConsumer != null) {
|
||||
avatarImageConsumer.accept(getAvatarImage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onAvatarImageReady(@NonNull Consumer<Bitmap> consumer) {
|
||||
avatarImageConsumer = consumer;
|
||||
if (multiDraweeHolder.size() > 0 && numberLayersInProcess.get() == 0) {
|
||||
avatarImageConsumer.accept(getAvatarImage());
|
||||
} else {
|
||||
initAvatarRectMatrix();
|
||||
showLayers(getLayerMap());
|
||||
}
|
||||
}
|
||||
|
||||
public void setAvatar(@NonNull Avatar avatar) {
|
||||
Avatar oldUser = this.avatar;
|
||||
this.avatar = avatar;
|
||||
|
||||
if (oldUser != null) {
|
||||
Map<LayerType, String> newLayerMap = getLayerMap(avatar, false);
|
||||
|
||||
boolean equals = currentLayers != null && currentLayers.equals(newLayerMap);
|
||||
|
||||
if (!equals) {
|
||||
multiDraweeHolder.clear();
|
||||
numberLayersInProcess.set(0);
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Rect getOriginalRect() {
|
||||
return (showMount || showPet) ? FULL_HERO_RECT : ((showBackground) ? COMPACT_HERO_RECT : HERO_ONLY_RECT);
|
||||
}
|
||||
|
||||
private Bitmap getAvatarImage() {
|
||||
assert avatar != null;
|
||||
assert avatarRectF != null;
|
||||
Rect canvasRect = new Rect();
|
||||
avatarRectF.round(canvasRect);
|
||||
avatarBitmap = Bitmap.createBitmap(canvasRect.width(), canvasRect.height(), Bitmap.Config.ARGB_8888);
|
||||
avatarCanvas = new Canvas(avatarBitmap);
|
||||
draw(avatarCanvas);
|
||||
|
||||
return avatarBitmap;
|
||||
}
|
||||
|
||||
private void initAvatarRectMatrix() {
|
||||
if (avatarRectF == null) {
|
||||
Rect srcRect = getOriginalRect();
|
||||
|
||||
if (isOrphan) {
|
||||
avatarRectF = new RectF(srcRect);
|
||||
|
||||
// change scale to not be 1:1
|
||||
// a quick fix as fresco AnimatedDrawable/ScaleTypeDrawable
|
||||
// will not translate matrix properly
|
||||
matrix.setScale(1.2f, 1.2f);
|
||||
matrix.mapRect(avatarRectF);
|
||||
} else {
|
||||
// full hero box when showMount and showPet is enabled (140w * 147h)
|
||||
// compact hero box when only showBackground is enabled (114w * 114h)
|
||||
// hero only box when all show settings disabled (90w * 90h)
|
||||
avatarRectF = new RectF(0, 0, getWidth(), getHeight());
|
||||
matrix.setRectToRect(new RectF(srcRect), avatarRectF, Matrix.ScaleToFit.START); // TODO support other ScaleToFit
|
||||
avatarRectF = new RectF(srcRect);
|
||||
matrix.mapRect(avatarRectF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
initAvatarRectMatrix();
|
||||
|
||||
// draw only when user is set
|
||||
if (avatar == null || !avatar.isValid()) return;
|
||||
|
||||
// request image layers if not yet processed
|
||||
if (multiDraweeHolder.size() == 0) {
|
||||
showLayers(getLayerMap());
|
||||
}
|
||||
|
||||
// manually call onAttach/onDetach if view is without parent as they will never be called otherwise
|
||||
if (isOrphan) multiDraweeHolder.onAttach();
|
||||
multiDraweeHolder.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
multiDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTemporaryDetach() {
|
||||
super.onStartTemporaryDetach();
|
||||
multiDraweeHolder.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
multiDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishTemporaryDetach() {
|
||||
super.onFinishTemporaryDetach();
|
||||
multiDraweeHolder.onAttach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return multiDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(Drawable who) {
|
||||
return multiDraweeHolder.verifyDrawable(who) || super.verifyDrawable(who);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(@NonNull Drawable drawable) {
|
||||
invalidate();
|
||||
if (avatarCanvas != null) draw(avatarCanvas);
|
||||
}
|
||||
|
||||
public enum LayerType {
|
||||
BACKGROUND(0),
|
||||
MOUNT_BODY(1),
|
||||
CHAIR(2),
|
||||
BACK(3),
|
||||
SKIN(4),
|
||||
SHIRT(5),
|
||||
ARMOR(6),
|
||||
BODY(7),
|
||||
HEAD_0(8),
|
||||
HAIR_BASE(9),
|
||||
HAIR_BANGS(10),
|
||||
HAIR_MUSTACHE(11),
|
||||
HAIR_BEARD(12),
|
||||
EYEWEAR(13),
|
||||
VISUAL_BUFF(14),
|
||||
HEAD(15),
|
||||
HEAD_ACCESSORY(16),
|
||||
HAIR_FLOWER(17),
|
||||
SHIELD(18),
|
||||
WEAPON(19),
|
||||
MOUNT_HEAD(20),
|
||||
ZZZ(21),
|
||||
PET(22);
|
||||
|
||||
final int order;
|
||||
|
||||
LayerType(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Consumer<T> {
|
||||
void accept(T t);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,540 @@
|
|||
package com.habitrpg.android.habitica.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.facebook.drawee.controller.BaseControllerListener
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy
|
||||
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.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.models.Avatar
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class AvatarView : View {
|
||||
|
||||
private var showBackground = true
|
||||
private var showMount = true
|
||||
private var showPet = true
|
||||
private var showSleeping = true
|
||||
private var hasBackground: Boolean = false
|
||||
private var hasMount: Boolean = false
|
||||
private var hasPet: Boolean = false
|
||||
private var isOrphan: Boolean = false
|
||||
private val multiDraweeHolder = MultiDraweeHolder<GenericDraweeHierarchy>()
|
||||
private var avatar: Avatar? = null
|
||||
private var avatarRectF: RectF? = null
|
||||
private val avatarMatrix = Matrix()
|
||||
private val numberLayersInProcess = AtomicInteger(0)
|
||||
private var avatarImageConsumer: Consumer<Bitmap?>? = null
|
||||
private var avatarBitmap: Bitmap? = null
|
||||
private var avatarCanvas: Canvas? = null
|
||||
private var currentLayers: Map<LayerType, String>? = null
|
||||
|
||||
private val layerMap: Map<LayerType, String>
|
||||
get() {
|
||||
assert(avatar != null)
|
||||
return getLayerMap(avatar!!, true)
|
||||
}
|
||||
|
||||
|
||||
private val originalRect: Rect
|
||||
get() = if (showMount || showPet) FULL_HERO_RECT else if (showBackground) COMPACT_HERO_RECT else HERO_ONLY_RECT
|
||||
|
||||
private val avatarImage: Bitmap?
|
||||
get() {
|
||||
assert(avatar != null)
|
||||
assert(avatarRectF != null)
|
||||
val canvasRect = Rect()
|
||||
avatarRectF?.round(canvasRect)
|
||||
avatarBitmap = Bitmap.createBitmap(canvasRect.width(), canvasRect.height(), Bitmap.Config.ARGB_8888)
|
||||
avatarCanvas = Canvas(avatarBitmap)
|
||||
draw(avatarCanvas)
|
||||
|
||||
return avatarBitmap
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(null, 0)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(attrs, 0)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
|
||||
init(attrs, defStyle)
|
||||
}
|
||||
|
||||
constructor(context: Context, showBackground: Boolean, showMount: Boolean, showPet: Boolean) : super(context) {
|
||||
|
||||
this.showBackground = showBackground
|
||||
this.showMount = showMount
|
||||
this.showPet = showPet
|
||||
isOrphan = true
|
||||
}
|
||||
|
||||
fun configureView(showBackground: Boolean, showMount: Boolean, showPet: Boolean) {
|
||||
this.showBackground = showBackground
|
||||
this.showMount = showMount
|
||||
this.showPet = showPet
|
||||
}
|
||||
|
||||
private fun init(attrs: AttributeSet?, defStyle: Int) {
|
||||
// Load attributes
|
||||
val a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.AvatarView, defStyle, 0)
|
||||
|
||||
try {
|
||||
showBackground = a.getBoolean(R.styleable.AvatarView_showBackground, true)
|
||||
showMount = a.getBoolean(R.styleable.AvatarView_showMount, true)
|
||||
showPet = a.getBoolean(R.styleable.AvatarView_showPet, true)
|
||||
showSleeping = a.getBoolean(R.styleable.AvatarView_showSleeping, true)
|
||||
} finally {
|
||||
a.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLayers(layerMap: Map<LayerType, String>) {
|
||||
if (multiDraweeHolder.size() > 0) return
|
||||
var i = 0
|
||||
|
||||
currentLayers = layerMap
|
||||
|
||||
numberLayersInProcess.set(layerMap.size)
|
||||
|
||||
for ((layerKey, layerName) in layerMap) {
|
||||
val layerNumber = i++
|
||||
|
||||
val hierarchy = GenericDraweeHierarchyBuilder(resources)
|
||||
.setFadeDuration(0)
|
||||
.build()
|
||||
|
||||
val draweeHolder = DraweeHolder.create(hierarchy, context)
|
||||
draweeHolder.topLevelDrawable.callback = this
|
||||
multiDraweeHolder.add(draweeHolder)
|
||||
|
||||
val controller = Fresco.newDraweeControllerBuilder()
|
||||
.setUri(IMAGE_URI_ROOT + getFileName(layerName))
|
||||
.setControllerListener(object : BaseControllerListener<ImageInfo>() {
|
||||
override fun onFinalImageSet(
|
||||
id: String?,
|
||||
imageInfo: ImageInfo?,
|
||||
anim: Animatable?) {
|
||||
if (imageInfo != null) {
|
||||
if (multiDraweeHolder.size() > layerNumber) {
|
||||
multiDraweeHolder.get(layerNumber).topLevelDrawable.bounds = getLayerBounds(layerKey, layerName, imageInfo)
|
||||
}
|
||||
onLayerComplete()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(id: String?, throwable: Throwable?) {
|
||||
Log.e(TAG, "Error loading layer: " + layerName, throwable)
|
||||
onLayerComplete()
|
||||
}
|
||||
})
|
||||
.setAutoPlayAnimations(!isOrphan)
|
||||
.build()
|
||||
draweeHolder.controller = controller
|
||||
}
|
||||
|
||||
if (isOrphan) multiDraweeHolder.onAttach()
|
||||
}
|
||||
|
||||
private fun getLayerMap(avatar: Avatar, resetHasAttributes: Boolean): Map<LayerType, String> {
|
||||
val layerMap = getAvatarLayerMap(avatar)
|
||||
|
||||
if (resetHasAttributes) {
|
||||
hasPet = false
|
||||
hasMount = hasPet
|
||||
hasBackground = hasMount
|
||||
}
|
||||
|
||||
val mountName = avatar.currentMount
|
||||
if (showMount && !TextUtils.isEmpty(mountName)) {
|
||||
layerMap[LayerType.MOUNT_BODY] = "Mount_Body_" + mountName
|
||||
layerMap[LayerType.MOUNT_HEAD] = "Mount_Head_" + mountName
|
||||
if (resetHasAttributes) hasMount = true
|
||||
}
|
||||
|
||||
val petName = avatar.currentPet
|
||||
if (showPet && !TextUtils.isEmpty(petName)) {
|
||||
layerMap[LayerType.PET] = "Pet-" + petName
|
||||
if (resetHasAttributes) hasPet = true
|
||||
}
|
||||
|
||||
val backgroundName = avatar.background
|
||||
if (showBackground && !TextUtils.isEmpty(backgroundName)) {
|
||||
layerMap[LayerType.BACKGROUND] = "background_" + backgroundName
|
||||
if (resetHasAttributes) hasBackground = true
|
||||
}
|
||||
|
||||
if (showSleeping && avatar.sleep) {
|
||||
layerMap[AvatarView.LayerType.ZZZ] = "zzz"
|
||||
}
|
||||
|
||||
return layerMap
|
||||
}
|
||||
|
||||
private fun getAvatarLayerMap(avatar: Avatar): EnumMap<AvatarView.LayerType, String> {
|
||||
val layerMap = EnumMap<AvatarView.LayerType, String>(AvatarView.LayerType::class.java)
|
||||
|
||||
if (!avatar.isValid) {
|
||||
return layerMap
|
||||
}
|
||||
|
||||
val prefs = avatar.preferences ?: return layerMap
|
||||
val outfit = if (prefs.costume) {
|
||||
avatar.costume
|
||||
} else {
|
||||
avatar.equipped
|
||||
}
|
||||
|
||||
var hasVisualBuffs = false
|
||||
|
||||
if (avatar.stats != null && avatar.stats.getBuffs() != null) {
|
||||
val buffs = avatar.stats.getBuffs()
|
||||
|
||||
if (buffs.snowball) {
|
||||
layerMap[AvatarView.LayerType.VISUAL_BUFF] = "snowman"
|
||||
hasVisualBuffs = true
|
||||
}
|
||||
|
||||
if (buffs.seafoam) {
|
||||
layerMap[AvatarView.LayerType.VISUAL_BUFF] = "seafoam_star"
|
||||
hasVisualBuffs = true
|
||||
}
|
||||
|
||||
if (buffs.shinySeed) {
|
||||
layerMap[AvatarView.LayerType.VISUAL_BUFF] = "avatar_floral_" + avatar.stats.getHabitClass()
|
||||
hasVisualBuffs = true
|
||||
}
|
||||
|
||||
if (buffs.spookySparkles) {
|
||||
layerMap[AvatarView.LayerType.VISUAL_BUFF] = "ghost"
|
||||
hasVisualBuffs = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasVisualBuffs) {
|
||||
if (!TextUtils.isEmpty(prefs.chair)) {
|
||||
layerMap[AvatarView.LayerType.CHAIR] = prefs.chair
|
||||
}
|
||||
|
||||
if (outfit != null) {
|
||||
if (!TextUtils.isEmpty(outfit.back) && "back_base_0" != outfit.back) {
|
||||
layerMap[AvatarView.LayerType.BACK] = outfit.back
|
||||
}
|
||||
if (outfit.isAvailable(outfit.armor)) {
|
||||
layerMap[AvatarView.LayerType.ARMOR] = prefs.size + "_" + outfit.armor
|
||||
}
|
||||
if (outfit.isAvailable(outfit.body)) {
|
||||
layerMap[AvatarView.LayerType.BODY] = outfit.body
|
||||
}
|
||||
if (outfit.isAvailable(outfit.eyeWear)) {
|
||||
layerMap[AvatarView.LayerType.EYEWEAR] = outfit.eyeWear
|
||||
}
|
||||
if (outfit.isAvailable(outfit.head)) {
|
||||
layerMap[AvatarView.LayerType.HEAD] = outfit.head
|
||||
}
|
||||
if (outfit.isAvailable(outfit.headAccessory)) {
|
||||
layerMap[AvatarView.LayerType.HEAD_ACCESSORY] = outfit.headAccessory
|
||||
}
|
||||
if (outfit.isAvailable(outfit.shield)) {
|
||||
layerMap[AvatarView.LayerType.SHIELD] = outfit.shield
|
||||
}
|
||||
if (outfit.isAvailable(outfit.weapon)) {
|
||||
layerMap[AvatarView.LayerType.WEAPON] = outfit.weapon
|
||||
}
|
||||
}
|
||||
|
||||
layerMap[AvatarView.LayerType.SKIN] = "skin_" + prefs.skin + if (prefs.sleep) "_sleep" else ""
|
||||
layerMap[AvatarView.LayerType.SHIRT] = prefs.size + "_shirt_" + prefs.shirt
|
||||
layerMap[AvatarView.LayerType.HEAD_0] = "head_0"
|
||||
|
||||
val hair = prefs.hair
|
||||
if (hair != null) {
|
||||
val hairColor = hair.color
|
||||
|
||||
if (hair.isAvailable(hair.base)) {
|
||||
layerMap[AvatarView.LayerType.HAIR_BASE] = "hair_base_" + hair.base + "_" + hairColor
|
||||
}
|
||||
if (hair.isAvailable(hair.bangs)) {
|
||||
layerMap[AvatarView.LayerType.HAIR_BANGS] = "hair_bangs_" + hair.bangs + "_" + hairColor
|
||||
}
|
||||
if (hair.isAvailable(hair.mustache)) {
|
||||
layerMap[AvatarView.LayerType.HAIR_MUSTACHE] = "hair_mustache_" + hair.mustache + "_" + hairColor
|
||||
}
|
||||
if (hair.isAvailable(hair.beard)) {
|
||||
layerMap[AvatarView.LayerType.HAIR_BEARD] = "hair_beard_" + hair.beard + "_" + hairColor
|
||||
}
|
||||
if (hair.isAvailable(hair.flower)) {
|
||||
layerMap[AvatarView.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[AvatarView.LayerType.HAIR_FLOWER] = "hair_flower_" + hair.flower
|
||||
}
|
||||
}
|
||||
|
||||
return layerMap
|
||||
}
|
||||
|
||||
private fun getLayerBounds(layerType: LayerType, layerName: String, layerImageInfo: ImageInfo): Rect {
|
||||
var offset: PointF? = null
|
||||
val bounds = Rect(0, 0, layerImageInfo.width, layerImageInfo.height)
|
||||
val boundsF = RectF(bounds)
|
||||
|
||||
// lookup layer specific offset
|
||||
when (layerName) {
|
||||
"weapon_special_critical" -> offset = if (showMount || showPet) {
|
||||
// full hero box
|
||||
when {
|
||||
hasMount -> PointF(13.0f, 12.0f)
|
||||
hasPet -> PointF(13.0f, 24.5f + 12.0f)
|
||||
else -> PointF(13.0f, 28.0f + 12.0f)
|
||||
}
|
||||
} else if (showBackground) {
|
||||
// compact hero box
|
||||
PointF(-12.0f, 18.0f + 12.0f)
|
||||
} else {
|
||||
// hero only box
|
||||
PointF(-12.0f, 12.0f)
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise lookup default layer type based offset
|
||||
if (offset == null) {
|
||||
when (layerType) {
|
||||
AvatarView.LayerType.BACKGROUND -> if (!(showMount || showPet)) {
|
||||
offset = PointF(-25.0f, 0.0f) // compact hero box
|
||||
}
|
||||
AvatarView.LayerType.MOUNT_BODY, AvatarView.LayerType.MOUNT_HEAD -> offset = PointF(25.0f, 18.0f) // full hero box
|
||||
AvatarView.LayerType.CHAIR, AvatarView.LayerType.BACK, AvatarView.LayerType.SKIN, AvatarView.LayerType.SHIRT, AvatarView.LayerType.ARMOR, AvatarView.LayerType.BODY, AvatarView.LayerType.HEAD_0, AvatarView.LayerType.HAIR_BASE, AvatarView.LayerType.HAIR_BANGS, AvatarView.LayerType.HAIR_MUSTACHE, AvatarView.LayerType.HAIR_BEARD, AvatarView.LayerType.EYEWEAR, AvatarView.LayerType.VISUAL_BUFF, AvatarView.LayerType.HEAD, AvatarView.LayerType.HEAD_ACCESSORY, AvatarView.LayerType.HAIR_FLOWER, AvatarView.LayerType.SHIELD, AvatarView.LayerType.WEAPON, AvatarView.LayerType.ZZZ -> if (showMount || showPet) {
|
||||
// full hero box
|
||||
offset = when {
|
||||
hasMount -> PointF(25.0f, 0f)
|
||||
hasPet -> PointF(25.0f, 24.5f)
|
||||
else -> PointF(25.0f, 28.0f)
|
||||
}
|
||||
} else if (showBackground) {
|
||||
// compact hero box
|
||||
offset = PointF(0.0f, 18.0f)
|
||||
}
|
||||
AvatarView.LayerType.PET -> offset = PointF(0f, (FULL_HERO_RECT.height() - layerImageInfo.height).toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (offset != null) {
|
||||
when (layerName) {
|
||||
"head_special_0" -> offset = PointF(offset.x, offset.y+3)
|
||||
"head_special_1" -> offset = PointF(offset.x, offset.y+3)
|
||||
}
|
||||
|
||||
val translateMatrix = Matrix()
|
||||
translateMatrix.setTranslate(offset.x, offset.y)
|
||||
translateMatrix.mapRect(boundsF)
|
||||
}
|
||||
|
||||
// resize bounds to fit and keep original aspect ratio
|
||||
avatarMatrix.mapRect(boundsF)
|
||||
boundsF.round(bounds)
|
||||
|
||||
return bounds
|
||||
}
|
||||
|
||||
private fun getFileName(imageName: String): String {
|
||||
val name = if (FILENAME_MAP.containsKey(imageName)) {
|
||||
FILENAME_MAP[imageName]
|
||||
} else {
|
||||
imageName
|
||||
}
|
||||
return name + if (FILEFORMAT_MAP.containsKey(imageName)) {
|
||||
"." + FILEFORMAT_MAP[imageName]
|
||||
} else {
|
||||
".png"
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLayerComplete() {
|
||||
if (numberLayersInProcess.decrementAndGet() == 0) {
|
||||
avatarImageConsumer?.accept(avatarImage)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAvatarImageReady(consumer: Consumer<Bitmap?>) {
|
||||
avatarImageConsumer = consumer
|
||||
if (multiDraweeHolder.size() > 0 && numberLayersInProcess.get() == 0) {
|
||||
avatarImageConsumer?.accept(avatarImage)
|
||||
} else {
|
||||
initAvatarRectMatrix()
|
||||
showLayers(layerMap)
|
||||
}
|
||||
}
|
||||
|
||||
fun setAvatar(avatar: Avatar) {
|
||||
val oldUser = this.avatar
|
||||
this.avatar = avatar
|
||||
|
||||
if (oldUser != null) {
|
||||
val newLayerMap = getLayerMap(avatar, false)
|
||||
|
||||
val equals = currentLayers != null && currentLayers == newLayerMap
|
||||
|
||||
if (!equals) {
|
||||
multiDraweeHolder.clear()
|
||||
numberLayersInProcess.set(0)
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun initAvatarRectMatrix() {
|
||||
if (avatarRectF == null) {
|
||||
val srcRect = originalRect
|
||||
|
||||
if (isOrphan) {
|
||||
avatarRectF = RectF(srcRect)
|
||||
|
||||
// change scale to not be 1:1
|
||||
// a quick fix as fresco AnimatedDrawable/ScaleTypeDrawable
|
||||
// will not translate matrix properly
|
||||
avatarMatrix.setScale(1.2f, 1.2f)
|
||||
avatarMatrix.mapRect(avatarRectF)
|
||||
} else {
|
||||
// full hero box when showMount and showPet is enabled (140w * 147h)
|
||||
// compact hero box when only showBackground is enabled (114w * 114h)
|
||||
// hero only box when all show settings disabled (90w * 90h)
|
||||
avatarRectF = RectF(0f, 0f, width.toFloat(), height.toFloat())
|
||||
avatarMatrix.setRectToRect(RectF(srcRect), avatarRectF, Matrix.ScaleToFit.START) // TODO support other ScaleToFit
|
||||
avatarRectF = RectF(srcRect)
|
||||
avatarMatrix.mapRect(avatarRectF)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
initAvatarRectMatrix()
|
||||
|
||||
// draw only when user is set
|
||||
if (avatar == null || !avatar!!.isValid) return
|
||||
|
||||
// request image layers if not yet processed
|
||||
if (multiDraweeHolder.size() == 0) {
|
||||
showLayers(layerMap)
|
||||
}
|
||||
|
||||
// manually call onAttach/onDetach if view is without parent as they will never be called otherwise
|
||||
if (isOrphan) multiDraweeHolder.onAttach()
|
||||
multiDraweeHolder.draw(canvas)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
multiDraweeHolder.onDetach()
|
||||
}
|
||||
|
||||
override fun onStartTemporaryDetach() {
|
||||
super.onStartTemporaryDetach()
|
||||
multiDraweeHolder.onDetach()
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
multiDraweeHolder.onAttach()
|
||||
}
|
||||
|
||||
override fun onFinishTemporaryDetach() {
|
||||
super.onFinishTemporaryDetach()
|
||||
multiDraweeHolder.onAttach()
|
||||
}
|
||||
|
||||
override fun verifyDrawable(who: Drawable): Boolean {
|
||||
return multiDraweeHolder.verifyDrawable(who) || super.verifyDrawable(who)
|
||||
}
|
||||
|
||||
override fun invalidateDrawable(drawable: Drawable) {
|
||||
invalidate()
|
||||
if (avatarCanvas != null) draw(avatarCanvas)
|
||||
}
|
||||
|
||||
enum class LayerType(internal val order: Int) {
|
||||
BACKGROUND(0),
|
||||
MOUNT_BODY(1),
|
||||
CHAIR(2),
|
||||
BACK(3),
|
||||
SKIN(4),
|
||||
SHIRT(5),
|
||||
ARMOR(6),
|
||||
BODY(7),
|
||||
HEAD_0(8),
|
||||
HAIR_BASE(9),
|
||||
HAIR_BANGS(10),
|
||||
HAIR_MUSTACHE(11),
|
||||
HAIR_BEARD(12),
|
||||
EYEWEAR(13),
|
||||
VISUAL_BUFF(14),
|
||||
HEAD(15),
|
||||
HEAD_ACCESSORY(16),
|
||||
HAIR_FLOWER(17),
|
||||
SHIELD(18),
|
||||
WEAPON(19),
|
||||
MOUNT_HEAD(20),
|
||||
ZZZ(21),
|
||||
PET(22)
|
||||
}
|
||||
|
||||
interface Consumer<in T> {
|
||||
fun accept(t: T)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val IMAGE_URI_ROOT = "https://habitica-assets.s3.amazonaws.com/mobileApp/images/"
|
||||
private const val TAG = "AvatarView"
|
||||
val FILEFORMAT_MAP: Map<String, String>
|
||||
val FILENAME_MAP: Map<String, String>
|
||||
private val FULL_HERO_RECT = Rect(0, 0, 140, 147)
|
||||
private val COMPACT_HERO_RECT = Rect(0, 0, 114, 114)
|
||||
private val HERO_ONLY_RECT = Rect(0, 0, 90, 90)
|
||||
|
||||
init {
|
||||
val tempMap = HashMap<String, String>()
|
||||
tempMap["head_special_1"] = "gif"
|
||||
tempMap["broad_armor_special_1"] = "gif"
|
||||
tempMap["slim_armor_special_1"] = "gif"
|
||||
tempMap["head_special_0"] = "gif"
|
||||
tempMap["slim_armor_special_0"] = "gif"
|
||||
tempMap["broad_armor_special_0"] = "gif"
|
||||
tempMap["weapon_special_critical"] = "gif"
|
||||
tempMap["weapon_special_0"] = "gif"
|
||||
tempMap["shield_special_0"] = "gif"
|
||||
tempMap["Pet-Wolf-Cerberus"] = "gif"
|
||||
FILEFORMAT_MAP = Collections.unmodifiableMap(tempMap)
|
||||
|
||||
|
||||
val tempNameMap = HashMap<String, String>()
|
||||
tempNameMap["head_special_1"] = "ContributorOnly-Equip-CrystalHelmet"
|
||||
tempNameMap["armor_special_1"] = "ContributorOnly-Equip-CrystalArmor"
|
||||
tempNameMap["weapon_special_critical"] = "weapon_special_critical"
|
||||
tempNameMap["Pet-Wolf-Cerberus"] = "Pet-Wolf-Cerberus"
|
||||
FILENAME_MAP = Collections.unmodifiableMap(tempNameMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,6 +116,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
|
|
@ -426,8 +427,12 @@ public class MainActivity extends BaseActivity implements TutorialView.OnTutoria
|
|||
Preferences preferences = user.getPreferences();
|
||||
|
||||
if (preferences != null) {
|
||||
apiClient.setLanguageCode(preferences.getLanguage());
|
||||
soundManager.setSoundTheme(preferences.getSound());
|
||||
if (preferences.getLanguage() != null) {
|
||||
apiClient.setLanguageCode(preferences.getLanguage());
|
||||
}
|
||||
if (preferences.getSound() != null) {
|
||||
soundManager.setSoundTheme(preferences.getSound());
|
||||
}
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
updateHeader();
|
||||
|
|
@ -594,7 +599,7 @@ public class MainActivity extends BaseActivity implements TutorialView.OnTutoria
|
|||
return;
|
||||
}
|
||||
|
||||
if (rewardKey.equals("potion")) {
|
||||
if (Objects.equals(rewardKey, "potion")) {
|
||||
int currentHp = user.getStats().getHp().intValue();
|
||||
int maxHp = user.getStats().getMaxHealth();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.adapter.inventory;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.habitrpg.android.habitica.R;
|
||||
import com.habitrpg.android.habitica.models.inventory.Equipment;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import io.realm.OrderedRealmCollection;
|
||||
import io.realm.RealmRecyclerViewAdapter;
|
||||
import rx.Observable;
|
||||
import rx.subjects.PublishSubject;
|
||||
|
||||
public class EquipmentRecyclerViewAdapter extends RealmRecyclerViewAdapter<Equipment, EquipmentRecyclerViewAdapter.GearViewHolder> {
|
||||
|
||||
public String equippedGear;
|
||||
public Boolean isCostume;
|
||||
public String type;
|
||||
|
||||
private PublishSubject<String> equipEvents = PublishSubject.create();
|
||||
|
||||
public EquipmentRecyclerViewAdapter(@Nullable OrderedRealmCollection<Equipment> data, boolean autoUpdate) {
|
||||
super(data, autoUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GearViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.gear_list_item, parent, false);
|
||||
|
||||
return new GearViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(GearViewHolder holder, int position) {
|
||||
if (getData() != null) {
|
||||
holder.bind(getData().get(position));
|
||||
}
|
||||
}
|
||||
|
||||
public Observable<String> getEquipEvents() {
|
||||
return equipEvents.asObservable();
|
||||
}
|
||||
|
||||
class GearViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
|
||||
@BindView(R.id.gear_container)
|
||||
View gearContainer;
|
||||
|
||||
@BindView(R.id.gear_text)
|
||||
TextView gearNameTextView;
|
||||
|
||||
@BindView(R.id.gear_notes)
|
||||
TextView gearNotesTextView;
|
||||
|
||||
@BindView(R.id.gear_image)
|
||||
SimpleDraweeView imageView;
|
||||
|
||||
@BindView(R.id.equippedIndicator)
|
||||
View equippedIndicator;
|
||||
|
||||
Equipment gear;
|
||||
|
||||
Context context;
|
||||
|
||||
public GearViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
ButterKnife.bind(this, itemView);
|
||||
|
||||
context = itemView.getContext();
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
public void bind(Equipment gear) {
|
||||
this.gear = gear;
|
||||
this.gearNameTextView.setText(this.gear.getText());
|
||||
this.gearNotesTextView.setText(this.gear.getNotes());
|
||||
|
||||
if (gear.getKey().equals(equippedGear)) {
|
||||
this.equippedIndicator.setVisibility(View.VISIBLE);
|
||||
this.gearContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.brand_700));
|
||||
} else {
|
||||
this.equippedIndicator.setVisibility(View.GONE);
|
||||
this.gearContainer.setBackgroundResource(R.drawable.selection_highlight);
|
||||
}
|
||||
|
||||
String imageUrl = "https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_" + gear.getKey() + ".png";
|
||||
imageView.setImageURI(Uri.parse(imageUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
equipEvents.onNext(this.gear.getKey());
|
||||
if (this.gear.getKey().equals(equippedGear)) {
|
||||
equippedGear = type + "_base_0";
|
||||
} else {
|
||||
equippedGear = this.gear.getKey();
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.habitrpg.android.habitica.ui.adapter.inventory
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.models.inventory.Equipment
|
||||
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import com.habitrpg.android.habitica.extensions.bindView
|
||||
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
||||
import io.realm.OrderedRealmCollection
|
||||
import io.realm.RealmRecyclerViewAdapter
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
|
||||
class EquipmentRecyclerViewAdapter(data: OrderedRealmCollection<Equipment>?, autoUpdate: Boolean) : RealmRecyclerViewAdapter<Equipment, EquipmentRecyclerViewAdapter.GearViewHolder>(data, autoUpdate) {
|
||||
|
||||
var equippedGear: String? = null
|
||||
var isCostume: Boolean? = null
|
||||
var type: String? = null
|
||||
|
||||
val equipEvents: PublishSubject<String> = PublishSubject.create<String>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GearViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.gear_list_item, parent, false)
|
||||
return GearViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: GearViewHolder, position: Int) {
|
||||
if (data != null) {
|
||||
holder.bind(data!![position])
|
||||
}
|
||||
}
|
||||
|
||||
inner class GearViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
private val gearContainer: View by bindView(itemView, R.id.gear_container)
|
||||
private val gearNameTextView: TextView by bindView(itemView, R.id.gear_text)
|
||||
private val gearNotesTextView: TextView by bindView(itemView, R.id.gear_notes)
|
||||
private val imageView: SimpleDraweeView by bindView(itemView, R.id.gear_image)
|
||||
private val equippedIndicator: View by bindView(itemView, R.id.equippedIndicator)
|
||||
|
||||
var gear: Equipment? = null
|
||||
var context: Context = itemView.context
|
||||
|
||||
init {
|
||||
context = itemView.context
|
||||
itemView.setOnClickListener {
|
||||
val key = gear?.key
|
||||
if (key != null) {
|
||||
equipEvents.onNext(key)
|
||||
equippedGear = if (key == equippedGear) {
|
||||
type + "_base_0"
|
||||
} else {
|
||||
key
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(gear: Equipment) {
|
||||
this.gear = gear
|
||||
this.gearNameTextView.text = this.gear?.text
|
||||
this.gearNotesTextView.text = this.gear?.notes
|
||||
|
||||
if (gear.key == equippedGear) {
|
||||
this.equippedIndicator.visibility = View.VISIBLE
|
||||
this.gearContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.brand_700))
|
||||
} else {
|
||||
this.equippedIndicator.visibility = View.GONE
|
||||
this.gearContainer.setBackgroundResource(R.drawable.selection_highlight)
|
||||
}
|
||||
DataBindingUtils.loadImage(imageView, "shop_"+gear.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,7 +245,6 @@ class ShopRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
private val namePlate: TextView by bindView(itemView, R.id.namePlate)
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
context = itemView.context
|
||||
descriptionView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
|
@ -278,7 +277,6 @@ class ShopRecyclerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
private val subscribeButton: Button? by bindView(itemView, R.id.subscribeButton)
|
||||
private val textView: TextView? by bindView(itemView, R.id.textView)
|
||||
init {
|
||||
ButterKnife.bind(this, view)
|
||||
subscribeButton?.setOnClickListener { EventBus.getDefault().post(OpenGemPurchaseFragmentCommand()) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class AvatarOverviewFragment : BaseMainFragment(), AdapterView.OnItemSelectedLis
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (apiClient != null) {
|
||||
apiClient.content
|
||||
apiClient.getContent()
|
||||
.subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.fragments.inventory.equipment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.habitrpg.android.habitica.R;
|
||||
import com.habitrpg.android.habitica.components.AppComponent;
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository;
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
|
||||
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter;
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment;
|
||||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class EquipmentDetailFragment extends BaseMainFragment {
|
||||
|
||||
@Inject
|
||||
InventoryRepository inventoryRepository;
|
||||
|
||||
public String type;
|
||||
public String equippedGear;
|
||||
public Boolean isCostume;
|
||||
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
EquipmentRecyclerViewAdapter adapter;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View v = inflater.inflate(R.layout.fragment_recyclerview, container, false);
|
||||
|
||||
setUnbinder(ButterKnife.bind(this, v));
|
||||
|
||||
|
||||
this.adapter = new EquipmentRecyclerViewAdapter(null, true);
|
||||
this.adapter.equippedGear = this.equippedGear;
|
||||
this.adapter.isCostume = this.isCostume;
|
||||
this.adapter.type = this.type;
|
||||
this.adapter.getEquipEvents()
|
||||
.flatMap(key -> inventoryRepository.equipGear(user, key, isCostume))
|
||||
.subscribe(items -> {}, RxErrorHandler.handleEmptyError());
|
||||
|
||||
|
||||
this.recyclerView.setAdapter(this.adapter);
|
||||
this.recyclerView.setLayoutManager(new LinearLayoutManager(activity));
|
||||
this.recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
|
||||
recyclerView.setItemAnimator(new SafeDefaultItemAnimator());
|
||||
|
||||
inventoryRepository.getOwnedEquipment(type).first().subscribe(this.adapter::updateData, RxErrorHandler.handleEmptyError());
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
inventoryRepository.close();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(AppComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.habitrpg.android.habitica.ui.fragments.inventory.equipment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.DividerItemDecoration
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.AppComponent
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.inventory.Equipment
|
||||
import com.habitrpg.android.habitica.models.user.Items
|
||||
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
|
||||
import io.realm.RealmResults
|
||||
import kotlinx.android.synthetic.main.fragment_recyclerview.*
|
||||
import rx.functions.Action1
|
||||
import javax.inject.Inject
|
||||
|
||||
class EquipmentDetailFragment : BaseMainFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var inventoryRepository: InventoryRepository
|
||||
|
||||
var type: String? = null
|
||||
var equippedGear: String? = null
|
||||
var isCostume: Boolean? = null
|
||||
|
||||
private var adapter: EquipmentRecyclerViewAdapter = EquipmentRecyclerViewAdapter(null, true)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
val v = inflater.inflate(R.layout.fragment_recyclerview, container, false)
|
||||
|
||||
this.adapter.equippedGear = this.equippedGear
|
||||
this.adapter.isCostume = this.isCostume
|
||||
this.adapter.type = this.type
|
||||
this.adapter.equipEvents.flatMap<Items> { key -> inventoryRepository.equipGear(user, key, isCostume!!) }
|
||||
.subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
|
||||
return v
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
recyclerView.adapter = this.adapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(activity)
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(getActivity()!!, DividerItemDecoration.VERTICAL))
|
||||
recyclerView.itemAnimator = SafeDefaultItemAnimator()
|
||||
|
||||
inventoryRepository.getOwnedEquipment(type).first().subscribe(Action1<RealmResults<Equipment>> { this.adapter.updateData(it) }, RxErrorHandler.handleEmptyError())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
inventoryRepository.close()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun injectFragment(component: AppComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.components.AppComponent
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import kotlinx.android.synthetic.main.fragment_equipment_overview.*
|
||||
import rx.functions.Action1
|
||||
|
|
@ -56,7 +57,7 @@ class EquipmentOverviewFragment : BaseMainFragment() {
|
|||
if (this.nameMapping.isEmpty()) {
|
||||
compositeSubscription.add(inventoryRepository.ownedEquipment.subscribe(Action1 {
|
||||
for (gear in it) {
|
||||
this.nameMapping.put(gear.key ?: "", gear.text)
|
||||
this.nameMapping[gear.key ?: ""] = gear.text
|
||||
}
|
||||
|
||||
setEquipmentNames()
|
||||
|
|
@ -120,9 +121,7 @@ class EquipmentOverviewFragment : BaseMainFragment() {
|
|||
fragment.type = type
|
||||
fragment.isCostume = isCostume
|
||||
fragment.equippedGear = equipped
|
||||
if (activity != null) {
|
||||
activity!!.displayFragment(fragment)
|
||||
}
|
||||
activity?.displayFragment(fragment)
|
||||
}
|
||||
|
||||
override fun customTitle(): String {
|
||||
|
|
|
|||
|
|
@ -24,15 +24,23 @@ import com.habitrpg.android.habitica.R
|
|||
|
||||
object DataBindingUtils {
|
||||
|
||||
fun loadImage(view: SimpleDraweeView?, imageName: String?) {
|
||||
fun loadImage(view: SimpleDraweeView?, imageName: String) {
|
||||
loadImage(view, imageName, "png")
|
||||
}
|
||||
|
||||
fun loadImage(view: SimpleDraweeView?, imageName: String?, imageFormat: String = "png") {
|
||||
if (view != null && imageName != null && view.visibility == View.VISIBLE) {
|
||||
view.setImageURI("https://habitica-assets.s3.amazonaws.com/mobileApp/images/$imageName.png")
|
||||
view.setImageURI("https://habitica-assets.s3.amazonaws.com/mobileApp/images/$imageName.$imageFormat")
|
||||
}
|
||||
}
|
||||
|
||||
fun loadImage(imageName: String, imageResult: (Bitmap) -> Unit) {
|
||||
loadImage(imageName, "png", imageResult)
|
||||
}
|
||||
|
||||
fun loadImage(imageName: String, imageFormat: String = "png", imageResult: (Bitmap) -> Unit) {
|
||||
val imageRequest = ImageRequestBuilder
|
||||
.newBuilderWithSource(Uri.parse("https://habitica-assets.s3.amazonaws.com/mobileApp/images/$imageName.png"))
|
||||
.newBuilderWithSource(Uri.parse("https://habitica-assets.s3.amazonaws.com/mobileApp/images/$imageName.$imageFormat"))
|
||||
.build()
|
||||
|
||||
val imagePipeline = Fresco.getImagePipeline()
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.helpers;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
// from https://code.google.com/p/tarsius/source/browse/branches/tarsius_0.00_dev2/src/org/tarsius/util/Debounce.java
|
||||
|
||||
public abstract class Debounce {
|
||||
private Timer timer = null;
|
||||
private long lastHit = 0;
|
||||
private long debounceDelay = 0;
|
||||
private long checkDelay = 0;
|
||||
|
||||
public Debounce(long debounceDelay, long checkDelay) {
|
||||
this.debounceDelay = debounceDelay;
|
||||
this.checkDelay = checkDelay;
|
||||
}
|
||||
|
||||
public abstract void execute();
|
||||
|
||||
public void hit() {
|
||||
lastHit = System.currentTimeMillis();
|
||||
if (this.timer != null) {
|
||||
this.timer.cancel();
|
||||
this.timer = null;
|
||||
}
|
||||
this.timer = new Timer("Debounce", true);
|
||||
this.timer.schedule(new DebounceTask(this), 0, checkDelay);
|
||||
}
|
||||
|
||||
private void checkExecute() {
|
||||
if ((System.currentTimeMillis() - lastHit) > debounceDelay) {
|
||||
this.timer.cancel();
|
||||
this.timer = null;
|
||||
execute();
|
||||
}
|
||||
}
|
||||
|
||||
private class DebounceTask extends TimerTask {
|
||||
|
||||
private Debounce debounceInstance = null;
|
||||
|
||||
public DebounceTask(Debounce debounceInstance) {
|
||||
this.debounceInstance = debounceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
debounceInstance.checkExecute();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.habitrpg.android.habitica.R;
|
||||
|
||||
import net.pherth.android.emoji_library.EmojiEditText;
|
||||
import net.pherth.android.emoji_library.EmojiPopup;
|
||||
|
||||
/**
|
||||
* @author data5tream
|
||||
*/
|
||||
public class EmojiKeyboard {
|
||||
|
||||
/**
|
||||
* Create a Emoji keyboard
|
||||
*
|
||||
* @param itemView Must contain views with the following IDs:
|
||||
* 'emoji.toggle.btn' for the ImageButton that is used to enable/disable the
|
||||
* emoji keyboard
|
||||
* 'edit.new.message.text' for the EmojiEditText where the emojis are put into
|
||||
* @param context The context from the calling Activity
|
||||
*/
|
||||
public static void createKeyboard(View itemView, final Context context) {
|
||||
|
||||
final ImageButton emojiButton = (ImageButton) itemView.findViewById(R.id.emoji_toggle_btn);
|
||||
final EmojiEditText emojiEditText = (EmojiEditText) itemView.findViewById(R.id.edit_new_message_text);
|
||||
final EmojiPopup popup = new EmojiPopup(itemView.getRootView(), context, ContextCompat.getColor(context, R.color.brand));
|
||||
|
||||
popup.setSizeForSoftKeyboard();
|
||||
|
||||
popup.setOnDismissListener(() -> changeEmojiKeyboardIcon(emojiButton, context, false));
|
||||
|
||||
popup.setOnSoftKeyboardOpenCloseListener(new EmojiPopup.OnSoftKeyboardOpenCloseListener() {
|
||||
|
||||
@Override
|
||||
public void onKeyboardOpen(int keyBoardHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKeyboardClose() {
|
||||
if (popup.isShowing())
|
||||
popup.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
popup.setOnEmojiconClickedListener(emojicon -> {
|
||||
if (emojiEditText == null || emojicon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int start = emojiEditText.getSelectionStart();
|
||||
int end = emojiEditText.getSelectionEnd();
|
||||
if (start < 0) {
|
||||
emojiEditText.append(emojicon.getEmoji());
|
||||
} else {
|
||||
emojiEditText.getText().replace(Math.min(start, end),
|
||||
Math.max(start, end), emojicon.getEmoji(), 0,
|
||||
emojicon.getEmoji().length());
|
||||
}
|
||||
});
|
||||
|
||||
popup.setOnEmojiconBackspaceClickedListener(v -> {
|
||||
KeyEvent event = new KeyEvent(
|
||||
0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
|
||||
emojiEditText.dispatchKeyEvent(event);
|
||||
});
|
||||
|
||||
emojiButton.setOnClickListener(v -> {
|
||||
|
||||
if (!popup.isShowing()) {
|
||||
|
||||
if (popup.isKeyBoardOpen()) {
|
||||
popup.showAtBottom();
|
||||
changeEmojiKeyboardIcon(emojiButton, context, true);
|
||||
} else {
|
||||
emojiEditText.setFocusableInTouchMode(true);
|
||||
emojiEditText.requestFocus();
|
||||
popup.showAtBottomPending();
|
||||
final InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.showSoftInput(emojiEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
changeEmojiKeyboardIcon(emojiButton, context, true);
|
||||
}
|
||||
} else {
|
||||
popup.dismiss();
|
||||
changeEmojiKeyboardIcon(emojiButton, context, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void changeEmojiKeyboardIcon(ImageButton view, Context context, Boolean keyboardOpened) {
|
||||
|
||||
if (keyboardOpened) {
|
||||
view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_keyboard_grey600_24dp));
|
||||
} else {
|
||||
view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_emoticon_grey600_24dp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.util.AttributeSet
|
|||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.ui.AvatarView
|
||||
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
|
||||
import kotlinx.android.synthetic.main.item_image_row.view.*
|
||||
|
||||
|
|
@ -13,14 +14,14 @@ class EquipmentItemRow(context: Context?, attrs: AttributeSet?) : LinearLayout(c
|
|||
var equipmentIdentifier: String? = null
|
||||
set(value) {
|
||||
field = value
|
||||
val imageName = if (equipmentIdentifier != null && equipmentIdentifier != "") "shop_"+equipmentIdentifier else "head_0"
|
||||
val imageName = if (equipmentIdentifier?.isNotEmpty() == true && equipmentIdentifier?.endsWith("base_0") == false) "shop_"+equipmentIdentifier else "head_0"
|
||||
DataBindingUtils.loadImage(imageView, imageName)
|
||||
}
|
||||
|
||||
var customizationIdentifier: String? = null
|
||||
set(value) {
|
||||
field = value
|
||||
val imageName = if (customizationIdentifier != null && customizationIdentifier != "") customizationIdentifier else "head_0"
|
||||
val imageName = if (customizationIdentifier?.isNotEmpty() == true) customizationIdentifier else "head_0"
|
||||
DataBindingUtils.loadImage(imageView, imageName)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.view.View
|
|||
import android.widget.FrameLayout
|
||||
import butterknife.ButterKnife
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.extensions.backgroundCompat
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import kotlinx.android.synthetic.main.fixvalues_edittext.view.*
|
||||
|
||||
|
|
@ -28,12 +29,11 @@ class FixValuesEditText(context: Context, attrs: AttributeSet) : FrameLayout(con
|
|||
val backgroundDrawable = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg)
|
||||
backgroundDrawable?.setColorFilter(field, PorterDuff.Mode.MULTIPLY)
|
||||
backgroundDrawable?.alpha = 50
|
||||
iconBackgroundView.background = backgroundDrawable
|
||||
iconBackgroundView.backgroundCompat = backgroundDrawable
|
||||
}
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.fixvalues_edittext, this)
|
||||
ButterKnife.bind(this)
|
||||
|
||||
val attributes = context.theme.obtainStyledAttributes(
|
||||
attrs,
|
||||
|
|
|
|||
Loading…
Reference in a new issue