Begin implementing local task scoring

This commit is contained in:
Phillip Thelen 2019-04-23 14:20:16 +02:00
parent 7a12357eba
commit 2bb002cbae
33 changed files with 420 additions and 340 deletions

View file

@ -194,7 +194,7 @@ android {
dimension "buildType"
}
beta {
alpha {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"alpha\""
resValue "string", "app_name", "Habitica Alpha"

View file

@ -5,4 +5,7 @@
<certificates src="user" />
</trust-anchors>
</debug-overrides>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.178.51</domain>
</domain-config>
</network-security-config>

View file

@ -22,11 +22,15 @@
</entry>
<entry>
<key>enableLocalChanges</key>
<value>true</value>
<value>false</value>
</entry>
<entry>
<key>enableLocalTaskScoring</key>
<value>false</value>
</entry>
<entry>
<key>lastVersionNumber</key>
<value></value>
<value>1.0</value>
</entry>
<entry>
<key>lastVersionCode</key>

View file

@ -22,8 +22,8 @@ interface TaskRepository : BaseRepository {
fun retrieveTasks(userId: String, tasksOrder: TasksOrder): Flowable<TaskList>
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean): Flowable<TaskScoringResult?>
fun taskChecked(user: User?, taskId: String, up: Boolean, force: Boolean): Maybe<TaskScoringResult?>
fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult?>
fun taskChecked(user: User?, taskId: String, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Maybe<TaskScoringResult?>
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>
fun getTask(taskId: String): Flowable<Task>

View file

@ -3,25 +3,25 @@ package com.habitrpg.android.habitica.data.implementation
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.local.TaskLocalRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.responses.TaskDirectionData
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.*
import com.habitrpg.android.habitica.models.user.User
import com.playseeds.android.sdk.inappmessaging.Log
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Single
import io.reactivex.functions.Consumer
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmResults
import java.text.SimpleDateFormat
import java.util.*
class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<TaskLocalRepository>(localRepository, apiClient, userID), TaskRepository {
class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiClient, userID: String, val appConfigManager: AppConfigManager) : BaseRepositoryImpl<TaskLocalRepository>(localRepository, apiClient, userID), TaskRepository {
override fun getTasksOfType(taskType: String): Flowable<RealmResults<Task>> = getTasks(taskType, userID)
private var lastTaskAction: Long = 0
@ -58,65 +58,91 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
}
@Suppress("ReturnCount")
override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean): Flowable<TaskScoringResult?> {
override fun taskChecked(user: User?, task: Task, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Flowable<TaskScoringResult?> {
val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) {
ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN)
} else null
if (user != null && localData != null) {
val stats = user.stats
val result = TaskScoringResult()
result.healthDelta = localData.hp - (stats?.hp ?: 0.0)
result.experienceDelta = localData.exp - (stats?.exp ?: 0.0)
result.manaDelta = localData.mp - (stats?.mp ?: 0.0)
result.goldDelta = localData.gp - (stats?.gp ?: 0.0)
result.hasLeveledUp = localData.lvl > stats?.lvl ?: 0
result.questDamage = localData._tmp?.quest?.progressDelta
result.drop = localData._tmp?.drop
notifyFunc?.invoke(result)
handleTaskResponse(user, localData, task, up, 0f)
}
val now = Date().time
val id = task.id
if (lastTaskAction > now - 500 && !force || id == null) {
return Flowable.empty()
}
lastTaskAction = now
return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.up else TaskDirection.down).toString())
return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text)
.map { res ->
// save local task changes
val result = TaskScoringResult()
if (user != null) {
val stats = user.stats
result.taskValueDelta = res.delta
result.healthDelta = res.hp - (stats?.hp ?: 0.0)
result.experienceDelta = res.exp - (stats?.exp ?: 0.0)
result.manaDelta = res.mp - (stats?.mp ?: 0.0)
result.goldDelta = res.gp - (stats?.gp ?: 0.0)
result.hasLeveledUp = res.lvl > stats?.lvl ?: 0
result.questDamage = res._tmp.quest?.progressDelta
if (res._tmp != null) {
result.drop = res._tmp.drop
}
this.localRepository.executeTransaction {
if (!task.isValid) {
return@executeTransaction
}
if (task.type != "reward") {
task.value = task.value + res.delta
if (Task.TYPE_DAILY == task.type || Task.TYPE_TODO == task.type) {
task.completed = up
if (Task.TYPE_DAILY == task.type && up) {
task.streak = (task.streak ?: 0) + 1
}
} else if (Task.TYPE_HABIT == task.type) {
if (up) {
task.counterUp = (task.counterUp ?: 0) + 1
} else {
task.counterDown = (task.counterDown ?: 0) + 1
}
}
}
stats?.hp = res.hp
stats?.exp = res.exp
stats?.mp = res.mp
stats?.gp = res.gp
stats?.lvl = res.lvl
user.party?.quest?.progress?.up = (user.party?.quest?.progress?.up ?: 0F) + (res._tmp.quest?.progressDelta?.toFloat() ?: 0F)
user.stats = stats
result.questDamage = res._tmp?.quest?.progressDelta
result.drop = res._tmp?.drop
if (localData == null) {
notifyFunc?.invoke(result)
}
}
handleTaskResponse(user, res, task, up, localData?.delta ?: 0f)
result
}
}
override fun taskChecked(user: User?, taskId: String, up: Boolean, force: Boolean): Maybe<TaskScoringResult?> {
private fun handleTaskResponse(user: User?, res: TaskDirectionData, task: Task, up: Boolean, localDelta: Float) {
if (user != null) {
val stats = user.stats
this.localRepository.executeTransaction {
if (!task.isValid) {
return@executeTransaction
}
if (task.type != "reward" && (task.value - localDelta) + res.delta != task.value) {
task.value = (task.value - localDelta) + res.delta
if (Task.TYPE_DAILY == task.type || Task.TYPE_TODO == task.type) {
task.completed = up
if (Task.TYPE_DAILY == task.type && up) {
task.streak = (task.streak ?: 0) + 1
}
} else if (Task.TYPE_HABIT == task.type) {
if (up) {
task.counterUp = (task.counterUp ?: 0) + 1
} else {
task.counterDown = (task.counterDown ?: 0) + 1
}
}
}
stats?.hp = res.hp
stats?.exp = res.exp
stats?.mp = res.mp
stats?.gp = res.gp
stats?.lvl = res.lvl
user.party?.quest?.progress?.up = (user.party?.quest?.progress?.up
?: 0F) + (res._tmp?.quest?.progressDelta?.toFloat() ?: 0F)
user.stats = stats
}
}
}
override fun taskChecked(user: User?, taskId: String, up: Boolean, force: Boolean, notifyFunc: ((TaskScoringResult) -> Unit)?): Maybe<TaskScoringResult?> {
return localRepository.getTask(taskId).firstElement()
.flatMap { task -> taskChecked(user, task, up, force).singleElement() }
.flatMap { task -> taskChecked(user, task, up, force, notifyFunc).singleElement() }
}
override fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task> {

View file

@ -267,7 +267,7 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli
}
if (tasks.isNotEmpty()) {
for (task in tasks) {
observable = observable.flatMap { taskRepository.taskChecked(null, task, true, true).firstElement() }
observable = observable.flatMap { taskRepository.taskChecked(null, task, true, true, null).firstElement() }
}
}
observable.flatMap { apiClient.runCron().firstElement() }

View file

@ -61,4 +61,8 @@ class AppConfigManager {
fun testingLevel(): AppTestingLevel {
return AppTestingLevel.valueOf(BuildConfig.TESTING_LEVEL)
}
fun enableLocalTaskScoring(): Boolean {
return remoteConfig.getBoolean("enableLocalTaskScoring")
}
}

View file

@ -1,44 +0,0 @@
package com.habitrpg.android.habitica.interactors;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.executors.PostExecutionThread;
import com.habitrpg.android.habitica.executors.ThreadExecutor;
import com.habitrpg.android.habitica.helpers.SoundManager;
import com.habitrpg.android.habitica.models.responses.TaskScoringResult;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import javax.inject.Inject;
import io.reactivex.Flowable;
public class BuyRewardUseCase extends UseCase<BuyRewardUseCase.RequestValues, TaskScoringResult> {
private TaskRepository taskRepository;
private SoundManager soundManager;
@Inject
public BuyRewardUseCase(TaskRepository taskRepository, SoundManager soundManager,
ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
this.taskRepository = taskRepository;
this.soundManager = soundManager;
}
@Override
protected Flowable<TaskScoringResult> buildUseCaseObservable(BuyRewardUseCase.RequestValues requestValues) {
return taskRepository
.taskChecked(requestValues.user, requestValues.task, false, false)
.doOnNext(res -> soundManager.loadAndPlayAudio(SoundManager.SoundReward));
}
public static final class RequestValues implements UseCase.RequestValues {
protected final Task task;
private final User user;
public RequestValues(User user, Task task) {
this.user = user;
this.task = task;
}
}
}

View file

@ -0,0 +1,26 @@
package com.habitrpg.android.habitica.interactors
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.executors.ThreadExecutor
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import javax.inject.Inject
import io.reactivex.Flowable
class BuyRewardUseCase @Inject
constructor(private val taskRepository: TaskRepository, private val soundManager: SoundManager,
threadExecutor: ThreadExecutor, postExecutionThread: PostExecutionThread) : UseCase<BuyRewardUseCase.RequestValues, TaskScoringResult>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(requestValues: BuyRewardUseCase.RequestValues): Flowable<TaskScoringResult?> {
return taskRepository
.taskChecked(requestValues.user, requestValues.task, false, false, requestValues.notifyFunc)
.doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundReward) }
}
class RequestValues(internal val user: User?, val task: Task, val notifyFunc: (TaskScoringResult) -> Unit) : UseCase.RequestValues
}

View file

@ -1,46 +0,0 @@
package com.habitrpg.android.habitica.interactors;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.executors.PostExecutionThread;
import com.habitrpg.android.habitica.executors.ThreadExecutor;
import com.habitrpg.android.habitica.helpers.SoundManager;
import com.habitrpg.android.habitica.models.responses.TaskScoringResult;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import javax.inject.Inject;
import io.reactivex.Flowable;
public class DailyCheckUseCase extends UseCase<DailyCheckUseCase.RequestValues, TaskScoringResult> {
private TaskRepository taskRepository;
private SoundManager soundManager;
@Inject
public DailyCheckUseCase(TaskRepository taskRepository, SoundManager soundManager,
ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
this.taskRepository = taskRepository;
this.soundManager = soundManager;
}
@Override
protected Flowable<TaskScoringResult> buildUseCaseObservable(RequestValues requestValues) {
return taskRepository.taskChecked(requestValues.user, requestValues.task, requestValues.up, false).doOnNext(res -> soundManager.loadAndPlayAudio(SoundManager.SoundDaily));
}
public static final class RequestValues implements UseCase.RequestValues {
protected boolean up = false;
protected final Task task;
public User user;
public RequestValues(User user, Task task, boolean up) {
this.user = user;
this.task = task;
this.up = up;
}
}
}

View file

@ -0,0 +1,32 @@
package com.habitrpg.android.habitica.interactors
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.executors.ThreadExecutor
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import javax.inject.Inject
import io.reactivex.Flowable
class DailyCheckUseCase @Inject
constructor(private val taskRepository: TaskRepository, private val soundManager: SoundManager,
threadExecutor: ThreadExecutor, postExecutionThread: PostExecutionThread) : UseCase<DailyCheckUseCase.RequestValues, TaskScoringResult>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<TaskScoringResult?> {
return taskRepository.taskChecked(requestValues.user, requestValues.task, requestValues.up, false, requestValues.notifyFunc)
.doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundDaily) }
}
class RequestValues(var user: User?, val task: Task, up: Boolean, val notifyFunc: (TaskScoringResult) -> Unit) : UseCase.RequestValues {
var up = false
init {
this.up = up
}
}
}

View file

@ -1,48 +0,0 @@
package com.habitrpg.android.habitica.interactors;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.executors.PostExecutionThread;
import com.habitrpg.android.habitica.executors.ThreadExecutor;
import com.habitrpg.android.habitica.helpers.SoundManager;
import com.habitrpg.android.habitica.models.responses.TaskScoringResult;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import javax.inject.Inject;
import io.reactivex.Flowable;
public class HabitScoreUseCase extends UseCase<HabitScoreUseCase.RequestValues, TaskScoringResult> {
private TaskRepository taskRepository;
private SoundManager soundManager;
@Inject
public HabitScoreUseCase(TaskRepository taskRepository, SoundManager soundManager,
ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
this.taskRepository = taskRepository;
this.soundManager = soundManager;
}
@Override
protected Flowable<TaskScoringResult> buildUseCaseObservable(RequestValues requestValues) {
return taskRepository
.taskChecked(requestValues.user, requestValues.habit, requestValues.up, false)
.doOnNext(res -> soundManager.loadAndPlayAudio(requestValues.up ? SoundManager.SoundPlusHabit : SoundManager.SoundMinusHabit));
}
public static final class RequestValues implements UseCase.RequestValues {
private final User user;
protected boolean up = false;
protected final Task habit;
public RequestValues(User user, Task habit, boolean up) {
this.user = user;
this.habit = habit;
this.up = up;
}
}
}

View file

@ -0,0 +1,32 @@
package com.habitrpg.android.habitica.interactors
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.executors.ThreadExecutor
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import javax.inject.Inject
import io.reactivex.Flowable
class HabitScoreUseCase @Inject
constructor(private val taskRepository: TaskRepository, private val soundManager: SoundManager,
threadExecutor: ThreadExecutor, postExecutionThread: PostExecutionThread) : UseCase<HabitScoreUseCase.RequestValues, TaskScoringResult>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<TaskScoringResult?> {
return taskRepository
.taskChecked(requestValues.user, requestValues.habit, requestValues.up, false, requestValues.notifyFunc)
.doOnNext { soundManager.loadAndPlayAudio(if (requestValues.up) SoundManager.SoundPlusHabit else SoundManager.SoundMinusHabit) }
}
class RequestValues(internal val user: User?, val habit: Task, up: Boolean, val notifyFunc: (TaskScoringResult) -> Unit) : UseCase.RequestValues {
var up = false
init {
this.up = up
}
}
}

View file

@ -47,7 +47,7 @@ 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"
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)
avatarView.setAvatar(requestValues.user)
avatarView.onAvatarImageReady(object : AvatarView.Consumer<Bitmap?> {

View file

@ -0,0 +1,156 @@
package com.habitrpg.android.habitica.interactors
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.responses.TaskDirectionData
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import java.util.ArrayList
class ScoreTaskLocallyInteractor {
companion object {
const val MAX_TASK_VALUE = 21.27
const val MIN_TASK_VALUE = -47.27
const val CLOSE_ENOUGH = 0.00001
private fun calculateDelta(task: Task, direction: TaskDirection): Double {
val currentValue = when {
task.value < MIN_TASK_VALUE -> MIN_TASK_VALUE
task.value > MAX_TASK_VALUE -> MAX_TASK_VALUE
else -> task.value
}
var nextDelta = Math.pow(0.9747, currentValue) * if (direction == TaskDirection.DOWN) -1 else 1
if (task.checklist?.size ?: 0 > 0) {
if (task.type == Task.TYPE_TODO) {
nextDelta *= 1 + (task.checklist?.map { if (it.completed) 1 else 0 }?.reduce { acc, i -> 0 }
?: 0)
}
}
return nextDelta
}
private fun scoreHabit(user: User, task: Task, direction: TaskDirection) {
}
private fun scoreDaily(user: User, task: Task, direction: TaskDirection) {
}
private fun scoreToDo(user: User, task: Task, direction: TaskDirection) {
}
fun score(user: User, task: Task, direction: TaskDirection): TaskDirectionData? {
return if (task.type == Task.TYPE_HABIT || direction == TaskDirection.UP) {
val stats = user.stats ?: return null
val computedStats = computeStats(user)
val result = TaskDirectionData()
result.hp = stats.hp ?: 0.0
result.exp = stats.exp ?: 0.0
result.gp = stats.gp ?: 0.0
result.mp = stats.mp ?: 0.0
val delta = calculateDelta(task, direction)
result.delta = delta.toFloat()
if (delta > 0) {
addPoints(result, delta, stats, computedStats, task, direction)
} else {
subtractPoints(result, delta, stats, computedStats, task)
}
when (task.type) {
Task.TYPE_HABIT -> scoreHabit(user, task, direction)
Task.TYPE_DAILY -> scoreDaily(user, task, direction)
Task.TYPE_TODO -> scoreToDo(user, task, direction)
}
if (result.hp <= 0.0) {
result.hp = 0.0
}
if (result.exp >= stats.toNextLevel?.toDouble() ?: 0.0) {
result.exp = result.exp - (stats.toNextLevel?.toDouble() ?: 0.0)
result.lvl = user.stats?.lvl ?: 0 + 1
result.hp = 50.0
} else {
result.lvl = user.stats?.lvl ?: 0
}
result
} else {
null
}
}
private fun subtractPoints(result: TaskDirectionData, delta: Double, stats: Stats, computedStats: Stats, task: Task) {
var conBonus = 1f - ((computedStats.con?.toFloat() ?: 0f) / 250f)
if (conBonus < 0.1) {
conBonus = 0.1f
}
val hpMod = delta * conBonus * task.priority * 2
result.hp = (stats.hp ?: 0.0) + Math.round(hpMod * 10) / 10.0
}
private fun addPoints(result: TaskDirectionData, delta: Double, stats: Stats, computedStats: Stats, task: Task, direction: TaskDirection) {
val intBonus = 1f + ((computedStats._int?.toFloat() ?: 0f) * 0.025f)
result.exp = (stats.exp
?: 0.0) + Math.round(delta * intBonus * task.priority * 6).toDouble()
val perBonus = 1f + ((computedStats.per?.toFloat() ?: 0f) * 0.02f)
val goldMod = delta * task.priority * perBonus
val streak = task.streak ?: 0
result.gp = (stats.gp ?: 0.0) + if (task.streak != null) {
val currentStreak = if (direction == TaskDirection.DOWN) streak - 1 else streak
val streakBonus = (currentStreak / 100) * 1
val afterStreak = goldMod * streakBonus
afterStreak
} else {
goldMod
}
}
private fun computeStats(user: User): Stats {
val levelStat = Math.min((user.stats?.lvl ?: 0) / 2.0f, 50f).toInt()
var totalStrength = levelStat
var totalIntelligence = levelStat
var totalConstitution = levelStat
var totalPerception = levelStat
totalStrength += user.stats?.buffs?.getStr()?.toInt() ?: 0
totalIntelligence += user.stats?.buffs?.get_int()?.toInt() ?: 0
totalConstitution += user.stats?.buffs?.getCon()?.toInt() ?: 0
totalPerception += user.stats?.buffs?.getPer()?.toInt() ?: 0
totalStrength += user.stats?.str ?: 0
totalIntelligence += user.stats?._int ?: 0
totalConstitution += user.stats?.con ?: 0
totalPerception += user.stats?.per ?: 0
val outfit = user.items?.gear?.equipped
val outfitList = ArrayList<String>()
outfit.notNull { thisOutfit ->
outfitList.add(thisOutfit.armor)
outfitList.add(thisOutfit.back)
outfitList.add(thisOutfit.body)
outfitList.add(thisOutfit.eyeWear)
outfitList.add(thisOutfit.head)
outfitList.add(thisOutfit.headAccessory)
outfitList.add(thisOutfit.shield)
outfitList.add(thisOutfit.weapon)
}
val stats = Stats()
stats.str = totalStrength
stats._int = totalIntelligence
stats.con = totalConstitution
stats.per = totalPerception
return stats
}
}
}

View file

@ -1,46 +0,0 @@
package com.habitrpg.android.habitica.interactors;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.executors.PostExecutionThread;
import com.habitrpg.android.habitica.executors.ThreadExecutor;
import com.habitrpg.android.habitica.helpers.SoundManager;
import com.habitrpg.android.habitica.models.responses.TaskScoringResult;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import javax.inject.Inject;
import io.reactivex.Flowable;
public class TodoCheckUseCase extends UseCase<TodoCheckUseCase.RequestValues, TaskScoringResult> {
private TaskRepository taskRepository;
private SoundManager soundManager;
@Inject
public TodoCheckUseCase(TaskRepository taskRepository, SoundManager soundManager,
ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
super(threadExecutor, postExecutionThread);
this.taskRepository = taskRepository;
this.soundManager = soundManager;
}
@Override
protected Flowable<TaskScoringResult> buildUseCaseObservable(TodoCheckUseCase.RequestValues requestValues) {
return taskRepository.taskChecked(requestValues.user, requestValues.task, requestValues.up, false).doOnNext(res -> soundManager.loadAndPlayAudio(SoundManager.SoundTodo));
}
public static final class RequestValues implements UseCase.RequestValues {
protected boolean up = false;
protected final Task task;
public User user;
public RequestValues(User user, Task task, boolean up) {
this.user = user;
this.task = task;
this.up = up;
}
}
}

View file

@ -0,0 +1,31 @@
package com.habitrpg.android.habitica.interactors
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.executors.ThreadExecutor
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import javax.inject.Inject
import io.reactivex.Flowable
class TodoCheckUseCase @Inject
constructor(private val taskRepository: TaskRepository, private val soundManager: SoundManager,
threadExecutor: ThreadExecutor, postExecutionThread: PostExecutionThread) : UseCase<TodoCheckUseCase.RequestValues, TaskScoringResult>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(requestValues: TodoCheckUseCase.RequestValues): Flowable<TaskScoringResult?> {
return taskRepository.taskChecked(requestValues.user, requestValues.task, requestValues.up, false, requestValues.notifyFunc).doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundTodo) }
}
class RequestValues(var user: User?, val task: Task, up: Boolean, val notifyFunc: (TaskScoringResult) -> Unit) : UseCase.RequestValues {
var up = false
init {
this.up = up
}
}
}

View file

@ -1,19 +0,0 @@
package com.habitrpg.android.habitica.models.responses;
/**
* Created by MagicMicky on 16/03/14.
*/
public enum TaskDirection {
up("up"),
down("down");
private final String dir;
TaskDirection(String dir) {
this.dir = dir;
}
public String toString() {
return this.dir;
}
}

View file

@ -0,0 +1,6 @@
package com.habitrpg.android.habitica.models.responses
enum class TaskDirection(val text: String) {
UP("up"),
DOWN("down");
}

View file

@ -1,53 +0,0 @@
package com.habitrpg.android.habitica.models.responses;
/**
* This class represent the data sent back from the API when calling /user/tasks/{id}/{direction}.
* It holds almost the same thing as Stats, except toNextLevel & maxHealth & maxHP.
* It also holds a delta, which represent the task value modification.
* Created by MagicMicky on 12/06/2014.
*/
public class TaskDirectionData {
private float delta;
private TaskDirectionDataTemp _tmp;
public Double exp;
public Double hp;
public Double gp;
public Double mp;
public Integer lvl;
public float getDelta() {
return delta;
}
public void setDelta(float delta) {
this.delta = delta;
}
public TaskDirectionDataTemp get_tmp() {
return _tmp;
}
public void set_tmp(TaskDirectionDataTemp tmp) {
this._tmp = tmp;
}
public Double getExp() {
return exp;
}
public Double getHp() {
return hp;
}
public Double getGp() {
return gp;
}
public Double getMp() {
return mp;
}
public Integer getLvl() {
return lvl;
}
}

View file

@ -0,0 +1,17 @@
package com.habitrpg.android.habitica.models.responses
/**
* This class represent the data sent back from the API when calling /user/tasks/{id}/{direction}.
* It holds almost the same thing as Stats, except toNextLevel & maxHealth & maxHP.
* It also holds a delta, which represent the task value modification.
* Created by MagicMicky on 12/06/2014.
*/
class TaskDirectionData {
var delta: Float = 0.toFloat()
var _tmp: TaskDirectionDataTemp? = null
var exp: Double = 0.0
var hp: Double = 0.0
var gp: Double = 0.0
var mp: Double = 0.0
var lvl: Int = 0
}

View file

@ -4,7 +4,7 @@ class TaskDirectionDataTemp {
var drop: TaskDirectionDataDrop? = null
var quest: TaskDirectionDataQuest? = null
var crit: Float? = null
}
class TaskDirectionDataQuest {

View file

@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.models.responses
class TaskScoringResult {
var taskValueDelta: Float? = null
var drop: TaskDirectionDataDrop? = null
var experienceDelta: Double? = null
var healthDelta: Double? = null

View file

@ -71,8 +71,8 @@ public class RepositoryModule {
@Provides
@Singleton
TaskRepository providesTaskRepository(TaskLocalRepository localRepository, ApiClient apiClient, @Named(AppModule.NAMED_USER_ID) String userId) {
return new TaskRepositoryImpl(localRepository, apiClient, userId);
TaskRepository providesTaskRepository(TaskLocalRepository localRepository, ApiClient apiClient, @Named(AppModule.NAMED_USER_ID) String userId, AppConfigManager appConfigManager) {
return new TaskRepositoryImpl(localRepository, apiClient, userId, appConfigManager);
}
@Provides

View file

@ -39,7 +39,7 @@ class TaskReceiver : BroadcastReceiver() {
if (extras != null) {
val taskTitle = extras.getString(TaskAlarmManager.TASK_NAME_INTENT_KEY)
val taskId = extras.getString(TaskAlarmManager.TASK_ID_INTENT_KEY)
//This will set up the next reminders for dailies
//This will set UP the next reminders for dailies
if (taskId != null) {
taskAlarmManager.addAlarmForTaskId(taskId)
}

View file

@ -32,7 +32,6 @@ import com.facebook.drawee.view.SimpleDraweeView
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.perf.FirebasePerformance
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.HostConfig
import com.habitrpg.android.habitica.api.MaintenanceApiService
@ -484,14 +483,13 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, null, snackbarMessage, BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()), ContextCompat.getColor(this, R.color.yellow_10), "-" + event.Reward.value, SnackbarDisplayType.NORMAL)
}, RxErrorHandler.handleEmptyError())
} else {
buyRewardUseCase.observable(BuyRewardUseCase.RequestValues(user, event.Reward))
.subscribe(Consumer {
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, null, getString(R.string.notification_purchase_reward),
BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()),
ContextCompat.getColor(this, R.color.yellow_10),
"-" + event.Reward.value.toInt(),
SnackbarDisplayType.DROP)
}, RxErrorHandler.handleEmptyError())
buyRewardUseCase.observable(BuyRewardUseCase.RequestValues(user, event.Reward) {
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, null, getString(R.string.notification_purchase_reward),
BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()),
ContextCompat.getColor(this, R.color.yellow_10),
"-" + event.Reward.value.toInt(),
SnackbarDisplayType.DROP)
}).subscribe(Consumer {}, RxErrorHandler.handleEmptyError())
}
}
@ -732,12 +730,12 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
fun onEvent(event: TaskCheckedCommand) {
when (event.Task.type) {
Task.TYPE_DAILY -> {
dailyCheckUseCase.observable(DailyCheckUseCase.RequestValues(user, event.Task, !event.Task.completed))
.subscribe(Consumer<TaskScoringResult> { this.displayTaskScoringResponse(it) }, RxErrorHandler.handleEmptyError())
dailyCheckUseCase.observable(DailyCheckUseCase.RequestValues(user, event.Task, !event.Task.completed) { result -> displayTaskScoringResponse(result)})
.subscribe(Consumer<TaskScoringResult> { }, RxErrorHandler.handleEmptyError())
}
Task.TYPE_TODO -> {
todoCheckUseCase.observable(TodoCheckUseCase.RequestValues(user, event.Task, !event.Task.completed))
.subscribe(Consumer<TaskScoringResult> { this.displayTaskScoringResponse(it) }, RxErrorHandler.handleEmptyError())
todoCheckUseCase.observable(TodoCheckUseCase.RequestValues(user, event.Task, !event.Task.completed) { result -> displayTaskScoringResponse(result)})
.subscribe(Consumer<TaskScoringResult> { }, RxErrorHandler.handleEmptyError())
}
}
}
@ -749,8 +747,8 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
@Subscribe
fun onEvent(event: HabitScoreEvent) {
habitScoreUseCase.observable(HabitScoreUseCase.RequestValues(user, event.habit, event.Up))
.subscribe(Consumer<TaskScoringResult> { this.displayTaskScoringResponse(it) }, RxErrorHandler.handleEmptyError())
habitScoreUseCase.observable(HabitScoreUseCase.RequestValues(user, event.habit, event.Up) { result -> displayTaskScoringResponse(result)})
.subscribe(Consumer<TaskScoringResult> { }, RxErrorHandler.handleEmptyError())
}
private fun checkMaintenance() {

View file

@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.AppTestingLevel
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
import com.habitrpg.android.habitica.ui.helpers.bindView
@ -129,10 +130,14 @@ class AboutFragment : BaseMainFragment() {
private fun sendEmail(subject: String) {
val version = Build.VERSION.SDK_INT
val device = Build.DEVICE
var bodyOfEmail = "Device: " + device +
" \nAndroid Version: " + version +
" \nAppVersion: " + getString(R.string.version_info, versionName, versionCode) +
" \nUser ID: " + userId
var bodyOfEmail = "Device: $device" +
" \nAndroid Version: $version"+
" \nAppVersion: " + getString(R.string.version_info, versionName, versionCode)
if (appConfigManager.testingLevel() != AppTestingLevel.PRODUCTION) {
bodyOfEmail += " ${appConfigManager.testingLevel().name}"
}
bodyOfEmail += " \nUser ID: $userId"
val user = this.user
if (user != null) {

View file

@ -245,7 +245,7 @@ class NavigationDrawerFragment : DialogFragment() {
}
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
* Users of this fragment must call this method to set UP the navigation drawer interactions.
*
* @param fragmentId The android:id of this fragment in its activity's layout.
* @param drawerLayout The DrawerLayout containing this fragment's UI.
@ -256,7 +256,7 @@ class NavigationDrawerFragment : DialogFragment() {
// set a custom shadow that overlays the main content when the drawer opens
this.drawerLayout?.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START)
// set up the drawer's list view with items and click listener
// set UP the drawer's list view with items and click listener
}
fun openDrawer() {

View file

@ -82,7 +82,7 @@ public class HabitButtonWidgetProvider extends BaseWidgetProvider {
int[] ids = {appWidgetId};
if (taskId != null) {
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, TaskDirection.up.toString().equals(direction), false))
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, TaskDirection.UP.toString().equals(direction), false, null))
.subscribe(taskDirectionData -> showToastForTaskDirection(context, taskDirectionData, userId), RxErrorHandler.handleEmptyError(), () -> this.onUpdate(context, mgr, ids));
}
}

View file

@ -83,7 +83,7 @@ public class HabitButtonWidgetService extends Service {
} 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.toString(), taskMapping.get(task.getId())));
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);
@ -91,7 +91,7 @@ public class HabitButtonWidgetService extends Service {
} 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.toString(), taskMapping.get(task.getId())));
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);

View file

@ -57,7 +57,7 @@ public abstract class TaskListWidgetProvider extends BaseWidgetProvider {
String taskId = intent.getStringExtra(TASK_ID_ITEM);
if (taskId != null) {
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, true, false))
getUserRepository().getUser(userId).firstElement().flatMap(user -> taskRepository.taskChecked(user, taskId, true, false, null))
.subscribe(taskDirectionData -> {
taskRepository.markTaskCompleted(taskId, true);
showToastForTaskDirection(context, taskDirectionData, userId);

View file

@ -91,7 +91,7 @@
// @Test
// public void shouldBeAbleToScoreTaskUp() {
// TestSubscriber<TaskDirectionData> testSubscriber = new TestSubscriber<>();
// apiClient.postTaskDirection(habit1.getId(), "up")
// apiClient.postTaskDirection(habit1.getId(), "UP")
// .subscribe(testSubscriber);
// testSubscriber.awaitTerminalEvent();
// testSubscriber.assertNoErrors();
@ -103,7 +103,7 @@
// @Test
// public void shouldBeAbleToScoreTaskDown() {
// TestSubscriber<TaskDirectionData> testSubscriber = new TestSubscriber<>();
// apiClient.postTaskDirection(habit1.getId(), "down")
// apiClient.postTaskDirection(habit1.getId(), "DOWN")
// .subscribe(testSubscriber);
// testSubscriber.awaitTerminalEvent();
// testSubscriber.assertNoErrors();

View file

@ -1,6 +1,3 @@
- Redesigned Class selection
- Redesigned Task Form
- Redesigned Report message dialog
- Optimized user and content loading
- Improved FAQ and settings
- Fixed Time Travelers shop display
In this update, we've made improvements in many areas of the app and smashed some bugs. Highlights include changes to Settings including easier language selection, fixes for the class selection screen, improvements to the Time Travelers Shop, and fixes for Challenge loading, To-Do reminders, and the Record Yesterdays Activity modal. Weve also made it easier to find the Habitica Help Guild and the Party Wanted Guild.
Be sure to download this update now for a better Habitica experience!