diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index c99fdc7c8..d9bba09b8 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -296,9 +296,6 @@ - diff --git a/Habitica/res/layout/widget_avatar_stats.xml b/Habitica/res/layout/widget_avatar_stats.xml index baf438406..4f738032d 100644 --- a/Habitica/res/layout/widget_avatar_stats.xml +++ b/Habitica/res/layout/widget_avatar_stats.xml @@ -5,8 +5,11 @@ android:id="@+id/widget_main_view" android:layout_width="match_parent" android:layout_height="wrap_content" + android:focusable="true" + android:focusableInTouchMode="true" android:background="@drawable/widget_background" android:padding="8dp" + android:elevation="2dp" android:orientation="vertical"> - - - - \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt index e1620a012..b3166efa8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarView.kt @@ -19,9 +19,7 @@ import com.habitrpg.android.habitica.extensions.dpToPx import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.models.Avatar import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils -import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.functions.Consumer -import io.reactivex.rxjava3.subjects.PublishSubject import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -40,7 +38,6 @@ class AvatarView : FrameLayout { private val avatarMatrix = Matrix() private val numberLayersInProcess = AtomicInteger(0) private var avatarImageConsumer: Consumer? = null - private var avatarBitmapSubject: PublishSubject = PublishSubject.create() private var avatarBitmap: Bitmap? = null private var avatarCanvas: Canvas? = null private var currentLayers: Map? = null @@ -403,7 +400,6 @@ class AvatarView : FrameLayout { private fun onLayerComplete() { if (numberLayersInProcess.decrementAndGet() == 0) { avatarImageConsumer?.accept(avatarImage) - avatarImage?.let { avatarBitmapSubject.onNext(it) } } } @@ -411,24 +407,12 @@ class AvatarView : FrameLayout { avatarImageConsumer = consumer if (imageViewHolder.size > 0 && numberLayersInProcess.get() == 0) { avatarImageConsumer?.accept(avatarImage) - avatarImage?.let { avatarBitmapSubject.onNext(it) } } else { initAvatarRectMatrix() 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) { val oldUser = this.avatar this.avatar = avatar diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt deleted file mode 100644 index fa8842ea7..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetFactory.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt index ad0f810c7..ceb4369a8 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.kt @@ -2,22 +2,32 @@ package com.habitrpg.android.habitica.widget import android.app.PendingIntent import android.appwidget.AppWidgetManager +import android.content.ComponentName import android.content.Context import android.content.Intent -import android.net.Uri -import android.os.Bundle +import android.view.View +import android.view.ViewGroup import android.widget.RemoteViews import com.habitrpg.android.habitica.HabiticaBaseApplication 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.views.HabiticaIconsHelper class AvatarStatsWidgetProvider : BaseWidgetProvider() { private var appWidgetManager: AppWidgetManager? = null + private var showManaBar: Boolean = true + private var showAvatar: Boolean = true + override fun layoutResourceId(): Int { - return R.layout.widget_main_avatar_stats + return R.layout.widget_avatar_stats } private fun setUp() { @@ -28,35 +38,100 @@ class AvatarStatsWidgetProvider : BaseWidgetProvider() { } override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + super.onUpdate(context, appWidgetManager, appWidgetIds) this.setUp() this.appWidgetManager = appWidgetManager this.context = context - for (widgetId in appWidgetIds) { - val intent = Intent(context, AvatarStatsWidgetService::class.java) - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId) - intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)) + userRepository.getUser().firstElement()?.subscribe({ this.updateData(it) }, RxErrorHandler.handleEmptyError()) + } + + 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 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.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 } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetService.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetService.kt deleted file mode 100644 index 167b5745c..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetService.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt index b29cc969c..9f45144a3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt @@ -15,23 +15,6 @@ import javax.inject.Inject abstract class BaseWidgetProvider : AppWidgetProvider() { - companion object { - /** - * Returns number of cells needed for given size of the widget.

- * 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 lateinit var userRepository: UserRepository @@ -39,6 +22,21 @@ abstract class BaseWidgetProvider : AppWidgetProvider() { protected var context: Context? = null + /** + * Returns number of cells needed for given size of the widget.

+ * 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) { this.context = context val options = appWidgetManager.getAppWidgetOptions(appWidgetId)