Refactor widgets

This commit is contained in:
Phillip Thelen 2019-08-23 12:24:47 +02:00
parent 10268c50dd
commit 7e4c01f078
32 changed files with 704 additions and 862 deletions

View file

@ -22,6 +22,7 @@ import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import retrofit2.http.*
@JvmSuppressWildcards
interface ApiService {
@get:GET("status")
val status: Flowable<HabitResponse<Status>>

View file

@ -16,6 +16,7 @@ import java.util.*
interface TaskRepository : BaseRepository {
fun getTasks(taskType: String, userID: String): Flowable<RealmResults<Task>>
fun getTasks(userId: String): Flowable<RealmResults<Task>>
fun getCurrentUserTasks(taskType: String): Flowable<RealmResults<Task>>
fun getTasksOfType(taskType: String): Flowable<RealmResults<Task>>
fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList)

View file

@ -32,6 +32,9 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
override fun getTasks(userId: String): Flowable<RealmResults<Task>> =
this.localRepository.getTasks(userId)
override fun getCurrentUserTasks(taskType: String): Flowable<RealmResults<Task>> =
this.localRepository.getTasks(taskType, userID)
override fun saveTasks(userId: String, order: TasksOrder, tasks: TaskList) {
localRepository.saveTasks(userId, order, tasks)
}

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.interactors
import android.graphics.Bitmap
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.habitrpg.android.habitica.R
@ -12,8 +11,8 @@ import com.habitrpg.android.habitica.helpers.SoundManager
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.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.Flowable
import io.reactivex.functions.Consumer
import org.greenrobot.eventbus.EventBus
@ -62,13 +61,9 @@ constructor(private val soundManager: SoundManager, threadExecutor: ThreadExecut
val event = ShareEvent()
event.sharedMessage = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel) + " https://habitica.com/social/level-UP"
val avatarView = AvatarView(requestValues.activity, true, true, true)
val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true)
avatarView.setAvatar(requestValues.user)
avatarView.onAvatarImageReady(object : AvatarView.Consumer<Bitmap?> {
override fun accept(t: Bitmap?) {
event.shareImage = t
}
})
avatarView.onAvatarImageReady(Consumer { t -> event.shareImage = t })
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))

View file

@ -188,10 +188,8 @@ open class User : RealmObject(), Avatar, VersionedObject {
return (this.balance * 4).toInt()
}
override fun getHourglassCount(): Int? {
return if (purchased != null) {
purchased?.plan?.consecutive?.trinkets
} else 0
override fun getHourglassCount(): Int {
return purchased?.plan?.consecutive?.trinkets ?: 0
}
override fun getCostume(): Outfit? {

View file

@ -18,6 +18,7 @@ import com.facebook.imagepipeline.image.ImageInfo
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.models.Avatar
import io.reactivex.functions.Consumer
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
@ -518,10 +519,6 @@ class AvatarView : View {
PET
}
interface Consumer<in T> {
fun accept(t: T)
}
companion object {
const val IMAGE_URI_ROOT = "https://habitica-assets.s3.amazonaws.com/mobileApp/images/"
private const val TAG = "AvatarView"

View file

@ -1,72 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.widget.RemoteViews;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.models.tasks.Task;
public class AddTaskWidgetProvider extends BaseWidgetProvider {
@Override
public int layoutResourceId() {
return R.layout.widget_add_task;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
// Get all ids
ComponentName thisWidget = new ComponentName(context,
AddTaskWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
if (Build.VERSION.SDK_INT >= 16) {
for (int widgetId : allWidgetIds) {
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId));
}
}
}
@Override
public RemoteViews configureRemoteViews(RemoteViews remoteViews, int widgetId, int columns, int rows) {
String selectedTaskType = getSelectedTaskType(widgetId);
String addText = "";
int backgroundResource = R.drawable.widget_add_habit_background;
switch (selectedTaskType) {
case Task.TYPE_HABIT:
addText = this.getContext().getResources().getString(R.string.add_habit);
backgroundResource = R.drawable.widget_add_habit_background;
break;
case Task.TYPE_DAILY:
addText = this.getContext().getResources().getString(R.string.add_daily);
backgroundResource = R.drawable.widget_add_daily_background;
break;
case Task.TYPE_TODO:
addText = this.getContext().getResources().getString(R.string.add_todo);
backgroundResource = R.drawable.widget_add_todo_background;
break;
case Task.TYPE_REWARD:
addText = this.getContext().getResources().getString(R.string.add_reward);
backgroundResource = R.drawable.widget_add_reward_background;
break;
}
remoteViews.setTextViewText(R.id.add_task_text, addText);
remoteViews.setInt(R.id.add_task_icon, "setBackgroundResource", backgroundResource);
return remoteViews;
}
private String getSelectedTaskType(int widgetId) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getContext());
return preferences.getString("add_task_widget_" + widgetId, Task.TYPE_HABIT);
}
}

View file

@ -0,0 +1,63 @@
package com.habitrpg.android.habitica.widget
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.preference.PreferenceManager
import android.widget.RemoteViews
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
class AddTaskWidgetProvider : BaseWidgetProvider() {
override fun layoutResourceId(): Int {
return R.layout.widget_add_task
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
// Get all ids
val thisWidget = ComponentName(context,
AddTaskWidgetProvider::class.java)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
for (widgetId in allWidgetIds) {
val options = appWidgetManager.getAppWidgetOptions(widgetId)
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId))
}
}
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews {
val selectedTaskType = getSelectedTaskType(widgetId)
var addText: String? = ""
var backgroundResource = R.drawable.widget_add_habit_background
when (selectedTaskType) {
Task.TYPE_HABIT -> {
addText = context?.resources?.getString(R.string.add_habit)
backgroundResource = R.drawable.widget_add_habit_background
}
Task.TYPE_DAILY -> {
addText = context?.resources?.getString(R.string.add_daily)
backgroundResource = R.drawable.widget_add_daily_background
}
Task.TYPE_TODO -> {
addText = context?.resources?.getString(R.string.add_todo)
backgroundResource = R.drawable.widget_add_todo_background
}
Task.TYPE_REWARD -> {
addText = context?.resources?.getString(R.string.add_reward)
backgroundResource = R.drawable.widget_add_reward_background
}
}
remoteViews.setTextViewText(R.id.add_task_text, addText)
remoteViews.setInt(R.id.add_task_icon, "setBackgroundResource", backgroundResource)
return remoteViews
}
private fun getSelectedTaskType(widgetId: Int): String {
val preferences = PreferenceManager.getDefaultSharedPreferences(this.context)
return preferences.getString("add_task_widget_$widgetId", Task.TYPE_HABIT) ?: ""
}
}

View file

@ -1,154 +0,0 @@
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.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.data.UserRepository;
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.modules.AppModule;
import com.habitrpg.android.habitica.ui.AvatarView;
import com.habitrpg.android.habitica.ui.activities.MainActivity;
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
public class AvatarStatsWidgetProvider extends BaseWidgetProvider {
private AppWidgetManager appWidgetManager;
@Override
public int layoutResourceId() {
return R.layout.widget_avatar_stats;
}
@Inject
@Named(AppModule.NAMED_USER_ID)
String userId;
@Inject
UserRepository userRepository;
private Boolean showManaBar = true;
private void setUp() {
if (userRepository == null) {
Objects.requireNonNull(HabiticaBaseApplication.Companion.getUserComponent()).inject(this);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
this.setUp();
this.appWidgetManager = appWidgetManager;
this.setContext(context);
userRepository.getUser(userId).firstElement().subscribe(this::updateData, RxErrorHandler.Companion.handleEmptyError());
}
@NonNull
@Override
public RemoteViews configureRemoteViews(@NonNull RemoteViews remoteViews, int widgetId, int columns, int rows) {
if (columns > 3) {
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.mp_wrapper, View.VISIBLE);
remoteViews.setViewVisibility(R.id.detail_info_view, View.VISIBLE);
} else {
remoteViews.setViewVisibility(R.id.mp_wrapper, View.GONE);
remoteViews.setViewVisibility(R.id.detail_info_view, View.GONE);
}
return remoteViews;
}
private void updateData(User user) {
if (user == null || user.getStats() == null) {
return;
}
Stats stats = user.getStats();
ComponentName thisWidget = new ComponentName(getContext(), AvatarStatsWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
Double currentHealth = HealthFormatter.format(stats.getHp());
String currentHealthString = HealthFormatter.formatToString(stats.getHp());
String healthValueString = currentHealthString + "/" + stats.getMaxHealth();
String expValueString = "" + stats.getExp().intValue() + "/" + stats.getToNextLevel();
String mpValueString = "" + stats.getMp().intValue() + "/" + stats.getMaxMP();
for (int widgetId : allWidgetIds) {
RemoteViews remoteViews = new RemoteViews(getContext().getPackageName(), 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.imageOfHeartDarkBg());
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.getMaxHealth(), currentHealth.intValue(), false);
remoteViews.setProgressBar(R.id.exp_bar, stats.getToNextLevel(), stats.getExp().intValue(), false);
remoteViews.setProgressBar(R.id.mp_bar, stats.getMaxMP(), stats.getMp().intValue(), false);
remoteViews.setViewVisibility(R.id.mp_wrapper, showManaBar && ( stats.getHabitClass() == null || stats.getLvl() < 10 || user.getPreferences().getDisableClasses()) ? View.GONE : View.VISIBLE);
remoteViews.setTextViewText(R.id.gold_tv, NumberAbbreviator.INSTANCE.abbreviate(getContext(), stats.getGp()));
remoteViews.setTextViewText(R.id.gems_tv, String.valueOf((int) (user.getBalance() * 4)));
int hourGlassCount = user.getHourglassCount();
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, String.valueOf(hourGlassCount));
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, getContext().getString(R.string.user_level, user.getStats().getLvl()));
AvatarView avatarView = new AvatarView(getContext(), true, true, true);
avatarView.setAvatar(user);
RemoteViews finalRemoteViews = remoteViews;
avatarView.onAvatarImageReady(bitmap -> {
finalRemoteViews.setImageViewBitmap(R.id.avatar_view, bitmap);
appWidgetManager.partiallyUpdateAppWidget(allWidgetIds, finalRemoteViews);
});
//If user click on life and xp: open the app
Intent openAppIntent = new Intent(getContext().getApplicationContext(), MainActivity.class);
PendingIntent openApp = PendingIntent.getActivity(getContext(), 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widget_main_view, openApp);
if (Build.VERSION.SDK_INT >= 16) {
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);
remoteViews = sizeRemoteViews(getContext(), options, widgetId);
}
appWidgetManager.updateAppWidget(widgetId, remoteViews);
}
}
}

View file

@ -0,0 +1,134 @@
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.view.View
import android.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
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
import io.reactivex.functions.Consumer
class AvatarStatsWidgetProvider : BaseWidgetProvider() {
private var appWidgetManager: AppWidgetManager? = null
private var showManaBar: Boolean = true
override fun layoutResourceId(): Int {
return R.layout.widget_avatar_stats
}
private fun setUp() {
if (!hasInjected) {
hasInjected = true
HabiticaBaseApplication.userComponent?.inject(this)
}
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
this.setUp()
this.appWidgetManager = appWidgetManager
this.context = context
userRepository?.getUser()?.firstElement()?.subscribe(Consumer<User> { this.updateData(it) }, RxErrorHandler.handleEmptyError())
}
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews {
if (columns > 3) {
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.mp_wrapper, View.VISIBLE)
remoteViews.setViewVisibility(R.id.detail_info_view, View.VISIBLE)
} else {
remoteViews.setViewVisibility(R.id.mp_wrapper, View.GONE)
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.imageOfHeartDarkBg())
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))
val avatarView = AvatarView(context, showBackground = true, showMount = true, showPet = true)
avatarView.setAvatar(user)
val finalRemoteViews = remoteViews
avatarView.onAvatarImageReady(Consumer { bitmap ->
finalRemoteViews.setImageViewBitmap(R.id.avatar_view, bitmap)
appWidgetManager.partiallyUpdateAppWidget(allWidgetIds, finalRemoteViews)
})
//If user click on life and xp: open the app
val openAppIntent = Intent(context.applicationContext, MainActivity::class.java)
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)
appWidgetManager.updateAppWidget(widgetId, remoteViews)
}
}
}

View file

@ -19,6 +19,8 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
@Inject
lateinit var userRepository: UserRepository
var hasInjected = false
protected var context: Context? = null
/**
@ -62,7 +64,7 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
return configureRemoteViews(remoteViews, widgetId, columns, rows)
}
protected fun showToastForTaskDirection(context: Context, data: TaskScoringResult?, userID: String) {
protected fun showToastForTaskDirection(context: Context, data: TaskScoringResult?) {
if (data != null) {
val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(context, data.experienceDelta!!, data.healthDelta!!, data.goldDelta!!, data.manaDelta!!)
val toast = Toast.makeText(context, pair.first, Toast.LENGTH_LONG)

View file

@ -1,14 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.content.Context;
import android.content.Intent;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.models.tasks.Task;
public class DailiesListFactory extends TaskListFactory {
public DailiesListFactory(Context context, Intent intent) {
super(context, intent, Task.TYPE_DAILY, R.layout.widget_dailies_list_row, R.id.dailies_text);
}
}

View file

@ -0,0 +1,10 @@
package com.habitrpg.android.habitica.widget
import android.content.Context
import android.content.Intent
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
class DailiesListFactory(context: Context, intent: Intent) : TaskListFactory(context, intent, Task.TYPE_DAILY, R.layout.widget_dailies_list_row, R.id.dailies_text)

View file

@ -1,46 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.content.Context;
import com.habitrpg.android.habitica.HabiticaApplication;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.api.HostConfig;
import com.habitrpg.android.habitica.data.ApiClient;
import java.util.Objects;
import javax.inject.Inject;
public class DailiesWidgetProvider extends TaskListWidgetProvider {
public static final String DAILY_ACTION = "com.habitrpg.android.habitica.DAILY_ACTION";
public static final String TASK_ID_ITEM = "com.habitrpg.android.habitica.TASK_ID_ITEM";
@Inject
ApiClient apiClient;
@Inject
HostConfig hostConfig;
private void setUp(Context context) {
if (apiClient == null) {
Objects.requireNonNull(HabiticaBaseApplication.Companion.getUserComponent()).inject(this);
}
}
@Override
protected Class getServiceClass() {
return DailiesWidgetService.class;
}
@Override
protected Class getProviderClass() {
return DailiesWidgetProvider.class;
}
@Override
protected int getTitleResId() {
return R.string.dailies;
}
}

View file

@ -0,0 +1,13 @@
package com.habitrpg.android.habitica.widget
import com.habitrpg.android.habitica.R
class DailiesWidgetProvider : TaskListWidgetProvider() {
override val serviceClass: Class<*>
get() = DailiesWidgetService::class.java
override val providerClass: Class<*>
get() = DailiesWidgetProvider::class.java
override val titleResId: Int
get() = R.string.dailies
}

View file

@ -1,14 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.content.Intent;
import android.widget.RemoteViewsService;
public class DailiesWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new DailiesListFactory(this.getApplicationContext(), intent);
}
}

View file

@ -0,0 +1,13 @@
package com.habitrpg.android.habitica.widget
import android.content.Intent
import android.widget.RemoteViewsService
class DailiesWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
return DailiesListFactory(this.applicationContext, intent)
}
}

View file

@ -1,94 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.widget.RemoteViews;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.models.responses.TaskDirection;
import com.habitrpg.android.habitica.modules.AppModule;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
public class HabitButtonWidgetProvider extends BaseWidgetProvider {
public static final String HABIT_ACTION = "com.habitrpg.android.habitica.HABIT_ACTION";
public static final String TASK_ID = "com.habitrpg.android.habitica.TASK_ID_ITEM";
public static final String TASK_DIRECTION = "com.habitrpg.android.habitica.TASK_DIRECTION";
@Inject
public TaskRepository taskRepository;
@Inject
@Named(AppModule.NAMED_USER_ID)
public String userId;
private void setUp() {
if (taskRepository == null) {
Objects.requireNonNull(HabiticaBaseApplication.Companion.getUserComponent()).inject(this);
}
}
@Override
public int layoutResourceId() {
return R.layout.widget_habit_button;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
setUp();
ComponentName thisWidget = new ComponentName(context,
HabitButtonWidgetProvider.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
for (int widgetId : allWidgetIds) {
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId));
}
// Build the intent to call the service
Intent intent = new Intent(context.getApplicationContext(), HabitButtonWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
try {
context.startService(intent);
} catch (IllegalStateException ignore) {
//TODO: Make this play more nicely with Android 8
}
}
@Override
public void onReceive(Context context, Intent intent) {
setUp();
if (intent.getAction().equals(HABIT_ACTION)) {
AppWidgetManager mgr = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
String taskId = intent.getStringExtra(TASK_ID);
String direction = intent.getStringExtra(TASK_DIRECTION);
int[] ids = {appWidgetId};
if (taskId != null) {
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, TaskDirection.UP.getText().equals(direction), false, null))
.subscribe(taskDirectionData -> showToastForTaskDirection(context, taskDirectionData, userId), RxErrorHandler.Companion.handleEmptyError(), () -> this.onUpdate(context, mgr, ids));
}
}
super.onReceive(context, intent);
}
@Override
public RemoteViews configureRemoteViews(RemoteViews remoteViews, int widgetId, int columns, int rows) {
return remoteViews;
}
}

View file

@ -0,0 +1,84 @@
package com.habitrpg.android.habitica.widget
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.responses.TaskDirection
import io.reactivex.functions.Action
import io.reactivex.functions.Consumer
import javax.inject.Inject
class HabitButtonWidgetProvider : BaseWidgetProvider() {
@Inject
lateinit var taskRepository: TaskRepository
private fun setUp() {
if (!hasInjected) {
hasInjected = true
HabiticaBaseApplication.userComponent?.inject(this)
}
}
override fun layoutResourceId(): Int {
return R.layout.widget_habit_button
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
setUp()
val thisWidget = ComponentName(context,
HabitButtonWidgetProvider::class.java)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
for (widgetId in allWidgetIds) {
val options = appWidgetManager.getAppWidgetOptions(widgetId)
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId))
}
// Build the intent to call the service
val intent = Intent(context.applicationContext, HabitButtonWidgetService::class.java)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds)
try {
context.startService(intent)
} catch (ignore: IllegalStateException) {
}
}
override fun onReceive(context: Context, intent: Intent) {
setUp()
if (intent.action == HABIT_ACTION) {
val mgr = AppWidgetManager.getInstance(context)
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID)
val taskId = intent.getStringExtra(TASK_ID)
val direction = intent.getStringExtra(TASK_DIRECTION)
val ids = intArrayOf(appWidgetId)
if (taskId != null) {
userRepository.getUser().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, TaskDirection.UP.text == direction, false, null) }
.subscribe(Consumer { taskDirectionData -> showToastForTaskDirection(context, taskDirectionData) }, RxErrorHandler.handleEmptyError(), Action { this.onUpdate(context, mgr, ids) })
}
}
super.onReceive(context, intent)
}
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews {
return remoteViews
}
companion object {
const val HABIT_ACTION = "com.habitrpg.android.habitica.HABIT_ACTION"
const val TASK_ID = "com.habitrpg.android.habitica.TASK_ID_ITEM"
const val TASK_DIRECTION = "com.habitrpg.android.habitica.TASK_DIRECTION"
}
}

View file

@ -1,130 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.IBinder;
import androidx.core.content.ContextCompat;
import android.text.SpannableStringBuilder;
import android.text.style.DynamicDrawableSpan;
import android.view.View;
import android.widget.RemoteViews;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.models.responses.TaskDirection;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.modules.AppModule;
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser;
import net.pherth.android.emoji_library.EmojiHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
public class HabitButtonWidgetService extends Service {
@Inject
@Named(AppModule.NAMED_USER_ID)
public String userId;
@Inject
public SharedPreferences sharedPreferences;
@Inject
public Resources resources;
@Inject
public Context context;
@Inject
TaskRepository taskRepository;
private AppWidgetManager appWidgetManager;
private Map<String, Integer> taskMapping;
private int[] allWidgetIds;
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
Objects.requireNonNull(HabiticaBaseApplication.Companion.getUserComponent()).inject(this);
this.appWidgetManager = AppWidgetManager.getInstance(this);
ComponentName thisWidget = new ComponentName(this, HabitButtonWidgetProvider.class);
allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
makeTaskMapping();
for (String taskid : this.taskMapping.keySet()) {
taskRepository.getUnmanagedTask(taskid).firstElement().subscribe(this::updateData, RxErrorHandler.Companion.handleEmptyError());
}
stopSelf();
return START_STICKY;
}
private void updateData(Task task) {
RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.widget_habit_button);
if (task != null && task.isValid()) {
CharSequence parsedText = MarkdownParser.INSTANCE.parseMarkdown(task.getText());
SpannableStringBuilder builder = new SpannableStringBuilder(parsedText);
EmojiHandler.addEmojis(this.context, builder, 16, DynamicDrawableSpan.ALIGN_BASELINE, 16, 0, -1, false);
remoteViews.setTextViewText(R.id.habit_title, builder);
if (!task.getUp()) {
remoteViews.setViewVisibility(R.id.btnPlusWrapper, View.GONE);
remoteViews.setOnClickPendingIntent(R.id.btnPlusWrapper, null);
} else {
remoteViews.setViewVisibility(R.id.btnPlusWrapper, View.VISIBLE);
remoteViews.setInt(R.id.btnPlus, "setBackgroundColor", ContextCompat.getColor(context, task.getLightTaskColor()));
remoteViews.setOnClickPendingIntent(R.id.btnPlusWrapper, getPendingIntent(task.getId(), TaskDirection.UP.getText(), taskMapping.get(task.getId())));
}
if (!task.getDown()) {
remoteViews.setViewVisibility(R.id.btnMinusWrapper, View.GONE);
remoteViews.setOnClickPendingIntent(R.id.btnMinusWrapper, null);
} else {
remoteViews.setViewVisibility(R.id.btnMinusWrapper, View.VISIBLE);
remoteViews.setInt(R.id.btnMinus, "setBackgroundColor", ContextCompat.getColor(context, task.getMediumTaskColor()));
remoteViews.setOnClickPendingIntent(R.id.btnMinusWrapper, getPendingIntent(task.getId(), TaskDirection.DOWN.getText(), taskMapping.get(task.getId())));
}
if (taskMapping.get(task.getId()) != null && remoteViews != null) {
appWidgetManager.updateAppWidget(taskMapping.get(task.getId()), remoteViews);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void makeTaskMapping() {
this.taskMapping = new HashMap<>();
for (int widgetId : allWidgetIds) {
String taskId = getTaskId(widgetId);
if (!taskId.equals("")) {
this.taskMapping.put(taskId, widgetId);
}
}
}
private String getTaskId(int widgetId) {
return sharedPreferences.getString("habit_button_widget_" + widgetId, "");
}
private PendingIntent getPendingIntent(String taskId, String direction, int widgetId) {
Intent taskIntent = new Intent(context, HabitButtonWidgetProvider.class);
taskIntent.setAction(HabitButtonWidgetProvider.HABIT_ACTION);
taskIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
taskIntent.putExtra(HabitButtonWidgetProvider.TASK_ID, taskId);
taskIntent.putExtra(HabitButtonWidgetProvider.TASK_DIRECTION, direction);
return PendingIntent.getBroadcast(context, widgetId + direction.hashCode(), taskIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}

View file

@ -0,0 +1,115 @@
package com.habitrpg.android.habitica.widget
import android.app.PendingIntent
import android.app.Service
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.IBinder
import android.text.SpannableStringBuilder
import android.text.style.DynamicDrawableSpan
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import io.reactivex.functions.Consumer
import net.pherth.android.emoji_library.EmojiHandler
import java.util.*
import javax.inject.Inject
class HabitButtonWidgetService : Service() {
@Inject
lateinit var sharedPreferences: SharedPreferences
@Inject
lateinit var context: Context
@Inject
lateinit var taskRepository: TaskRepository
private var appWidgetManager: AppWidgetManager? = null
private var taskMapping = mutableMapOf<String, Int>()
private var allWidgetIds: IntArray? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
HabiticaBaseApplication.userComponent?.inject(this)
this.appWidgetManager = AppWidgetManager.getInstance(this)
val thisWidget = ComponentName(this, HabitButtonWidgetProvider::class.java)
allWidgetIds = appWidgetManager?.getAppWidgetIds(thisWidget)
makeTaskMapping()
for (taskid in this.taskMapping.keys) {
taskRepository.getUnmanagedTask(taskid).firstElement().subscribe(Consumer<Task> { this.updateData(it) }, RxErrorHandler.handleEmptyError())
}
stopSelf()
return Service.START_STICKY
}
private fun updateData(task: Task?) {
val remoteViews = RemoteViews(this.packageName, R.layout.widget_habit_button)
if (task != null && task.isValid) {
val parsedText = MarkdownParser.parseMarkdown(task.text)
val builder = SpannableStringBuilder(parsedText)
EmojiHandler.addEmojis(this.context, builder, 16, DynamicDrawableSpan.ALIGN_BASELINE, 16, 0, -1, false)
remoteViews.setTextViewText(R.id.habit_title, builder)
if (task.up != true) {
remoteViews.setViewVisibility(R.id.btnPlusWrapper, View.GONE)
remoteViews.setOnClickPendingIntent(R.id.btnPlusWrapper, null)
} else {
remoteViews.setViewVisibility(R.id.btnPlusWrapper, View.VISIBLE)
remoteViews.setInt(R.id.btnPlus, "setBackgroundColor", ContextCompat.getColor(context, task.lightTaskColor))
remoteViews.setOnClickPendingIntent(R.id.btnPlusWrapper, getPendingIntent(task.id, TaskDirection.UP.text, taskMapping[task.id]!!))
}
if (task.down != true) {
remoteViews.setViewVisibility(R.id.btnMinusWrapper, View.GONE)
remoteViews.setOnClickPendingIntent(R.id.btnMinusWrapper, null)
} else {
remoteViews.setViewVisibility(R.id.btnMinusWrapper, View.VISIBLE)
remoteViews.setInt(R.id.btnMinus, "setBackgroundColor", ContextCompat.getColor(context, task.mediumTaskColor))
remoteViews.setOnClickPendingIntent(R.id.btnMinusWrapper, getPendingIntent(task.id, TaskDirection.DOWN.text, taskMapping[task.id]!!))
}
if (taskMapping[task.id] != null) {
appWidgetManager?.updateAppWidget(taskMapping[task.id]!!, remoteViews)
}
}
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun makeTaskMapping() {
this.taskMapping = HashMap()
for (widgetId in allWidgetIds!!) {
val taskId = getTaskId(widgetId)
if (taskId != "") {
this.taskMapping[taskId] = widgetId
}
}
}
private fun getTaskId(widgetId: Int): String {
return sharedPreferences.getString("habit_button_widget_$widgetId", "") ?: ""
}
private fun getPendingIntent(taskId: String?, direction: String, widgetId: Int): PendingIntent {
val taskIntent = Intent(context, HabitButtonWidgetProvider::class.java)
taskIntent.action = HabitButtonWidgetProvider.HABIT_ACTION
taskIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
taskIntent.putExtra(HabitButtonWidgetProvider.TASK_ID, taskId)
taskIntent.putExtra(HabitButtonWidgetProvider.TASK_DIRECTION, direction)
return PendingIntent.getBroadcast(context, widgetId + direction.hashCode(), taskIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
}
}

View file

@ -1,148 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.style.DynamicDrawableSpan;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.habitrpg.android.habitica.HabiticaApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.data.UserRepository;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.modules.AppModule;
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser;
import net.pherth.android.emoji_library.EmojiHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
public abstract class TaskListFactory implements RemoteViewsService.RemoteViewsFactory {
private final int widgetId;
@Inject
@Named(AppModule.NAMED_USER_ID)
public String userID;
@Inject
TaskRepository taskRepository;
@Inject
UserRepository userRepository;
private int listItemResId;
private int listItemTextResId;
private String taskType;
private List<Task> taskList = new ArrayList<>();
private Context context = null;
private boolean reloadData;
TaskListFactory(Context context, Intent intent, String taskType, int listItemResId, int listItemTextResId) {
this.context = context;
this.widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
this.listItemResId = listItemResId;
this.listItemTextResId = listItemTextResId;
this.reloadData = false;
this.taskType = taskType;
if (userID == null) {
Objects.requireNonNull(HabiticaApplication.Companion.getUserComponent()).inject(this);
}
this.loadData();
}
private void loadData() {
Handler mainHandler = new Handler(context.getMainLooper());
mainHandler.post(() -> taskRepository.getTasks(taskType, userID)
.firstElement()
.toObservable()
.flatMap(Observable::fromIterable)
.filter(task -> (task.getType().equals(Task.TYPE_TODO) && !task.getCompleted()) || task.isDisplayedActive())
.toList()
.flatMapMaybe(tasks -> taskRepository.getTaskCopies(tasks).firstElement())
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(tasks -> {
reloadData = false;
taskList = tasks;
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(widgetId, R.id.list_view);
}, RxErrorHandler.Companion.handleEmptyError()));
}
@Override
public void onCreate() {
}
@Override
public void onDataSetChanged() {
if (this.reloadData) {
this.loadData();
}
this.reloadData = true;
}
@Override
public void onDestroy() {
}
@Override
public int getCount() {
return taskList.size();
}
@Override
public RemoteViews getViewAt(int position) {
final RemoteViews remoteView = new RemoteViews(
context.getPackageName(), listItemResId);
if (taskList.size() > position) {
Task task = taskList.get(position);
CharSequence parsedText = MarkdownParser.INSTANCE.parseMarkdown(task.getText());
SpannableStringBuilder builder = new SpannableStringBuilder(parsedText);
EmojiHandler.addEmojis(this.context, builder, 16, DynamicDrawableSpan.ALIGN_BASELINE, 16, 0, -1, false);
remoteView.setTextViewText(listItemTextResId, builder);
remoteView.setInt(R.id.checkbox_background, "setBackgroundResource", task.getLightTaskColor());
Intent fillInIntent = new Intent();
fillInIntent.putExtra(TaskListWidgetProvider.TASK_ID_ITEM, task.getId());
remoteView.setOnClickFillInIntent(R.id.widget_list_row, fillInIntent);
}
return remoteView;
}
@Override
public RemoteViews getLoadingView() {
return new RemoteViews(context.getPackageName(), listItemResId);
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int position) {
if (taskList.size() > position) {
Task task = taskList.get(position);
return task.getId().hashCode();
}
return position;
}
@Override
public boolean hasStableIds() {
return true;
}
}

View file

@ -0,0 +1,114 @@
package com.habitrpg.android.habitica.widget
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.text.SpannableStringBuilder
import android.text.style.DynamicDrawableSpan
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.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Consumer
import net.pherth.android.emoji_library.EmojiHandler
import java.util.*
import javax.inject.Inject
abstract class TaskListFactory internal constructor(val context: Context, intent: Intent, private val taskType: String, private val listItemResId: Int, private val listItemTextResId: Int) : RemoteViewsService.RemoteViewsFactory {
private val widgetId: Int = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0)
@Inject
lateinit var taskRepository: TaskRepository
@Inject
lateinit var userRepository: UserRepository
private var taskList: List<Task> = ArrayList()
private var reloadData: Boolean = false
init {
this.reloadData = false
}
private fun loadData() {
val mainHandler = Handler(context.mainLooper)
mainHandler.post {
taskRepository.getCurrentUserTasks(taskType)
.firstElement()
.toObservable()
.flatMap { Observable.fromIterable(it) }
.filter { task -> task.type == Task.TYPE_TODO && !task.completed || task.isDisplayedActive }
.toList()
.flatMapMaybe { tasks -> taskRepository.getTaskCopies(tasks).firstElement() }
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Consumer { tasks ->
reloadData = false
taskList = tasks
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(widgetId, R.id.list_view)
}, RxErrorHandler.handleEmptyError())
}
}
override fun onCreate() {
HabiticaBaseApplication.userComponent?.inject(this)
this.loadData()
}
override fun onDestroy() {}
override fun onDataSetChanged() {
if (this.reloadData) {
this.loadData()
}
this.reloadData = true
}
override fun getCount(): Int {
return taskList.size
}
override fun getViewAt(position: Int): RemoteViews {
val remoteView = RemoteViews(context.packageName, listItemResId)
if (taskList.size > position) {
val task = taskList[position]
val parsedText = MarkdownParser.parseMarkdown(task.text)
val builder = SpannableStringBuilder(parsedText)
EmojiHandler.addEmojis(this.context, builder, 16, DynamicDrawableSpan.ALIGN_BASELINE, 16, 0, -1, false)
remoteView.setTextViewText(listItemTextResId, builder)
remoteView.setInt(R.id.checkbox_background, "setBackgroundResource", task.lightTaskColor)
val fillInIntent = Intent()
fillInIntent.putExtra(TaskListWidgetProvider.TASK_ID_ITEM, task.id)
remoteView.setOnClickFillInIntent(R.id.widget_list_row, fillInIntent)
}
return remoteView
}
override fun getLoadingView(): RemoteViews {
return RemoteViews(context.packageName, listItemResId)
}
override fun getViewTypeCount(): Int {
return 1
}
override fun getItemId(position: Int): Long {
if (taskList.size > position) {
val task = taskList[position]
return task.id.hashCode().toLong()
}
return position.toLong()
}
override fun hasStableIds(): Boolean {
return true
}
}

View file

@ -1,125 +0,0 @@
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.Build;
import android.os.Bundle;
import android.widget.RemoteViews;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.data.ApiClient;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.modules.AppModule;
import com.habitrpg.android.habitica.ui.activities.MainActivity;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
public abstract class TaskListWidgetProvider extends BaseWidgetProvider {
public static final String DAILY_ACTION = "com.habitrpg.android.habitica.DAILY_ACTION";
public static final String TASK_ID_ITEM = "com.habitrpg.android.habitica.TASK_ID_ITEM";
@Inject
ApiClient apiClient;
@Inject
@Named(AppModule.NAMED_USER_ID)
String userId;
@Inject
TaskRepository taskRepository;
private void setUp(Context context) {
if (apiClient == null) {
Objects.requireNonNull(HabiticaBaseApplication.Companion.getUserComponent()).inject(this);
}
}
protected abstract Class getServiceClass();
protected abstract Class getProviderClass();
protected abstract int getTitleResId();
@Override
public void onReceive(Context context, Intent intent) {
setUp(context);
if (intent.getAction().equals(DAILY_ACTION)) {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
String taskId = intent.getStringExtra(TASK_ID_ITEM);
if (taskId != null) {
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, true, false, null))
.subscribe(taskDirectionData -> {
taskRepository.markTaskCompleted(taskId, true);
showToastForTaskDirection(context, taskDirectionData, userId);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view);
}, RxErrorHandler.Companion.handleEmptyError());
}
}
super.onReceive(context, intent);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
setUp(context);
ComponentName thisWidget = new ComponentName(context, getProviderClass());
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
if (Build.VERSION.SDK_INT >= 16) {
for (int widgetId : allWidgetIds) {
Bundle options = appWidgetManager.getAppWidgetOptions(widgetId);
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId));
}
}
for (int appWidgetId : appWidgetIds) {
Intent intent = new Intent(context, getServiceClass());
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_task_list);
rv.setRemoteAdapter(appWidgetId, R.id.list_view, intent);
rv.setEmptyView(R.id.list_view, R.id.emptyView);
rv.setTextViewText(R.id.widget_title, context.getString(getTitleResId()));
// if the user click on the title: open App
Intent openAppIntent = new Intent(context.getApplicationContext(), MainActivity.class);
PendingIntent openApp = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT);
rv.setOnClickPendingIntent(R.id.widget_title, openApp);
Intent taskIntent = new Intent(context, getProviderClass());
taskIntent.setAction(DAILY_ACTION);
taskIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, taskIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
rv.setPendingIntentTemplate(R.id.list_view, toastPendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, rv);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view);
}
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public int layoutResourceId() {
return R.layout.widget_task_list;
}
@Override
public RemoteViews configureRemoteViews(RemoteViews remoteViews, int widgetId, int columns, int rows) {
return remoteViews;
}
}

View file

@ -0,0 +1,108 @@
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.widget.RemoteViews
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.functions.Consumer
import javax.inject.Inject
abstract class TaskListWidgetProvider : BaseWidgetProvider() {
@Inject
lateinit var taskRepository: TaskRepository
protected abstract val serviceClass: Class<*>
protected abstract val providerClass: Class<*>
protected abstract val titleResId: Int
private fun setUp() {
if (!hasInjected) {
hasInjected = true
HabiticaBaseApplication.userComponent?.inject(this)
}
}
override fun onReceive(context: Context, intent: Intent) {
setUp()
if (intent.action == DAILY_ACTION) {
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID)
val taskId = intent.getStringExtra(TASK_ID_ITEM)
if (taskId != null) {
userRepository.getUser().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, up = true, force = false, notifyFunc = null) }
.subscribe(Consumer { taskDirectionData ->
showToastForTaskDirection(context, taskDirectionData)
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view)
}, RxErrorHandler.handleEmptyError())
}
}
super.onReceive(context, intent)
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
setUp()
val thisWidget = ComponentName(context, providerClass)
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
for (widgetId in allWidgetIds) {
val options = appWidgetManager.getAppWidgetOptions(widgetId)
appWidgetManager.partiallyUpdateAppWidget(widgetId,
sizeRemoteViews(context, options, widgetId))
}
for (appWidgetId in appWidgetIds) {
val intent = Intent(context, serviceClass)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
val rv = RemoteViews(context.packageName, R.layout.widget_task_list)
rv.setRemoteAdapter(R.id.list_view, intent)
rv.setEmptyView(R.id.list_view, R.id.emptyView)
rv.setTextViewText(R.id.widget_title, context.getString(titleResId))
// if the user click on the title: open App
val openAppIntent = Intent(context.applicationContext, MainActivity::class.java)
val openApp = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_UPDATE_CURRENT)
rv.setOnClickPendingIntent(R.id.widget_title, openApp)
val taskIntent = Intent(context, providerClass)
taskIntent.action = DAILY_ACTION
taskIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
val toastPendingIntent = PendingIntent.getBroadcast(context, 0, taskIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
rv.setPendingIntentTemplate(R.id.list_view, toastPendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, rv)
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view)
}
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
override fun layoutResourceId(): Int {
return R.layout.widget_task_list
}
override fun configureRemoteViews(remoteViews: RemoteViews, widgetId: Int, columns: Int, rows: Int): RemoteViews {
return remoteViews
}
companion object {
const val DAILY_ACTION = "com.habitrpg.android.habitica.DAILY_ACTION"
const val TASK_ID_ITEM = "com.habitrpg.android.habitica.TASK_ID_ITEM"
}
}

View file

@ -1,13 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.content.Context;
import android.content.Intent;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.models.tasks.Task;
public class TodoListFactory extends TaskListFactory {
public TodoListFactory(Context context, Intent intent) {
super(context, intent, Task.TYPE_TODO, R.layout.widget_todo_list_row, R.id.todo_text);
}
}

View file

@ -0,0 +1,9 @@
package com.habitrpg.android.habitica.widget
import android.content.Context
import android.content.Intent
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
class TodoListFactory(context: Context, intent: Intent) : TaskListFactory(context, intent, Task.TYPE_TODO, R.layout.widget_todo_list_row, R.id.todo_text)

View file

@ -1,21 +0,0 @@
package com.habitrpg.android.habitica.widget;
import com.habitrpg.android.habitica.R;
public class TodoListWidgetProvider extends TaskListWidgetProvider {
@Override
protected Class getServiceClass() {
return TodosWidgetService.class;
}
@Override
protected Class getProviderClass() {
return TodoListWidgetProvider.class;
}
@Override
protected int getTitleResId() {
return R.string.todos;
}
}

View file

@ -0,0 +1,14 @@
package com.habitrpg.android.habitica.widget
import com.habitrpg.android.habitica.R
class TodoListWidgetProvider : TaskListWidgetProvider() {
override val serviceClass: Class<*>
get() = TodosWidgetService::class.java
override val providerClass: Class<*>
get() = TodoListWidgetProvider::class.java
override val titleResId: Int
get() = R.string.todos
}

View file

@ -1,12 +0,0 @@
package com.habitrpg.android.habitica.widget;
import android.content.Intent;
import android.widget.RemoteViewsService;
public class TodosWidgetService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new TodoListFactory(this.getApplicationContext(), intent);
}
}

View file

@ -0,0 +1,11 @@
package com.habitrpg.android.habitica.widget
import android.content.Intent
import android.widget.RemoteViewsService
class TodosWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
return TodoListFactory(this.applicationContext, intent)
}
}

View file

@ -3,12 +3,12 @@ apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
}