Revert "Bug fix stats widget (#1664)"

This reverts commit b77158d244.

# Conflicts:
#	Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java
#	Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
This commit is contained in:
Phillip Thelen 2021-11-19 11:43:24 +01:00
parent ea6adb6f16
commit 164fefe228
8 changed files with 118 additions and 274 deletions

View file

@ -296,9 +296,6 @@
<service <service
android:name=".widget.TodosWidgetService" android:name=".widget.TodosWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".widget.AvatarStatsWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
<service android:name=".widget.HabitButtonWidgetService"/> <service android:name=".widget.HabitButtonWidgetService"/>
</application> </application>

View file

@ -5,8 +5,11 @@
android:id="@+id/widget_main_view" android:id="@+id/widget_main_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:background="@drawable/widget_background" android:background="@drawable/widget_background"
android:padding="8dp" android:padding="8dp"
android:elevation="2dp"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:orientation="horizontal" android:orientation="horizontal"

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:id="@+id/widget_main_avatar_view"
android:elevation="2dp">
<ListView
android:id="@+id/widget_avatar_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/widget_avatar_empty_view"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

View file

@ -19,9 +19,7 @@ import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.models.Avatar import com.habitrpg.android.habitica.models.Avatar
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.functions.Consumer import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.subjects.PublishSubject
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -40,7 +38,6 @@ class AvatarView : FrameLayout {
private val avatarMatrix = Matrix() private val avatarMatrix = Matrix()
private val numberLayersInProcess = AtomicInteger(0) private val numberLayersInProcess = AtomicInteger(0)
private var avatarImageConsumer: Consumer<Bitmap?>? = null private var avatarImageConsumer: Consumer<Bitmap?>? = null
private var avatarBitmapSubject: PublishSubject<Bitmap> = PublishSubject.create()
private var avatarBitmap: Bitmap? = null private var avatarBitmap: Bitmap? = null
private var avatarCanvas: Canvas? = null private var avatarCanvas: Canvas? = null
private var currentLayers: Map<LayerType, String>? = null private var currentLayers: Map<LayerType, String>? = null
@ -403,7 +400,6 @@ class AvatarView : FrameLayout {
private fun onLayerComplete() { private fun onLayerComplete() {
if (numberLayersInProcess.decrementAndGet() == 0) { if (numberLayersInProcess.decrementAndGet() == 0) {
avatarImageConsumer?.accept(avatarImage) avatarImageConsumer?.accept(avatarImage)
avatarImage?.let { avatarBitmapSubject.onNext(it) }
} }
} }
@ -411,24 +407,12 @@ class AvatarView : FrameLayout {
avatarImageConsumer = consumer avatarImageConsumer = consumer
if (imageViewHolder.size > 0 && numberLayersInProcess.get() == 0) { if (imageViewHolder.size > 0 && numberLayersInProcess.get() == 0) {
avatarImageConsumer?.accept(avatarImage) avatarImageConsumer?.accept(avatarImage)
avatarImage?.let { avatarBitmapSubject.onNext(it) }
} else { } else {
initAvatarRectMatrix() initAvatarRectMatrix()
showLayers(layerMap) showLayers(layerMap)
} }
} }
fun createAvatarImage(): Bitmap? {
if (imageViewHolder.size > 0 && numberLayersInProcess.get() == 0) {
avatarImageConsumer?.accept(avatarImage)
avatarImage?.let { avatarBitmapSubject.onNext(it) }
} else {
initAvatarRectMatrix()
showLayers(layerMap)
}
return avatarBitmapSubject.hide().firstElement().blockingGet()
}
fun setAvatar(avatar: Avatar) { fun setAvatar(avatar: Avatar) {
val oldUser = this.avatar val oldUser = this.avatar
this.avatar = avatar this.avatar = avatar

View file

@ -1,181 +0,0 @@
package com.habitrpg.android.habitica.widget
import android.appwidget.AppWidgetManager
import android.content.Context
import android.os.Handler
import android.view.View
import android.view.ViewGroup
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.helpers.HealthFormatter
import com.habitrpg.android.habitica.helpers.NumberAbbreviator
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.AvatarView
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class AvatarStatsWidgetFactory(
private val context: Context,
private val widgetId: Int
): RemoteViewsService.RemoteViewsFactory {
private var isInitialized: Boolean = false
@Inject
lateinit var userRepository: UserRepository
private val disposable = CompositeDisposable()
private var user: User? = null
private var shouldLoadData: Boolean = false
private val appWidgetManager = AppWidgetManager.getInstance(context)
private fun setup() {
if (!isInitialized) {
HabiticaBaseApplication.userComponent?.inject(this)
isInitialized = true
}
}
private fun loadUser() {
val mainHandler = Handler(context.mainLooper)
mainHandler.post {
disposable.add(userRepository.getUser()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(
{ user ->
this.user = user
this.shouldLoadData = false
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.widget_avatar_list)
},
RxErrorHandler.handleEmptyError()
)
)
}
}
private fun getRemoteViewForUser(user: User, stats: Stats): RemoteViews {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_avatar_stats)
val options = appWidgetManager.getAppWidgetOptions(widgetId)
val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
val minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
val cols = BaseWidgetProvider.getCellsForSize(minWidth)
val rows = BaseWidgetProvider.getCellsForSize(minHeight)
val showAvatar = cols > 3
val showManaBar = rows > 1
val currentHealth = HealthFormatter.format(stats.hp ?: 0.0)
val currentHealthString = HealthFormatter.formatToString(stats.hp ?: 0.0)
val healthValueString = currentHealthString + "/" + stats.maxHealth
val expValueString = "" + stats.exp?.toInt() + "/" + stats.toNextLevel
val mpValueString = "" + stats.mp?.toInt() + "/" + stats.maxMP
remoteViews.setTextViewText(R.id.TV_hp_value, healthValueString)
remoteViews.setTextViewText(R.id.exp_TV_value, expValueString)
remoteViews.setTextViewText(R.id.mp_TV_value, mpValueString)
remoteViews.setImageViewBitmap(R.id.ic_hp_header, HabiticaIconsHelper.imageOfHeartLightBg())
remoteViews.setImageViewBitmap(R.id.ic_exp_header, HabiticaIconsHelper.imageOfExperience())
remoteViews.setImageViewBitmap(R.id.ic_mp_header, HabiticaIconsHelper.imageOfMagic())
remoteViews.setProgressBar(R.id.hp_bar, stats.maxHealth ?: 0, currentHealth.toInt(), false)
remoteViews.setProgressBar(R.id.exp_bar, stats.toNextLevel ?: 0, stats.exp?.toInt() ?: 0, false)
remoteViews.setProgressBar(R.id.mp_bar, stats.maxMP ?: 0, stats.mp?.toInt() ?: 0, false)
remoteViews.setViewVisibility(R.id.mp_wrapper, if (showManaBar && (stats.habitClass == null || (stats.lvl ?: 0) < 10 || user.preferences?.disableClasses == true)) View.GONE else View.VISIBLE)
remoteViews.setTextViewText(R.id.gold_tv, NumberAbbreviator.abbreviate(context, stats.gp ?: 0.0))
remoteViews.setTextViewText(R.id.gems_tv, (user.balance * 4).toInt().toString())
val hourGlassCount = user.hourglassCount
if (hourGlassCount == 0) {
remoteViews.setViewVisibility(R.id.hourglass_icon, View.GONE)
remoteViews.setViewVisibility(R.id.hourglasses_tv, View.GONE)
} else {
remoteViews.setImageViewBitmap(R.id.hourglass_icon, HabiticaIconsHelper.imageOfHourglass())
remoteViews.setViewVisibility(R.id.hourglass_icon, View.VISIBLE)
remoteViews.setTextViewText(R.id.hourglasses_tv, hourGlassCount.toString())
remoteViews.setViewVisibility(R.id.hourglasses_tv, View.VISIBLE)
}
remoteViews.setImageViewBitmap(R.id.gem_icon, HabiticaIconsHelper.imageOfGem())
remoteViews.setImageViewBitmap(R.id.gold_icon, HabiticaIconsHelper.imageOfGold())
remoteViews.setTextViewText(R.id.lvl_tv, context.getString(R.string.user_level, user.stats?.lvl ?: 0))
if (showAvatar) {
val avatarView =
AvatarView(context, showBackground = true, showMount = true, showPet = true)
val layoutParams = ViewGroup.LayoutParams(140.dpToPx(context), 147.dpToPx(context))
avatarView.layoutParams = layoutParams
avatarView.setAvatar(user)
avatarView.createAvatarImage()?.let { bitmap ->
remoteViews.setImageViewBitmap(R.id.avatar_view, bitmap)
}
}
if (showAvatar) {
remoteViews.setViewVisibility(R.id.avatar_view, View.VISIBLE)
} else {
remoteViews.setViewVisibility(R.id.avatar_view, View.GONE)
}
if (showManaBar) {
remoteViews.setViewVisibility(R.id.detail_info_view, View.VISIBLE)
} else {
remoteViews.setViewVisibility(R.id.detail_info_view, View.GONE)
}
return remoteViews
}
override fun onCreate() {
setup()
loadUser()
}
override fun onDestroy() {
disposable.clear()
}
override fun onDataSetChanged() {
if (shouldLoadData) {
loadUser()
}
shouldLoadData = true
}
override fun getCount(): Int {
return 1
}
override fun getViewAt(p0: Int): RemoteViews {
val user = this.user
val stats = user?.stats
return if (user != null && stats != null) {
getRemoteViewForUser(user, stats)
} else {
RemoteViews(context.packageName, R.layout.widget_avatar_stats)
}
}
override fun getLoadingView() = RemoteViews(context.packageName, R.layout.widget_avatar_stats)
override fun getViewTypeCount(): Int {
return 1
}
override fun getItemId(position: Int) = position.toLong()
override fun hasStableIds() = true
}

View file

@ -2,22 +2,32 @@ package com.habitrpg.android.habitica.widget
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.view.View
import android.os.Bundle import android.view.ViewGroup
import android.widget.RemoteViews import android.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.withImmutableFlag import com.habitrpg.android.habitica.extensions.dpToPx
import com.habitrpg.android.habitica.helpers.HealthFormatter
import com.habitrpg.android.habitica.helpers.NumberAbbreviator
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.AvatarView
import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
class AvatarStatsWidgetProvider : BaseWidgetProvider() { class AvatarStatsWidgetProvider : BaseWidgetProvider() {
private var appWidgetManager: AppWidgetManager? = null private var appWidgetManager: AppWidgetManager? = null
private var showManaBar: Boolean = true
private var showAvatar: Boolean = true
override fun layoutResourceId(): Int { override fun layoutResourceId(): Int {
return R.layout.widget_main_avatar_stats return R.layout.widget_avatar_stats
} }
private fun setUp() { private fun setUp() {
@ -28,35 +38,100 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() {
} }
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
this.setUp() this.setUp()
this.appWidgetManager = appWidgetManager this.appWidgetManager = appWidgetManager
this.context = context this.context = context
for (widgetId in appWidgetIds) {
val intent = Intent(context, AvatarStatsWidgetService::class.java) userRepository.getUser().firstElement()?.subscribe({ this.updateData(it) }, RxErrorHandler.handleEmptyError())
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId) }
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews {
showAvatar = columns > 3
if (showAvatar) {
remoteViews.setViewVisibility(R.id.avatar_view, View.VISIBLE)
} else {
remoteViews.setViewVisibility(R.id.avatar_view, View.GONE)
}
showManaBar = rows > 1
if (rows > 1) {
remoteViews.setViewVisibility(R.id.detail_info_view, View.VISIBLE)
} else {
remoteViews.setViewVisibility(R.id.detail_info_view, View.GONE)
}
return remoteViews
}
private fun updateData(user: User?) {
val context = context
val appWidgetManager = appWidgetManager
val stats = user?.stats
if (user == null || stats == null || context == null || appWidgetManager == null) {
return
}
val thisWidget = ComponentName(context, AvatarStatsWidgetProvider::class.java)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
val currentHealth = HealthFormatter.format(stats.hp ?: 0.0)
val currentHealthString = HealthFormatter.formatToString(stats.hp ?: 0.0)
val healthValueString = currentHealthString + "/" + stats.maxHealth
val expValueString = "" + stats.exp?.toInt() + "/" + stats.toNextLevel
val mpValueString = "" + stats.mp?.toInt() + "/" + stats.maxMP
for (widgetId in allWidgetIds) {
var remoteViews = RemoteViews(context.packageName, R.layout.widget_avatar_stats)
remoteViews.setTextViewText(R.id.TV_hp_value, healthValueString)
remoteViews.setTextViewText(R.id.exp_TV_value, expValueString)
remoteViews.setTextViewText(R.id.mp_TV_value, mpValueString)
remoteViews.setImageViewBitmap(R.id.ic_hp_header, HabiticaIconsHelper.imageOfHeartLightBg())
remoteViews.setImageViewBitmap(R.id.ic_exp_header, HabiticaIconsHelper.imageOfExperience())
remoteViews.setImageViewBitmap(R.id.ic_mp_header, HabiticaIconsHelper.imageOfMagic())
remoteViews.setProgressBar(R.id.hp_bar, stats.maxHealth ?: 0, currentHealth.toInt(), false)
remoteViews.setProgressBar(R.id.exp_bar, stats.toNextLevel ?: 0, stats.exp?.toInt() ?: 0, false)
remoteViews.setProgressBar(R.id.mp_bar, stats.maxMP ?: 0, stats.mp?.toInt() ?: 0, false)
remoteViews.setViewVisibility(R.id.mp_wrapper, if (showManaBar && (stats.habitClass == null || (stats.lvl ?: 0) < 10 || user.preferences?.disableClasses == true)) View.GONE else View.VISIBLE)
remoteViews.setTextViewText(R.id.gold_tv, NumberAbbreviator.abbreviate(context, stats.gp ?: 0.0))
remoteViews.setTextViewText(R.id.gems_tv, (user.balance * 4).toInt().toString())
val hourGlassCount = user.hourglassCount
if (hourGlassCount == 0) {
remoteViews.setViewVisibility(R.id.hourglass_icon, View.GONE)
remoteViews.setViewVisibility(R.id.hourglasses_tv, View.GONE)
} else {
remoteViews.setImageViewBitmap(R.id.hourglass_icon, HabiticaIconsHelper.imageOfHourglass())
remoteViews.setViewVisibility(R.id.hourglass_icon, View.VISIBLE)
remoteViews.setTextViewText(R.id.hourglasses_tv, hourGlassCount.toString())
remoteViews.setViewVisibility(R.id.hourglasses_tv, View.VISIBLE)
}
remoteViews.setImageViewBitmap(R.id.gem_icon, HabiticaIconsHelper.imageOfGem())
remoteViews.setImageViewBitmap(R.id.gold_icon, HabiticaIconsHelper.imageOfGold())
remoteViews.setTextViewText(R.id.lvl_tv, context.getString(R.string.user_level, user.stats?.lvl ?: 0))
if (showAvatar) {
val avatarView =
AvatarView(context, showBackground = true, showMount = true, showPet = true)
val layoutParams = ViewGroup.LayoutParams(140.dpToPx(context), 147.dpToPx(context))
avatarView.layoutParams = layoutParams
avatarView.setAvatar(user)
val finalRemoteViews = remoteViews
avatarView.onAvatarImageReady { bitmap ->
finalRemoteViews.setImageViewBitmap(R.id.avatar_view, bitmap)
appWidgetManager.partiallyUpdateAppWidget(allWidgetIds, finalRemoteViews)
}
}
val openAppIntent = Intent(context.applicationContext, MainActivity::class.java) val openAppIntent = Intent(context.applicationContext, MainActivity::class.java)
val openApp = PendingIntent.getActivity(context, 0, openAppIntent, withImmutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)) val openApp = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT)
remoteViews.setOnClickPendingIntent(R.id.widget_main_view, openApp)
val options = appWidgetManager.getAppWidgetOptions(widgetId)
remoteViews = sizeRemoteViews(context, options, widgetId)
val remoteViews = RemoteViews(context.packageName, R.layout.widget_main_avatar_stats)
remoteViews.setRemoteAdapter(R.id.widget_avatar_list, intent)
remoteViews.setEmptyView(R.id.widget_avatar_list, R.id.widget_avatar_empty_view)
remoteViews.setOnClickPendingIntent(R.id.widget_main_avatar_view, openApp)
appWidgetManager.updateAppWidget(widgetId, remoteViews) appWidgetManager.updateAppWidget(widgetId, remoteViews)
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.widget_avatar_list)
} }
} }
override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle
) {
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_avatar_list)
}
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews = remoteViews
} }

View file

@ -1,12 +0,0 @@
package com.habitrpg.android.habitica.widget
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.widget.RemoteViewsService
class AvatarStatsWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
val widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0)
return AvatarStatsWidgetFactory(this.applicationContext, widgetId)
}
}

View file

@ -15,23 +15,6 @@ import javax.inject.Inject
abstract class BaseWidgetProvider : AppWidgetProvider() { abstract class BaseWidgetProvider : AppWidgetProvider() {
companion object {
/**
* Returns number of cells needed for given size of the widget.<br></br>
* see http://stackoverflow.com/questions/14270138/dynamically-adjusting-widgets-content-and-layout-to-the-size-the-user-defined-t
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/
fun getCellsForSize(size: Int): Int {
var n = 2
while (70 * n - 30 < size) {
++n
}
return n - 1
}
}
@Inject @Inject
lateinit var userRepository: UserRepository lateinit var userRepository: UserRepository
@ -39,6 +22,21 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
protected var context: Context? = null protected var context: Context? = null
/**
* Returns number of cells needed for given size of the widget.<br></br>
* see http://stackoverflow.com/questions/14270138/dynamically-adjusting-widgets-content-and-layout-to-the-size-the-user-defined-t
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/
private fun getCellsForSize(size: Int): Int {
var n = 2
while (70 * n - 30 < size) {
++n
}
return n - 1
}
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) { override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) {
this.context = context this.context = context
val options = appWidgetManager.getAppWidgetOptions(appWidgetId) val options = appWidgetManager.getAppWidgetOptions(appWidgetId)