More coroutine work

This commit is contained in:
Phillip Thelen 2022-11-07 13:37:43 +01:00
parent 576d81c868
commit 4520248ed8
49 changed files with 349 additions and 253 deletions

View file

@ -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"

View file

@ -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>

View file

@ -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>>

View file

@ -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);
}

View file

@ -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>

View file

@ -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)

View file

@ -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> {

View file

@ -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())

View file

@ -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>>
}

View file

@ -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)

View file

@ -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) {

View file

@ -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
}

View file

@ -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 {
}
}

View file

@ -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
}

View file

@ -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()
}
}
}

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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(
{

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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(""))
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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>>>> =

View file

@ -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
}
}
}
}

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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" {