mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-07-15 02:31:57 +00:00
More coroutine work
This commit is contained in:
parent
576d81c868
commit
4520248ed8
49 changed files with 349 additions and 253 deletions
|
|
@ -34,6 +34,16 @@
|
|||
<deepLink app:uri="habitica.com/tasks" />
|
||||
<deepLink app:uri="habitica.com" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/taskFormActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.TaskFormActivity">
|
||||
|
||||
</activity>
|
||||
<activity
|
||||
android:id="@+id/taskSummaryActivity"
|
||||
android:name="com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity">
|
||||
|
||||
</activity>
|
||||
<fragment
|
||||
android:id="@+id/partyFragment"
|
||||
android:name="com.habitrpg.android.habitica.ui.fragments.social.party.PartyFragment"
|
||||
|
|
|
|||
|
|
@ -1255,6 +1255,7 @@
|
|||
<string name="copy_tasks_description">Show assigned and open tasks on your personal task lists</string>
|
||||
<string name="copy_shared_tasks">Copy shared tasks</string>
|
||||
<string name="group_plan_settings">Group Plan Settings</string>
|
||||
<string name="task_summary">Task Summary</string>
|
||||
<plurals name="you_x_others">
|
||||
<item quantity="zero">You</item>
|
||||
<item quantity="one">You, %d other</item>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ interface ApiService {
|
|||
fun getTask(@Path("id") id: String): Flowable<HabitResponse<Task>>
|
||||
|
||||
@POST("tasks/{id}/score/{direction}")
|
||||
fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): Flowable<HabitResponse<TaskDirectionData>>
|
||||
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse<TaskDirectionData>
|
||||
@POST("tasks/bulk-score")
|
||||
fun bulkScoreTasks(@Body data: List<Map<String, String>>): Flowable<HabitResponse<BulkTaskScoringData>>
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ interface ApiService {
|
|||
fun postTaskNewPosition(@Path("id") id: String, @Path("position") position: Int): Flowable<HabitResponse<List<String>>>
|
||||
|
||||
@POST("tasks/{taskId}/checklist/{itemId}/score")
|
||||
fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): Flowable<HabitResponse<Task>>
|
||||
suspend fun scoreChecklistItem(@Path("taskId") taskId: String, @Path("itemId") itemId: String): HabitResponse<Task>
|
||||
|
||||
@POST("tasks/user")
|
||||
fun createTask(@Body item: Task): Flowable<HabitResponse<Task>>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import com.habitrpg.android.habitica.ui.activities.SetupActivity;
|
|||
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskSummaryActivity;
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskSummaryViewModel;
|
||||
import com.habitrpg.android.habitica.ui.activities.VerifyUsernameActivity;
|
||||
import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter;
|
||||
import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder;
|
||||
|
|
@ -364,4 +366,8 @@ public interface UserComponent {
|
|||
void inject(@NotNull DeathActivity deathActivity);
|
||||
|
||||
void inject(@NotNull DeviceCommunicationService deviceCommunicationService);
|
||||
|
||||
void inject(@NotNull TaskSummaryActivity taskSummaryActivity);
|
||||
|
||||
void inject(@NotNull TaskSummaryViewModel taskSummaryViewModel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ interface ApiClient {
|
|||
|
||||
fun getTask(id: String): Flowable<Task>
|
||||
|
||||
fun postTaskDirection(id: String, direction: String): Flowable<TaskDirectionData>
|
||||
suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData?
|
||||
fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData>
|
||||
|
||||
fun postTaskNewPosition(id: String, position: Int): Flowable<List<String>>
|
||||
|
||||
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>
|
||||
suspend fun scoreChecklistItem(taskId: String, itemId: String): Task?
|
||||
|
||||
fun createTask(item: Task): Flowable<Task>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,24 +22,24 @@ interface TaskRepository : BaseRepository {
|
|||
suspend fun retrieveTasks(userId: String, tasksOrder: TasksOrder): TaskList?
|
||||
fun retrieveTasks(userId: String, tasksOrder: TasksOrder, dueDate: Date): Flowable<TaskList>
|
||||
|
||||
fun taskChecked(
|
||||
suspend fun taskChecked(
|
||||
user: User?,
|
||||
task: Task,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Flowable<TaskScoringResult>
|
||||
fun taskChecked(
|
||||
): TaskScoringResult?
|
||||
suspend fun taskChecked(
|
||||
user: User?,
|
||||
taskId: String,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Maybe<TaskScoringResult>
|
||||
fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task>
|
||||
): TaskScoringResult?
|
||||
suspend fun scoreChecklistItem(taskId: String, itemId: String): Task?
|
||||
|
||||
fun getTask(taskId: String): Flowable<Task>
|
||||
fun getTaskCopy(taskId: String): Flowable<Task>
|
||||
fun getTask(taskId: String): Flow<Task>
|
||||
fun getTaskCopy(taskId: String): Flow<Task>
|
||||
fun createTask(task: Task, force: Boolean = false): Flowable<Task>
|
||||
fun updateTask(task: Task, force: Boolean = false): Maybe<Task>?
|
||||
fun deleteTask(taskId: String): Flowable<Void>
|
||||
|
|
@ -55,7 +55,7 @@ interface TaskRepository : BaseRepository {
|
|||
|
||||
fun updateTaskPosition(taskType: TaskType, taskID: String, newPosition: Int): Maybe<List<String>>
|
||||
|
||||
fun getUnmanagedTask(taskid: String): Flowable<Task>
|
||||
fun getUnmanagedTask(taskid: String): Flow<Task>
|
||||
|
||||
fun updateTaskInBackground(task: Task)
|
||||
|
||||
|
|
|
|||
|
|
@ -451,8 +451,8 @@ class ApiClientImpl(
|
|||
return apiService.getTask(id).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun postTaskDirection(id: String, direction: String): Flowable<TaskDirectionData> {
|
||||
return apiService.postTaskDirection(id, direction).compose(configureApiCallObserver())
|
||||
override suspend fun postTaskDirection(id: String, direction: String): TaskDirectionData? {
|
||||
return handleSuspendCall { apiService.postTaskDirection(id, direction) }
|
||||
}
|
||||
|
||||
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
|
||||
|
|
@ -463,8 +463,8 @@ class ApiClientImpl(
|
|||
return apiService.postTaskNewPosition(id, position).compose(configureApiCallObserver())
|
||||
}
|
||||
|
||||
override fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task> {
|
||||
return apiService.scoreChecklistItem(taskId, itemId).compose(configureApiCallObserver())
|
||||
override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? {
|
||||
return handleSuspendCall { apiService.scoreChecklistItem(taskId, itemId) }
|
||||
}
|
||||
|
||||
override fun createTask(item: Task): Flowable<Task> {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import io.reactivex.rxjava3.core.Flowable
|
|||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
|
@ -70,13 +71,13 @@ class TaskRepositoryImpl(
|
|||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun taskChecked(
|
||||
override suspend fun taskChecked(
|
||||
user: User?,
|
||||
task: Task,
|
||||
up: Boolean,
|
||||
force: Boolean,
|
||||
notifyFunc: ((TaskScoringResult) -> Unit)?
|
||||
): Flowable<TaskScoringResult> {
|
||||
): TaskScoringResult? {
|
||||
val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) {
|
||||
ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN)
|
||||
} else null
|
||||
|
|
@ -90,41 +91,33 @@ class TaskRepositoryImpl(
|
|||
val now = Date().time
|
||||
val id = task.id
|
||||
if (lastTaskAction > now - 500 && !force || id == null) {
|
||||
return Flowable.empty()
|
||||
return null
|
||||
}
|
||||
|
||||
lastTaskAction = now
|
||||
return this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text)
|
||||
.flatMapMaybe {
|
||||
// There are cases where the user object is not set correctly. So the app refetches it as a fallback
|
||||
if (user == null) {
|
||||
localRepository.getUser(userID).firstElement()
|
||||
} else {
|
||||
Maybe.just(user)
|
||||
}.map { user -> Pair(it, user) }
|
||||
}
|
||||
.map { (res, user): Pair<TaskDirectionData, User> ->
|
||||
// save local task changes
|
||||
val res = this.apiClient.postTaskDirection(id, (if (up) TaskDirection.UP else TaskDirection.DOWN).text) ?: return null
|
||||
// There are cases where the user object is not set correctly. So the app refetches it as a fallback
|
||||
val thisUser = user ?: localRepository.getUser(userID).firstOrNull() ?: return null
|
||||
// save local task changes
|
||||
|
||||
analyticsManager.logEvent(
|
||||
"task_scored",
|
||||
bundleOf(
|
||||
Pair("type", task.type),
|
||||
Pair("scored_up", up),
|
||||
Pair("value", task.value)
|
||||
)
|
||||
)
|
||||
if (res.lvl == 0) {
|
||||
// Team tasks that require approval have weird data that we should just ignore.
|
||||
return@map TaskScoringResult()
|
||||
}
|
||||
val result = TaskScoringResult(res, user.stats)
|
||||
if (localData == null) {
|
||||
notifyFunc?.invoke(result)
|
||||
}
|
||||
handleTaskResponse(user, res, task, up, localData?.delta ?: 0f)
|
||||
result
|
||||
}
|
||||
analyticsManager.logEvent(
|
||||
"task_scored",
|
||||
bundleOf(
|
||||
Pair("type", task.type),
|
||||
Pair("scored_up", up),
|
||||
Pair("value", task.value)
|
||||
)
|
||||
)
|
||||
if (res.lvl == 0) {
|
||||
// Team tasks that require approval have weird data that we should just ignore.
|
||||
return TaskScoringResult()
|
||||
}
|
||||
val result = TaskScoringResult(res, thisUser.stats)
|
||||
if (localData == null) {
|
||||
notifyFunc?.invoke(result)
|
||||
}
|
||||
handleTaskResponse(thisUser, res, task, up, localData?.delta ?: 0f)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun bulkScoreTasks(data: List<Map<String, String>>): Flowable<BulkTaskScoringData> {
|
||||
|
|
@ -195,31 +188,29 @@ class TaskRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun taskChecked(
|
||||
override suspend 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, notifyFunc).singleElement() }
|
||||
): TaskScoringResult? {
|
||||
val task = localRepository.getTask(taskId).firstOrNull() ?: return null
|
||||
return taskChecked(user, task, up, force, notifyFunc)
|
||||
}
|
||||
|
||||
override fun scoreChecklistItem(taskId: String, itemId: String): Flowable<Task> {
|
||||
return apiClient.scoreChecklistItem(taskId, itemId)
|
||||
.flatMapMaybe { localRepository.getTask(taskId).firstElement() }
|
||||
.doOnNext { task ->
|
||||
val updatedItem: ChecklistItem? = task.checklist?.lastOrNull { itemId == it.id }
|
||||
if (updatedItem != null) {
|
||||
localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed }
|
||||
}
|
||||
}
|
||||
override suspend fun scoreChecklistItem(taskId: String, itemId: String): Task? {
|
||||
val task = apiClient.scoreChecklistItem(taskId, itemId)
|
||||
val updatedItem: ChecklistItem? = task?.checklist?.lastOrNull { itemId == it.id }
|
||||
if (updatedItem != null) {
|
||||
localRepository.modify(updatedItem) { liveItem -> liveItem.completed = !liveItem.completed }
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
override fun getTask(taskId: String): Flowable<Task> = localRepository.getTask(taskId)
|
||||
override fun getTask(taskId: String) = localRepository.getTask(taskId)
|
||||
|
||||
override fun getTaskCopy(taskId: String): Flowable<Task> = localRepository.getTaskCopy(taskId)
|
||||
override fun getTaskCopy(taskId: String) = localRepository.getTaskCopy(taskId)
|
||||
|
||||
override fun createTask(task: Task, force: Boolean): Flowable<Task> {
|
||||
val now = Date().time
|
||||
|
|
@ -310,8 +301,7 @@ class TaskRepositoryImpl(
|
|||
.doOnSuccess { localRepository.updateTaskPositions(it) }
|
||||
}
|
||||
|
||||
override fun getUnmanagedTask(taskid: String): Flowable<Task> =
|
||||
getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
|
||||
override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
|
||||
|
||||
override fun updateTaskInBackground(task: Task) {
|
||||
updateTask(task).subscribe({ }, ExceptionHandler.rx())
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ interface TaskLocalRepository : BaseLocalRepository {
|
|||
|
||||
fun deleteTask(taskID: String)
|
||||
|
||||
fun getTask(taskId: String): Flowable<Task>
|
||||
fun getTaskCopy(taskId: String): Flowable<Task>
|
||||
fun getTask(taskId: String): Flow<Task>
|
||||
fun getTaskCopy(taskId: String): Flow<Task>
|
||||
|
||||
fun markTaskCompleted(taskId: String, isCompleted: Boolean)
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ interface TaskLocalRepository : BaseLocalRepository {
|
|||
fun updateTaskPositions(taskOrder: List<String>)
|
||||
fun saveCompletedTodos(userId: String, tasks: MutableCollection<Task>)
|
||||
fun getErroredTasks(userID: String): Flowable<out List<Task>>
|
||||
fun getUser(userID: String): Flowable<User>
|
||||
fun getUserFlowable(userID: String): Flowable<User>
|
||||
fun getUser(userID: String): Flow<User>
|
||||
fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import io.realm.kotlin.toFlow
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), TaskLocalRepository {
|
||||
|
||||
|
|
@ -187,19 +189,17 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTask(taskId: String): Flowable<Task> {
|
||||
override fun getTask(taskId: String): Flow<Task> {
|
||||
if (realm.isClosed) {
|
||||
return Flowable.empty()
|
||||
return emptyFlow()
|
||||
}
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Task::class.java).equalTo("id", taskId).findAll().asFlowable()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
.cast(Task::class.java)
|
||||
)
|
||||
return realm.where(Task::class.java).equalTo("id", taskId).findAll().toFlow()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isNotEmpty() }
|
||||
.map { it.first() }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override fun getTaskCopy(taskId: String): Flowable<Task> {
|
||||
override fun getTaskCopy(taskId: String): Flow<Task> {
|
||||
return getTask(taskId)
|
||||
.map { task ->
|
||||
return@map if (task.isManaged && task.isValid) {
|
||||
|
|
@ -270,7 +270,7 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
).retry(1)
|
||||
}
|
||||
|
||||
override fun getUser(userID: String): Flowable<User> {
|
||||
override fun getUserFlowable(userID: String): Flowable<User> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(User::class.java)
|
||||
.equalTo("id", userID)
|
||||
|
|
@ -281,6 +281,16 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
|
|||
)
|
||||
}
|
||||
|
||||
override fun getUser(userID: String): Flow<User> {
|
||||
return realm.where(User::class.java)
|
||||
.equalTo("id", userID)
|
||||
.findAll()
|
||||
.toFlow()
|
||||
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
|
||||
.map { users -> users.first() }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override fun getTasksForChallenge(challengeID: String?, userID: String?): Flowable<out List<Task>> {
|
||||
return RxJavaBridge.toV3Flowable(
|
||||
realm.where(Task::class.java)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ import com.habitrpg.android.habitica.receivers.TaskReceiver
|
|||
import com.habitrpg.shared.habitica.HLogger
|
||||
import com.habitrpg.shared.habitica.LogLevel
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
|
@ -55,10 +59,12 @@ class TaskAlarmManager(
|
|||
// We currently only use this function to schedule the next reminder for dailies
|
||||
// We may be able to use repeating alarms instead of this in the future
|
||||
fun addAlarmForTaskId(taskId: String) {
|
||||
taskRepository.getTaskCopy(taskId)
|
||||
.filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type }
|
||||
.firstElement()
|
||||
.subscribe({ this.setAlarmsForTask(it) }, ExceptionHandler.rx())
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val task = taskRepository.getTaskCopy(taskId)
|
||||
.filter { task -> task.isValid && task.isManaged && TaskType.DAILY == task.type }
|
||||
.first()
|
||||
setAlarmsForTask(task)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun scheduleAllSavedAlarms(preventDailyReminder: Boolean) {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,27 @@
|
|||
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.helpers.SoundManager
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
import javax.inject.Inject
|
||||
|
||||
class BuyRewardUseCase @Inject
|
||||
constructor(
|
||||
private val taskRepository: TaskRepository,
|
||||
private val soundManager: SoundManager,
|
||||
postExecutionThread: PostExecutionThread
|
||||
) : UseCase<BuyRewardUseCase.RequestValues, TaskScoringResult?>(postExecutionThread) {
|
||||
) : FlowUseCase<BuyRewardUseCase.RequestValues, TaskScoringResult?>() {
|
||||
|
||||
override fun buildUseCaseObservable(requestValues: RequestValues): Flowable<TaskScoringResult?> {
|
||||
return taskRepository
|
||||
.taskChecked(requestValues.user, requestValues.task, false, false, requestValues.notifyFunc)
|
||||
.doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundReward) }
|
||||
override suspend fun run(requestValues: RequestValues): TaskScoringResult? {
|
||||
val response = taskRepository.taskChecked(requestValues.user, requestValues.task, false, false, requestValues.notifyFunc)
|
||||
soundManager.loadAndPlayAudio(SoundManager.SoundReward)
|
||||
return response
|
||||
}
|
||||
|
||||
class RequestValues(
|
||||
internal val user: User?,
|
||||
val task: Task,
|
||||
val notifyFunc: (TaskScoringResult) -> Unit
|
||||
) : UseCase.RequestValues
|
||||
) : FlowUseCase.RequestValues
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package com.habitrpg.android.habitica.interactors;
|
||||
|
||||
import com.habitrpg.android.habitica.executors.PostExecutionThread;
|
||||
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
|
||||
public abstract class UseCase<Q extends UseCase.RequestValues, T> {
|
||||
|
||||
private final PostExecutionThread postExecutionThread;
|
||||
|
||||
protected UseCase(PostExecutionThread postExecutionThread) {
|
||||
this.postExecutionThread = postExecutionThread;
|
||||
}
|
||||
|
||||
protected abstract Flowable<T> buildUseCaseObservable(Q requestValues);
|
||||
|
||||
public Flowable<T> observable(Q requestValues) {
|
||||
return this.buildUseCaseObservable(requestValues)
|
||||
.subscribeOn(postExecutionThread.getScheduler())
|
||||
.observeOn(postExecutionThread.getScheduler());
|
||||
}
|
||||
|
||||
public interface RequestValues {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.habitrpg.android.habitica.interactors
|
||||
|
||||
import com.habitrpg.android.habitica.executors.PostExecutionThread
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class UseCase<Q : UseCase.RequestValues?, T: Any> protected constructor(private val postExecutionThread: PostExecutionThread) {
|
||||
protected abstract fun buildUseCaseObservable(requestValues: Q): Flowable<T>
|
||||
fun observable(requestValues: Q): Flowable<T> {
|
||||
return buildUseCaseObservable(requestValues)
|
||||
.subscribeOn(postExecutionThread.scheduler)
|
||||
.observeOn(postExecutionThread.scheduler)
|
||||
}
|
||||
|
||||
interface RequestValues
|
||||
}
|
||||
|
||||
abstract class FlowUseCase<Q : FlowUseCase.RequestValues?, T> {
|
||||
protected abstract suspend fun run(requestValues: Q): T
|
||||
suspend fun observable(requestValues: Q): T {
|
||||
return withContext(Dispatchers.IO) {
|
||||
run(requestValues)
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestValues
|
||||
}
|
||||
|
|
@ -113,16 +113,17 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
|||
}
|
||||
context?.getString(R.string.complete_task_action) -> {
|
||||
taskID?.let {
|
||||
taskRepository.taskChecked(null, it, up = true, force = false) {
|
||||
}.subscribe({
|
||||
val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(
|
||||
it.experienceDelta,
|
||||
it.healthDelta,
|
||||
it.goldDelta,
|
||||
it.manaDelta
|
||||
)
|
||||
showToast(pair.first)
|
||||
}, ExceptionHandler.rx())
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
taskRepository.taskChecked(null, it, up = true, force = false) {
|
||||
val pair = NotifyUserUseCase.getNotificationAndAddStatsToUserAsText(
|
||||
it.experienceDelta,
|
||||
it.healthDelta,
|
||||
it.goldDelta,
|
||||
it.manaDelta
|
||||
)
|
||||
showToast(pair.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +135,8 @@ class LocalNotificationActionReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
private fun getMessageText(key: String?): String? {
|
||||
return intent?.let { RemoteInput.getResultsFromIntent(it)?.getCharSequence(key)?.toString() }
|
||||
return intent?.let {
|
||||
RemoteInput.getResultsFromIntent(it)?.getCharSequence(key)?.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity
|
|||
import com.habitrpg.shared.habitica.HLogger
|
||||
import com.habitrpg.shared.habitica.LogLevel
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import io.reactivex.rxjava3.functions.Consumer
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class TaskReceiver : BroadcastReceiver() {
|
||||
|
|
@ -43,18 +45,13 @@ class TaskReceiver : BroadcastReceiver() {
|
|||
taskAlarmManager.addAlarmForTaskId(taskId)
|
||||
}
|
||||
|
||||
taskRepository.getTask(taskId ?: "")
|
||||
.firstElement()
|
||||
.subscribe(
|
||||
Consumer {
|
||||
if (it.isUpdatedToday && it.completed) {
|
||||
return@Consumer
|
||||
}
|
||||
|
||||
createNotification(context, it)
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val task = taskRepository.getTask(taskId ?: "").firstOrNull() ?: return@launch
|
||||
if (task.isUpdatedToday && task.completed) {
|
||||
return@launch
|
||||
}
|
||||
createNotification(context, task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import com.habitrpg.android.habitica.databinding.AdventureGuideItemBinding
|
|||
import com.habitrpg.android.habitica.extensions.fromHtml
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.common.habitica.extensions.loadImage
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.common.habitica.extensions.loadImage
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdventureGuideActivity : BaseActivity() {
|
||||
|
|
@ -33,7 +33,7 @@ class AdventureGuideActivity : BaseActivity() {
|
|||
return R.layout.activity_main
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityAdventureGuideBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ArmoireActivity : BaseActivity() {
|
|||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityArmoireBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,9 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
|
|||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
|
||||
|
|
@ -62,10 +60,10 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||
|
||||
internal var toolbar: Toolbar? = null
|
||||
|
||||
protected abstract fun getLayoutResId(): Int
|
||||
protected abstract fun getLayoutResId(): Int?
|
||||
|
||||
open fun getContentView(): View {
|
||||
return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(getLayoutResId(), null)
|
||||
open fun getContentView(layoutResId: Int? = getLayoutResId()): View {
|
||||
return (getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(layoutResId ?: 0, null)
|
||||
}
|
||||
|
||||
var compositeSubscription = CompositeDisposable()
|
||||
|
|
@ -94,7 +92,9 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
habiticaApplication
|
||||
injectActivity(HabiticaBaseApplication.userComponent)
|
||||
setContentView(getContentView())
|
||||
getLayoutResId()?.let {
|
||||
setContentView(getContentView(it))
|
||||
}
|
||||
compositeSubscription = CompositeDisposable()
|
||||
compositeSubscription.add(notificationsManager.displayNotificationEvents.subscribe(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class ChallengeFormActivity : BaseActivity() {
|
|||
|
||||
private var savingInProgress = false
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityCreateChallengeBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ClassSelectionActivity : BaseActivity() {
|
|||
return R.layout.activity_class_selection
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityClassSelectionBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class DeathActivity: BaseActivity() {
|
|||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityDeathBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class FixCharacterValuesActivity : BaseActivity() {
|
|||
|
||||
override fun getLayoutResId(): Int = R.layout.activity_fixcharacter
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityFixcharacterBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,7 +496,7 @@ class FullProfileActivity : BaseActivity() {
|
|||
return R.layout.activity_full_profile
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityFullProfileBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class GiftGemsActivity : PurchaseActivity() {
|
|||
return R.layout.activity_gift_gems
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityGiftGemsBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class GiftSubscriptionActivity : PurchaseActivity() {
|
|||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityGiftSubscriptionBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class GroupFormActivity : BaseActivity() {
|
|||
return R.layout.activity_group_form
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityGroupFormBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class GroupInviteActivity : BaseActivity() {
|
|||
return R.layout.activity_party_invite
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityPartyInviteBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class HabitButtonWidgetActivity : BaseActivity() {
|
|||
return R.layout.widget_configure_habit_button
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = WidgetConfigureHabitButtonBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChan
|
|||
return R.layout.activity_intro
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityIntroBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class LoginActivity : BaseActivity() {
|
|||
return R.layout.activity_login
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
return R.layout.activity_main
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class MaintenanceActivity : BaseActivity() {
|
|||
return R.layout.activity_maintenance
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityMaintenanceBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
|
|||
|
||||
override fun getLayoutResId(): Int = R.layout.activity_notifications
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityNotificationsBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class ReportMessageActivity : BaseActivity() {
|
|||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityReportMessageBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
|
|||
return R.layout.activity_setup
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivitySetupBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class SkillMemberActivity : BaseActivity() {
|
|||
component?.inject(this)
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivitySkillMembersBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class SkillTasksActivity : BaseActivity() {
|
|||
loadTaskLists()
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivitySkillTasksBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ import com.habitrpg.android.habitica.data.TaskRepository
|
|||
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
|
||||
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
|
||||
import com.habitrpg.android.habitica.extensions.addCancelButton
|
||||
import com.habitrpg.common.habitica.extensions.dpToPx
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
|
||||
import com.habitrpg.android.habitica.models.Tag
|
||||
|
|
@ -48,9 +46,9 @@ import com.habitrpg.shared.habitica.models.tasks.Frequency
|
|||
import com.habitrpg.shared.habitica.models.tasks.HabitResetOption
|
||||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import io.realm.RealmList
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
class TaskFormActivity : BaseActivity() {
|
||||
|
|
@ -105,7 +103,7 @@ class TaskFormActivity : BaseActivity() {
|
|||
return R.layout.activity_task_form
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityTaskFormBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
@ -203,36 +201,32 @@ class TaskFormActivity : BaseActivity() {
|
|||
when {
|
||||
taskId != null -> {
|
||||
isCreating = false
|
||||
compositeSubscription.add(
|
||||
taskRepository.getUnmanagedTask(taskId).firstElement().subscribe(
|
||||
{
|
||||
if (!it.isValid) return@subscribe
|
||||
task = it
|
||||
initialTaskInstance = it
|
||||
// tintColor = ContextCompat.getColor(this, it.mediumTaskColor)
|
||||
fillForm(it)
|
||||
it.challengeID?.let { challengeID ->
|
||||
compositeSubscription.add(
|
||||
challengeRepository.retrieveChallenge(challengeID)
|
||||
.subscribe(
|
||||
{ challenge ->
|
||||
this.challenge = challenge
|
||||
binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name)
|
||||
binding.challengeNameView.visibility = View.VISIBLE
|
||||
disableEditingForUneditableFieldsInChallengeTask()
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
lifecycleScope.launch(ExceptionHandler.coroutine()) {
|
||||
val task = taskRepository.getUnmanagedTask(taskId).firstOrNull() ?: return@launch
|
||||
if (!task.isValid) return@launch
|
||||
this@TaskFormActivity.task = task
|
||||
initialTaskInstance = task
|
||||
// tintColor = ContextCompat.getColor(this, it.mediumTaskColor)
|
||||
fillForm(task)
|
||||
task.challengeID?.let { challengeID ->
|
||||
compositeSubscription.add(
|
||||
challengeRepository.retrieveChallenge(challengeID)
|
||||
.subscribe(
|
||||
{ challenge ->
|
||||
this@TaskFormActivity.challenge = challenge
|
||||
binding.challengeNameView.text = getString(R.string.challenge_task_name, challenge.name)
|
||||
binding.challengeNameView.visibility = View.VISIBLE
|
||||
disableEditingForUneditableFieldsInChallengeTask()
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
}
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
bundle.containsKey(PARCELABLE_TASK) -> {
|
||||
isCreating = false
|
||||
task = bundle.getParcelable(PARCELABLE_TASK)
|
||||
task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java)
|
||||
task?.let { fillForm(it) }
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -547,16 +541,13 @@ class TaskFormActivity : BaseActivity() {
|
|||
resultIntent.putExtra(TASK_TYPE_KEY, taskType.value)
|
||||
if (!isChallengeTask) {
|
||||
if (isCreating) {
|
||||
if (isDiscardCancelled) {
|
||||
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
|
||||
}
|
||||
taskRepository.createTaskInBackground(thisTask)
|
||||
} else {
|
||||
if (isDiscardCancelled) {
|
||||
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
|
||||
}
|
||||
taskRepository.updateTaskInBackground(thisTask)
|
||||
}
|
||||
if (isDiscardCancelled) {
|
||||
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
|
||||
}
|
||||
|
||||
if (thisTask.type == TaskType.DAILY || thisTask.type == TaskType.TODO) {
|
||||
taskAlarmManager.scheduleAlarmsForTask(thisTask)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.habitrpg.android.habitica.ui.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.components.UserComponent
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class TaskSummaryViewModel(val taskId: String) : BaseViewModel() {
|
||||
@Inject
|
||||
lateinit var taskRespository: TaskRepository
|
||||
val task = taskRespository.getTask(taskId).asLiveData()
|
||||
|
||||
override fun inject(component: UserComponent) {
|
||||
component.inject(this)
|
||||
}
|
||||
}
|
||||
|
||||
class TaskSummaryActivity: BaseActivity() {
|
||||
override fun getLayoutResId(): Int? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectActivity(component: UserComponent?) {
|
||||
component?.inject(this)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
|
||||
val task by viewModel.task.observeAsState()
|
||||
Column {
|
||||
Text(stringResource(R.string.task_summary), Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TaskSummaryViewPreview() {
|
||||
TaskSummaryView(TaskSummaryViewModel(""))
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ class VerifyUsernameActivity : BaseActivity() {
|
|||
return R.layout.activity_verify_username
|
||||
}
|
||||
|
||||
override fun getContentView(): View {
|
||||
override fun getContentView(layoutResId: Int?): View {
|
||||
binding = ActivityVerifyUsernameBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.habitrpg.android.habitica.ui.fragments.tasks
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
|
|
@ -30,6 +29,7 @@ import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
|
|||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.helpers.NotificationsManager
|
||||
import com.habitrpg.android.habitica.helpers.SoundManager
|
||||
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
|
||||
|
|
@ -141,10 +141,9 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
playSound(it.second)
|
||||
context?.let { it1 -> notificationsManager.dismissTaskNotification(it1, it.first) }
|
||||
}?.subscribeWithErrorHandler { scoreTask(it.first, it.second) }?.let { recyclerSubscription.add(it) }
|
||||
recyclerAdapter?.checklistItemScoreEvents
|
||||
?.flatMap {
|
||||
taskRepository.scoreChecklistItem(it.first.id ?: "", it.second.id ?: "")
|
||||
}?.subscribeWithErrorHandler {}?.let { recyclerSubscription.add(it) }
|
||||
recyclerAdapter?.checklistItemScoreEvents?.subscribeWithErrorHandler {
|
||||
scoreChecklistItem(it.first, it.second)
|
||||
}?.let { recyclerSubscription.add(it) }
|
||||
recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { recyclerSubscription.add(it) }
|
||||
recyclerAdapter?.adventureGuideOpenEvents?.subscribeWithErrorHandler { MainNavigationController.navigate(R.id.adventureGuideActivity) }?.let { recyclerSubscription.add(it) }
|
||||
|
||||
|
|
@ -163,6 +162,12 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
}
|
||||
}
|
||||
|
||||
private fun scoreChecklistItem(task: Task, item: ChecklistItem) {
|
||||
lifecycleScope.launch(ExceptionHandler.coroutine()) {
|
||||
taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTaskResult(result: TaskScoringResult, value: Int) {
|
||||
if (taskType == TaskType.REWARD) {
|
||||
(activity as? MainActivity)?.let { activity ->
|
||||
|
|
@ -530,12 +535,12 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
|
|||
bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id)
|
||||
bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value)
|
||||
|
||||
val intent = Intent(activity, TaskFormActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
TasksFragment.lastTaskFormOpen = Date()
|
||||
if (isAdded) {
|
||||
startActivity(intent)
|
||||
if (task.canEdit(viewModel.userViewModel.userID)) {
|
||||
MainNavigationController.navigate(R.id.taskFormActivity, bundle)
|
||||
} else {
|
||||
MainNavigationController.navigate(R.id.taskSummaryActivity, bundle)
|
||||
}
|
||||
TasksFragment.lastTaskFormOpen = Date()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider {
|
|||
direction: TaskDirection,
|
||||
onResult: (TaskScoringResult, Int) -> Unit
|
||||
) {
|
||||
compositeSubscription.add(
|
||||
viewModelScope.launch(ExceptionHandler.coroutine()) {
|
||||
taskRepository.taskChecked(
|
||||
null,
|
||||
task.id ?: "",
|
||||
|
|
@ -134,8 +134,8 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider {
|
|||
putLong("last_task_reporting", Date().time)
|
||||
}
|
||||
}
|
||||
}.subscribe({}, ExceptionHandler.rx())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val filterSets: HashMap<TaskType, MutableLiveData<Triple<String?, String?, List<String>>>> =
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ import android.widget.ImageView
|
|||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.databinding.NpcBannerBinding
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.common.habitica.extensions.DataBindingUtils
|
||||
import com.habitrpg.common.habitica.extensions.layoutInflater
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class NPCBannerView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
|
||||
|
|
@ -50,14 +49,9 @@ class NPCBannerView(context: Context, attrs: AttributeSet?) : FrameLayout(contex
|
|||
val width = (height * aspectRatio).roundToInt()
|
||||
val drawable = BitmapDrawable(context.resources, Bitmap.createScaledBitmap(it.toBitmap(), width, height, false))
|
||||
drawable.tileModeX = Shader.TileMode.REPEAT
|
||||
Observable.just(drawable)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{
|
||||
binding.backgroundView.background = it
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
MainScope().launch {
|
||||
binding.backgroundView.background = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.widget.ImageView
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
|
|
@ -22,7 +23,7 @@ import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
|||
import com.habitrpg.shared.habitica.models.tasks.TaskType
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Calendar
|
||||
|
|
@ -83,7 +84,7 @@ class YesterdailyDialog private constructor(
|
|||
}
|
||||
}
|
||||
lastCronRun = Date()
|
||||
GlobalScope.launch(ExceptionHandler.coroutine()) {
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
userRepository.runCron(completedTasks)
|
||||
}
|
||||
displayedDialog = null
|
||||
|
|
@ -141,12 +142,12 @@ class YesterdailyDialog private constructor(
|
|||
val checkboxHolder = checklistView.findViewById<View>(R.id.checkBoxHolder) as? ViewGroup
|
||||
checkboxHolder?.setOnClickListener { _ ->
|
||||
item.completed = !item.completed
|
||||
taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "").subscribe({ }, ExceptionHandler.rx())
|
||||
scoreChecklistItem(task, item)
|
||||
configureChecklistView(checklistView, task, item)
|
||||
}
|
||||
checklistView.setOnClickListener {
|
||||
item.completed = !item.completed
|
||||
taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "").subscribe({ }, ExceptionHandler.rx())
|
||||
scoreChecklistItem(task, item)
|
||||
configureChecklistView(checklistView, task, item)
|
||||
}
|
||||
checkboxHolder?.setBackgroundResource(
|
||||
|
|
@ -175,6 +176,15 @@ class YesterdailyDialog private constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun scoreChecklistItem(
|
||||
task: Task,
|
||||
item: ChecklistItem
|
||||
) {
|
||||
lifecycleScope.launch(ExceptionHandler.coroutine()) {
|
||||
taskRepository.scoreChecklistItem(task.id ?: "", item.id ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureTaskView(taskView: View, task: Task) {
|
||||
val completed = !task.isDisplayedActive
|
||||
val checkmark = taskView.findViewById<ImageView>(R.id.checkmark)
|
||||
|
|
@ -274,7 +284,7 @@ class YesterdailyDialog private constructor(
|
|||
)
|
||||
} else {
|
||||
lastCronRun = Date()
|
||||
GlobalScope.launch(ExceptionHandler.coroutine()) {
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
userRepository.runCron()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class HabitButtonWidgetProvider : BaseWidgetProvider() {
|
||||
|
|
@ -72,8 +75,12 @@ class HabitButtonWidgetProvider : BaseWidgetProvider() {
|
|||
val ids = intArrayOf(appWidgetId)
|
||||
|
||||
if (taskId != null) {
|
||||
userRepository.getUserFlowable().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, TaskDirection.UP.text == direction, false, null) }
|
||||
.subscribe({ taskDirectionData -> showToastForTaskDirection(context, taskDirectionData) }, ExceptionHandler.rx(), { this.onUpdate(context, mgr, ids) })
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val user = userRepository.getUser().firstOrNull()
|
||||
val response = taskRepository.taskChecked(user, taskId, TaskDirection.UP.text == direction, false, null)
|
||||
showToastForTaskDirection(context, response)
|
||||
this@HabitButtonWidgetProvider.onUpdate(context, mgr, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onReceive(context, intent)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
|||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.common.habitica.helpers.MarkdownParser
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
|
||||
|
|
@ -42,10 +45,14 @@ class HabitButtonWidgetService : Service() {
|
|||
allWidgetIds = appWidgetManager?.getAppWidgetIds(thisWidget)
|
||||
makeTaskMapping()
|
||||
|
||||
for (taskid in this.taskMapping.keys) {
|
||||
taskRepository.getUnmanagedTask(taskid).firstElement().subscribe({ this.updateData(it) }, ExceptionHandler.rx())
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
for (taskid in taskMapping.keys) {
|
||||
val task = taskRepository.getUnmanagedTask(taskid).firstOrNull()
|
||||
updateData(task)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stopSelf()
|
||||
|
||||
return START_STICKY
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ import com.habitrpg.android.habitica.extensions.withImmutableFlag
|
|||
import com.habitrpg.android.habitica.extensions.withMutableFlag
|
||||
import com.habitrpg.android.habitica.helpers.ExceptionHandler
|
||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class TaskListWidgetProvider : BaseWidgetProvider() {
|
||||
|
|
@ -44,14 +47,12 @@ abstract class TaskListWidgetProvider : BaseWidgetProvider() {
|
|||
val taskId = intent.getStringExtra(TASK_ID_ITEM)
|
||||
|
||||
if (taskId != null) {
|
||||
userRepository.getUserFlowable().firstElement().flatMap { user -> taskRepository.taskChecked(user, taskId, up = true, force = false, notifyFunc = null) }
|
||||
.subscribe(
|
||||
{ taskDirectionData ->
|
||||
showToastForTaskDirection(context, taskDirectionData)
|
||||
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view)
|
||||
},
|
||||
ExceptionHandler.rx()
|
||||
)
|
||||
MainScope().launch(ExceptionHandler.coroutine()) {
|
||||
val user = userRepository.getUser().firstOrNull()
|
||||
val response = taskRepository.taskChecked(user, taskId, up = true, force = false, notifyFunc = null)
|
||||
showToastForTaskDirection(context, response)
|
||||
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_view)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onReceive(context, intent)
|
||||
|
|
|
|||
|
|
@ -81,10 +81,10 @@ class TaskRepositoryImplTest : WordSpec({
|
|||
every { apiClient.postTaskDirection(any(), "up") } returns Flowable.just(
|
||||
TaskDirectionData()
|
||||
)
|
||||
every { localRepository.getUser("") } returns Flowable.just(user)
|
||||
every { localRepository.getUserFlowable("") } returns Flowable.just(user)
|
||||
repository.taskChecked(null, task, true, false, null)
|
||||
eventually(5000) {
|
||||
localRepository.getUser("")
|
||||
localRepository.getUserFlowable("")
|
||||
}
|
||||
}
|
||||
"does not update user for team tasks" {
|
||||
|
|
|
|||
Loading…
Reference in a new issue