diff --git a/.travis.yml b/.travis.yml index ae6569942..678714f62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 3c0d94e5f..5a50b6d4d 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -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' diff --git a/Habitica/res/layout-w320dp/party_member.xml b/Habitica/res/layout-w320dp/party_member.xml index b45981566..c859cfa3f 100644 --- a/Habitica/res/layout-w320dp/party_member.xml +++ b/Habitica/res/layout-w320dp/party_member.xml @@ -1,5 +1,6 @@ @@ -10,13 +11,14 @@ android:layout_margin="5dp" android:orientation="horizontal"> - + android:layout_height="147dp" + android:layout_marginRight="5dp" + app:showBackground="true" + app:showMount="true" + app:showPet="true" /> - + android:layout_height="@dimen/avatar_header_height" /> - + android:layout_height="@dimen/avatar_header_height" /> - + android:layout_height="@dimen/avatar_header_height" /> - + android:layout_height="@dimen/avatar_header_height" /> - + android:layout_marginRight="16dp" + app:showBackground="true" + app:showMount="true" + app:showPet="true" /> - + android:layout_gravity="center_horizontal" + app:showBackground="false" + app:showMount="false" + app:showPet="false" /> - + android:layout_gravity="center_horizontal" + app:showBackground="false" + app:showMount="false" + app:showPet="false" /> @@ -8,10 +9,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + app:showBackground="true" + app:showMount="true" + app:showPet="true" /> @@ -10,13 +11,14 @@ style="@style/CardContent" android:orientation="horizontal"> - + android:layout_height="90dp" + android:layout_marginRight="5dp" + app:showBackground="false" + app:showMount="false" + app:showPet="false" /> + + + + + + diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaApplication.java b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaApplication.java index 0825dfd09..861d80d01 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaApplication.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaApplication.java @@ -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() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.java new file mode 100644 index 000000000..aaa9cc064 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.java @@ -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 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 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 multiDraweeHolder = new MultiDraweeHolder<>(); + private HabitRPGUser user; + private RectF avatarRectF; + private Matrix matrix = new Matrix(); + private AtomicInteger numberLayersInProcess = new AtomicInteger(0); + private Consumer 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 layerMap) { + if (multiDraweeHolder.size() > 0) return; + int i = 0; + + numberLayersInProcess.set(layerMap.size()); + + for (Map.Entry 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 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() { + @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 getLayerMap() { + assert user != null; + return getLayerMap(user, true); + } + + private Map getLayerMap(@NonNull HabitRPGUser user, boolean resetHasAttributes) { + EnumMap 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 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 currentLayerMap = getLayerMap(oldUser, false); + Map 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 { + void accept(T t); + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java index 40f750c57..f29910609 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java @@ -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(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.java index 4d2476eb1..d7d97191f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.java @@ -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, 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, 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, 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, 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, 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, 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(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.java index 621d1267e..b5781f81b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/PartyMemberRecyclerViewAdapter.java @@ -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= 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); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.java index 282000b3c..9b3f28ddd 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/AvatarSetupFragment.java @@ -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); } } diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/HabitRPGUser.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/HabitRPGUser.java index c602d8feb..8a1fb062b 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/HabitRPGUser.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/HabitRPGUser.java @@ -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 getAvatarLayerMap() { + EnumMap 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; + } } diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Hair.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Hair.java index 6829241d5..69dc72e6a 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Hair.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Hair.java @@ -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; + } } diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Outfit.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Outfit.java index ff92c63e7..ba899a95c 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Outfit.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Outfit.java @@ -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"); + } } diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java index a7723ca6c..f336c1e0b 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/Preferences.java @@ -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) {