Merge pull request #539 from nivl4/feat/avatar-gif-support

feat: add GIF supported AvatarView
This commit is contained in:
Phillip Thelen 2016-06-02 12:32:14 +02:00
commit c8a44f28a9
21 changed files with 656 additions and 117 deletions

View file

@ -9,7 +9,7 @@ android:
# Uncomment the lines below if you want to
# use the latest revision of Android SDK Tools
# - platform-tools
# - tools
- tools
# The BuildTools version used by your project
- build-tools-23.0.3

View file

@ -117,6 +117,13 @@ dependencies {
//Analytics
compile 'com.amplitude:android-sdk:2.7.1'
// Fresco Image Management Library
compile('com.facebook.fresco:fresco:0.10.0') {
exclude module: 'bolts-android'
}
compile('com.facebook.fresco:animated-gif:0.10.0') {
exclude module: 'bolts-android'
}
//Tests
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
testCompile 'org.robolectric:robolectric:3.0'

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
@ -10,13 +11,14 @@
android:layout_margin="5dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/avatar"
android:layout_marginRight="5dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatarView"
android:layout_width="140dp"
android:layout_height="147dp" />
android:layout_height="147dp"
android:layout_marginRight="5dp"
app:showBackground="true"
app:showMount="true"
app:showPet="true" />
<LinearLayout
android:layout_width="0dp"

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
@ -17,10 +18,10 @@
android:layout_height="wrap_content"
style="@style/CardContent"
android:background="@drawable/selection_highlight">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/healerAvatarView"
android:layout_width="@dimen/avatar_header_width"
android:layout_height="@dimen/avatar_header_height"
android:id="@+id/healerImageView" />
android:layout_height="@dimen/avatar_header_height" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -49,10 +50,10 @@
android:layout_height="wrap_content"
style="@style/CardContent"
android:background="@drawable/selection_highlight">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/mageAvatarView"
android:layout_width="@dimen/avatar_header_width"
android:layout_height="@dimen/avatar_header_height"
android:id="@+id/mageImageView" />
android:layout_height="@dimen/avatar_header_height" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -81,10 +82,10 @@
android:layout_height="wrap_content"
style="@style/CardContent"
android:background="@drawable/selection_highlight">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/rogueAvatarView"
android:layout_width="@dimen/avatar_header_width"
android:layout_height="@dimen/avatar_header_height"
android:id="@+id/rogueImageView" />
android:layout_height="@dimen/avatar_header_height" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -113,10 +114,10 @@
android:layout_height="wrap_content"
style="@style/CardContent"
android:background="@drawable/selection_highlight">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/warriorAvatarView"
android:layout_width="@dimen/avatar_header_width"
android:layout_height="@dimen/avatar_header_height"
android:id="@+id/warriorImageView" />
android:layout_height="@dimen/avatar_header_height" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -1,6 +1,7 @@
<?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:id="@+id/avatar_with_bars_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -18,16 +19,16 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/IMG_ProfilePicture"
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatarView"
android:layout_width="@dimen/avatar_header_width"
android:layout_height="@dimen/avatar_header_height"
android:layout_gravity="center_vertical"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:scaleType="fitStart"
android:src="@mipmap/ic_launcher"
android:contentDescription="@string/profile_image" />
android:layout_marginRight="16dp"
app:showBackground="true"
app:showMount="true"
app:showPet="true" />
<LinearLayout
android:id="@+id/LL_header"

View file

@ -1,5 +1,6 @@
<?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="match_parent"
android:paddingLeft="24dip"
@ -15,11 +16,14 @@
android:layout_height="wrap_content"
android:layout_margin="8dp" />
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:layout_width="@dimen/avatar_small_width"
android:layout_height="@dimen/avatar_small_height"
android:id="@+id/avatarView"
android:layout_gravity="center_horizontal" />
android:layout_gravity="center_horizontal"
app:showBackground="false"
app:showMount="false"
app:showPet="false" />
<TextView
android:layout_width="wrap_content"

View file

@ -1,16 +1,20 @@
<?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="match_parent"
android:paddingLeft="24dip"
android:paddingEnd="24dip"
android:paddingStart="24dip"
android:paddingRight="24dip">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:layout_width="@dimen/avatar_small_width"
android:layout_height="@dimen/avatar_small_height"
android:id="@+id/avatarView"
android:layout_gravity="center_horizontal" />
android:layout_gravity="center_horizontal"
app:showBackground="false"
app:showMount="false"
app:showPet="false" />
<TextView
android:layout_width="wrap_content"

View file

@ -1,5 +1,6 @@
<?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="match_parent">
@ -8,10 +9,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatarView"
android:layout_width="@dimen/avatar_width"
android:layout_height="@dimen/avatar_height"
android:id="@+id/avatarView" />
app:showBackground="true"
app:showMount="true"
app:showPet="true" />
<TextView
android:layout_width="wrap_content"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/CardView.Default">
@ -10,13 +11,14 @@
style="@style/CardContent"
android:orientation="horizontal">
<ImageView
android:id="@+id/avatar"
android:layout_marginRight="5dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/avatarView"
android:layout_width="103dp"
android:layout_height="90dp" />
android:layout_height="90dp"
android:layout_marginRight="5dp"
app:showBackground="false"
app:showMount="false"
app:showPet="false" />
<LinearLayout
android:layout_width="0dp"

View file

@ -0,0 +1,7 @@
<resources>
<declare-styleable name="AvatarView">
<attr name="showBackground" format="boolean" />
<attr name="showMount" format="boolean" />
<attr name="showPet" format="boolean" />
</declare-styleable>
</resources>

View file

@ -4,6 +4,7 @@ import com.amplitude.api.Amplitude;
import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.core.CrashlyticsCore;
import com.facebook.FacebookSdk;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.habitrpg.android.habitica.ui.activities.IntroActivity;
import com.habitrpg.android.habitica.ui.activities.LoginActivity;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
@ -58,6 +59,8 @@ public class HabiticaApplication extends MultiDexApplication {
createBillingAndCheckout();
registerActivityLifecycleCallbacks();
Amplitude.getInstance().initialize(this, getString(R.string.amplitude_app_id)).enableForegroundTracking(this);
Fresco.initialize(this);
}
private void setupLeakCanary() {

View file

@ -0,0 +1,454 @@
package com.habitrpg.android.habitica.ui;
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.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
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 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 hasBackground;
private boolean hasMount;
private boolean hasPet;
private boolean isOrphan;
private MultiDraweeHolder<GenericDraweeHierarchy> multiDraweeHolder = new MultiDraweeHolder<>();
private HabitRPGUser user;
private RectF avatarRectF;
private Matrix matrix = new Matrix();
private AtomicInteger numberLayersInProcess = new AtomicInteger(0);
private Consumer<Bitmap> avatarImageConsumer;
private Bitmap avatarBitmap;
private Canvas avatarCanvas;
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;
}
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);
} finally {
a.recycle();
}
}
private void showLayers(@NonNull Map<LayerType, String> layerMap) {
if (multiDraweeHolder.size() > 0) return;
int i = 0;
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) {
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 user != null;
return getLayerMap(user, true);
}
private Map<LayerType, String> getLayerMap(@NonNull HabitRPGUser user, boolean resetHasAttributes) {
EnumMap<LayerType, String> layerMap = user.getAvatarLayerMap();
if (resetHasAttributes) hasBackground = hasMount = hasPet = false;
String mountName = user.getItems().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 = user.getItems().getCurrentPet();
if (showPet && !TextUtils.isEmpty(petName)) {
layerMap.put(LayerType.PET, "Pet-" + petName);
if (resetHasAttributes) hasPet = true;
}
String backgroundName = user.getPreferences().getBackground();
if (showBackground && !TextUtils.isEmpty(backgroundName)) {
layerMap.put(LayerType.BACKGROUND, "background_" + backgroundName);
if (resetHasAttributes) hasBackground = true;
}
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 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 setUser(@NonNull HabitRPGUser user) {
HabitRPGUser oldUser = this.user;
this.user = user;
if (oldUser != null) {
Map<LayerType, String> currentLayerMap = getLayerMap(oldUser, false);
Map<LayerType, String> newLayerMap = getLayerMap(user, false);
if (!currentLayerMap.equals(newLayerMap)) {
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 user != 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 (user == null) 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
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),
HEAD(14),
HEAD_ACCESSORY(15),
HAIR_FLOWER(16),
SHIELD(17),
WEAPON(18),
MOUNT_HEAD(19),
ZZZ(20),
PET(21);
final int order;
LayerType(int order) {
this.order = order;
}
}
public interface Consumer<T> {
void accept(T t);
}
}

View file

@ -4,7 +4,6 @@ import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.databinding.ValueBarBinding;
import com.habitrpg.android.habitica.events.BoughtGemsEvent;
import com.habitrpg.android.habitica.events.commands.OpenGemPurchaseFragmentCommand;
import com.habitrpg.android.habitica.userpicture.UserPicture;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
import com.magicmicky.habitrpgwrapper.lib.models.Stats;
@ -20,7 +19,6 @@ import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
@ -31,7 +29,7 @@ public class AvatarWithBarsViewModel implements View.OnClickListener {
private ValueBarBinding xpBar;
private ValueBarBinding mpBar;
private ImageView image;
private AvatarView avatarView;
private android.content.res.Resources res;
@ -39,7 +37,6 @@ public class AvatarWithBarsViewModel implements View.OnClickListener {
private TextView lvlText, goldText, silverText, gemsText;
private HabitRPGUser userObject;
private UserPicture userPicture;
private int cachedMaxHealth, cachedMaxExp, cachedMaxMana;
@ -59,7 +56,7 @@ public class AvatarWithBarsViewModel implements View.OnClickListener {
gemsText = (TextView) v.findViewById(R.id.gems_tv);
View hpBarView = v.findViewById(R.id.hpBar);
image = (ImageView) v.findViewById(R.id.IMG_ProfilePicture);
avatarView = (AvatarView) v.findViewById(R.id.avatarView);
hpBar = DataBindingUtil.bind(hpBarView);
xpBar = DataBindingUtil.bind(v.findViewById(R.id.xpBar));
mpBar = DataBindingUtil.bind(v.findViewById(R.id.mpBar));
@ -70,7 +67,6 @@ public class AvatarWithBarsViewModel implements View.OnClickListener {
gemsText.setClickable(true);
gemsText.setOnClickListener(this);
this.userPicture = new UserPicture(this.context);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@ -83,8 +79,7 @@ public class AvatarWithBarsViewModel implements View.OnClickListener {
int gp = (stats.getGp().intValue());
int sp = (int) ((stats.getGp() - gp) * 100);
userPicture.setUser(user);
userPicture.setPictureOn(image);
avatarView.setUser(user);
if (stats.get_class() != null) {
userClass += stats.getCleanedClassName();

View file

@ -3,7 +3,7 @@ package com.habitrpg.android.habitica.ui.activities;
import com.habitrpg.android.habitica.APIHelper;
import com.habitrpg.android.habitica.HabiticaApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.userpicture.UserPicture;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.magicmicky.habitrpgwrapper.lib.models.Gear;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
import com.magicmicky.habitrpgwrapper.lib.models.Hair;
@ -15,7 +15,6 @@ import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.widget.ImageView;
import java.util.HashMap;
import java.util.Map;
@ -30,14 +29,14 @@ public class ClassSelectionActivity extends BaseActivity implements Action1<Habi
Boolean classWasUnset = false;
Boolean shouldFinish = false;
@BindView(R.id.healerImageView)
ImageView healerImageView;
@BindView(R.id.mageImageView)
ImageView mageImageView;
@BindView(R.id.rogueImageView)
ImageView rogueImageView;
@BindView(R.id.warriorImageView)
ImageView warriorImageView;
@BindView(R.id.healerAvatarView)
AvatarView healerAvatarView;
@BindView(R.id.mageAvatarView)
AvatarView mageAvatarView;
@BindView(R.id.rogueAvatarView)
AvatarView rogueAvatarView;
@BindView(R.id.warriorAvatarView)
AvatarView warriorAvatarView;
APIHelper apiHelper;
@ -77,18 +76,14 @@ public class ClassSelectionActivity extends BaseActivity implements Action1<Habi
healerOutfit.setShield("shield_healer_5");
healerOutfit.setWeapon("weapon_healer_6");
HabitRPGUser healer = this.makeUser(preferences, healerOutfit);
UserPicture healerUserPicture = new UserPicture(this);
healerUserPicture.setUser(healer);
healerUserPicture.setPictureOn(healerImageView);
healerAvatarView.setUser(healer);
Outfit mageOutfit = new Outfit();
mageOutfit.setArmor("armor_wizard_5");
mageOutfit.setHead("head_wizard_5");
mageOutfit.setWeapon("weapon_wizard_6");
HabitRPGUser mage = this.makeUser(preferences, mageOutfit);
UserPicture mageUserPicture = new UserPicture(this);
mageUserPicture.setUser(mage);
mageUserPicture.setPictureOn(mageImageView);
mageAvatarView.setUser(mage);
Outfit rogueOutfit = new Outfit();
rogueOutfit.setArmor("armor_rogue_5");
@ -96,9 +91,7 @@ public class ClassSelectionActivity extends BaseActivity implements Action1<Habi
rogueOutfit.setShield("shield_rogue_6");
rogueOutfit.setWeapon("weapon_rogue_6");
HabitRPGUser rogue = this.makeUser(preferences, rogueOutfit);
UserPicture rogueUserPicture = new UserPicture(this);
rogueUserPicture.setUser(rogue);
rogueUserPicture.setPictureOn(rogueImageView);
rogueAvatarView.setUser(rogue);
Outfit warriorOutfit = new Outfit();
warriorOutfit.setArmor("armor_warrior_5");
@ -106,9 +99,7 @@ public class ClassSelectionActivity extends BaseActivity implements Action1<Habi
warriorOutfit.setShield("shield_warrior_5");
warriorOutfit.setWeapon("weapon_warrior_6");
HabitRPGUser warrior = this.makeUser(preferences, warriorOutfit);
UserPicture warriorUserPicture = new UserPicture(this);
warriorUserPicture.setUser(warrior);
warriorUserPicture.setPictureOn(warriorImageView);
warriorAvatarView.setUser(warrior);
if (!isInitialSelection) {
apiHelper.apiService.changeClass()

View file

@ -30,6 +30,7 @@ import com.habitrpg.android.habitica.events.commands.OpenMenuItemCommand;
import com.habitrpg.android.habitica.events.commands.SellItemCommand;
import com.habitrpg.android.habitica.events.commands.UnlockPathCommand;
import com.habitrpg.android.habitica.events.commands.UpdateUserCommand;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel;
import com.habitrpg.android.habitica.ui.menu.MainDrawerBuilder;
import com.habitrpg.android.habitica.ui.TutorialView;
@ -38,7 +39,6 @@ import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment;
import com.habitrpg.android.habitica.ui.fragments.GemsPurchaseFragment;
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils;
import com.habitrpg.android.habitica.userpicture.BitmapUtils;
import com.habitrpg.android.habitica.userpicture.UserPicture;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
import com.magicmicky.habitrpgwrapper.lib.models.SuppressedModals;
import com.magicmicky.habitrpgwrapper.lib.models.Tag;
@ -164,8 +164,8 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
private APIHelper apiHelper;
private AlertDialog faintDialog;
private UserPicture sideUserPicture;
private UserPicture dialogUserPicture;
private AvatarView sideAvatarView;
private AvatarView dialogAvatarView;
private Date lastSync;
@ -199,8 +199,7 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
drawer = MainDrawerBuilder.CreateDefaultBuilderSettings(this, toolbar, accountHeader)
.build();
drawer.setSelectionAtPosition(1, false);
this.sideUserPicture = new UserPicture(this, true, false);
this.dialogUserPicture = new UserPicture(this, false, false);
sideAvatarView = new AvatarView(this, true, false, false);
if (this.filterDrawer == null) {
filterDrawer = new DrawerBuilder()
@ -588,9 +587,9 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
}
}
profile.withName(user.getProfile().getName());
sideUserPicture.setUser(this.user);
sideUserPicture.setPictureWithRunnable(avatar -> {
profile.withIcon(avatar);
sideAvatarView.setUser(user);
sideAvatarView.onAvatarImageReady(avatarImage -> {
profile.withIcon(avatarImage);
accountHeader.updateProfile(profile);
});
accountHeader.updateProfile(profile);
@ -1002,9 +1001,8 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
hpBar.setPartyMembers(true);
AvatarWithBarsViewModel.setHpBarData(hpBar, user.getStats(), this);
ImageView avatarView = (ImageView) customView.findViewById(R.id.avatarView);
this.dialogUserPicture.setUser(this.user);
this.dialogUserPicture.setPictureOn(avatarView);
dialogAvatarView = (AvatarView) customView.findViewById(R.id.avatarView);
dialogAvatarView.setUser(user);
}
this.faintDialog = new AlertDialog.Builder(this)
@ -1036,11 +1034,16 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
if (customView != null) {
TextView detailView = (TextView) customView.findViewById(R.id.levelupDetail);
detailView.setText(this.getString(R.string.levelup_detail, level));
ImageView avatarView = (ImageView) customView.findViewById(R.id.avatarView);
this.dialogUserPicture.setUser(this.user);
this.dialogUserPicture.setPictureOn(avatarView);
dialogAvatarView = (AvatarView) customView.findViewById(R.id.avatarView);
dialogAvatarView.setUser(user);
}
final ShareEvent event = new ShareEvent();
event.sharedMessage = getString(R.string.share_levelup, level) + " https://habitica.com/social/level-up";
AvatarView avatarView = new AvatarView(this, true, true, true);
avatarView.setUser(user);
avatarView.onAvatarImageReady(avatarImage -> event.shareImage = avatarImage);
AlertDialog alert = new AlertDialog.Builder(this)
.setTitle(R.string.levelup_header)
.setView(customView)
@ -1048,15 +1051,8 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, Ha
checkClassSelection();
})
.setNeutralButton(R.string.share, (dialog, which) -> {
ShareEvent event = new ShareEvent();
event.sharedMessage = getString(R.string.share_levelup, level) + " https://habitica.com/social/level-up";
UserPicture picture = new UserPicture(this, true, true);
picture.setUser(this.user);
picture.setPictureWithRunnable(avatarBitmap -> {
event.shareImage = avatarBitmap;
EventBus.getDefault().post(event);
dialog.dismiss();
});
EventBus.getDefault().post(event);
dialog.dismiss();
})
.create();

View file

@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.databinding.ValueBarBinding;
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel;
import com.habitrpg.android.habitica.ui.helpers.ViewHelper;
import com.habitrpg.android.habitica.userpicture.UserPicture;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
import android.content.Context;
@ -12,11 +12,9 @@ import android.content.res.Resources;
import android.databinding.DataBindingUtil;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
@ -56,8 +54,8 @@ public class PartyMemberRecyclerViewAdapter extends RecyclerView.Adapter<PartyMe
class MemberViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.avatar)
ImageView imageView;
@BindView(R.id.avatarView)
AvatarView avatarView;
@BindView(R.id.username)
TextView userName;
@ -74,7 +72,6 @@ public class PartyMemberRecyclerViewAdapter extends RecyclerView.Adapter<PartyMe
ValueBarBinding hpBar;
Resources resources;
UserPicture userPicture;
public MemberViewHolder(View itemView) {
super(itemView);
@ -87,20 +84,12 @@ public class PartyMemberRecyclerViewAdapter extends RecyclerView.Adapter<PartyMe
hpBar.setPartyMembers(true);
resources = itemView.getResources();
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
if (dpWidth >= 320) {
userPicture = new UserPicture(itemView.getContext(), true, true);
} else {
userPicture = new UserPicture(itemView.getContext(), false, false);
}
}
public void bind(HabitRPGUser user) {
android.content.Context ctx = itemView.getContext();
userPicture.setUser(user);
userPicture.setPictureOn(imageView);
avatarView.setUser(user);
AvatarWithBarsViewModel.setHpBarData(hpBar, user.getStats(), ctx);

View file

@ -1,11 +1,11 @@
package com.habitrpg.android.habitica.ui.fragments.setup;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.habitrpg.android.habitica.ui.activities.SetupActivity;
import com.habitrpg.android.habitica.ui.adapter.setup.CustomizationSetupAdapter;
import com.habitrpg.android.habitica.ui.fragments.BaseFragment;
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration;
import com.habitrpg.android.habitica.userpicture.UserPicture;
import com.magicmicky.habitrpgwrapper.lib.models.Customization;
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
import com.raizlabs.android.dbflow.sql.builder.Condition;
@ -19,7 +19,6 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.List;
@ -38,11 +37,10 @@ public class AvatarSetupFragment extends BaseFragment {
RecyclerView recyclerView;
@BindView(R.id.avatarView)
ImageView avatarView;
AvatarView avatarView;
CustomizationSetupAdapter adapter;
GridLayoutManager layoutManager;
UserPicture userPicture;
@Nullable
@Override
@ -77,7 +75,6 @@ public class AvatarSetupFragment extends BaseFragment {
this.recyclerView.setAdapter(this.adapter);
this.loadCustomizations();
userPicture = new UserPicture(this.activity, true, true);
if (this.user != null) {
this.updateAvatar();
}
@ -115,8 +112,8 @@ public class AvatarSetupFragment extends BaseFragment {
public void setUser(HabitRPGUser user) {
this.user = user;
if (this.userPicture != null) {
this.updateAvatar();
if (avatarView != null) {
updateAvatar();
}
if (this.adapter != null) {
this.adapter.user = user;
@ -125,8 +122,7 @@ public class AvatarSetupFragment extends BaseFragment {
}
private void updateAvatar() {
this.userPicture.setUser(this.user);
this.userPicture.setPictureOn(this.avatarView);
avatarView.setUser(user);
}
}

View file

@ -3,6 +3,7 @@ package com.magicmicky.habitrpgwrapper.lib.models;
import com.google.gson.annotations.SerializedName;
import com.habitrpg.android.habitica.HabitDatabase;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TasksOrder;
import com.raizlabs.android.dbflow.annotation.Column;
@ -15,8 +16,12 @@ import com.raizlabs.android.dbflow.sql.builder.Condition;
import com.raizlabs.android.dbflow.sql.language.Select;
import com.raizlabs.android.dbflow.structure.BaseModel;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@Table(databaseName = HabitDatabase.NAME)
public class HabitRPGUser extends BaseModel {
@ -365,4 +370,73 @@ public class HabitRPGUser extends BaseModel {
return layerNames;
}
public EnumMap<AvatarView.LayerType, String> getAvatarLayerMap() {
EnumMap<AvatarView.LayerType, String> layerMap = new EnumMap<>(AvatarView.LayerType.class);
Preferences prefs = getPreferences();
Outfit outfit = (prefs.getCostume()) ? getItems().getGear().getCostume() : getItems().getGear().getEquipped();
if (!TextUtils.isEmpty(prefs.getChair())) {
layerMap.put(AvatarView.LayerType.CHAIR, prefs.getChair());
}
if (outfit != null) {
if (!TextUtils.isEmpty(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());
}
}
if (prefs.getSleep()) {
layerMap.put(AvatarView.LayerType.ZZZ, "zzz");
}
return layerMap;
}
}

View file

@ -75,4 +75,8 @@ public class Hair extends BaseModel {
public int getFlower() { return flower; }
public void setFlower(int flower) { this.flower = flower; }
public boolean isAvailable(int hairId) {
return hairId > 0;
}
}

View file

@ -9,6 +9,8 @@ import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.structure.BaseModel;
import android.text.TextUtils;
/**
* Created by viirus on 20/07/15.
*/
@ -54,4 +56,7 @@ public class Outfit extends BaseModel {
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");
}
}

View file

@ -190,10 +190,10 @@ public class Preferences extends BaseModel {
}
public String getChair() {
if (chair != null) {
if (chair != null && !chair.equals("none")) {
return "chair_"+chair;
}
return chair;
return null;
}
public void setChair(String chair) {