Fully remove RxJava

This commit is contained in:
Phillip Thelen 2022-11-17 14:19:21 +01:00
parent c4a482eac8
commit 3bd68fedf0
66 changed files with 309 additions and 916 deletions

View file

@ -35,7 +35,6 @@ dependencies {
exclude module: 'okhttp'
}
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version"
//Dependency Injection
implementation "com.google.dagger:dagger:$daggerhilt_version"
@ -54,17 +53,11 @@ dependencies {
implementation('com.jaredrummler:android-device-names:2.1.0')
// IAP Handling / Verification
implementation "com.android.billingclient:billing-ktx:5.0.0"
implementation "com.android.billingclient:billing-ktx:5.1.0"
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar'
//RxJava
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.4'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
implementation "com.github.akarnokd:rxjava3-bridge:3.0.2"
//Analytics
implementation "com.amplitude:android-sdk:$amplitude_version"
implementation "com.amplitude:analytics-android:$amplitude_version"
//Tests
testImplementation 'io.kotest:kotest-runner-junit5:5.3.0'
@ -80,9 +73,9 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
debugImplementation 'androidx.fragment:fragment-testing:1.5.4'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.4'
androidTestImplementation 'io.mockk:mockk-android:1.12.3'
androidTestImplementation 'io.kotest:kotest-assertions-core:5.3.0'
androidTestUtil("androidx.test:orchestrator:1.4.1")
@ -172,7 +165,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
}
signingConfigs {

View file

@ -18,8 +18,6 @@ import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.amplitude.api.Amplitude
import com.amplitude.api.Identify
import com.google.android.gms.wearable.Wearable
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.installations.FirebaseInstallations
@ -29,6 +27,7 @@ import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.modules.UserModule
@ -76,11 +75,7 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife
if (!BuildConfig.DEBUG) {
try {
Amplitude.getInstance().initialize(this, getString(R.string.amplitude_app_id)).enableForegroundTracking(this)
val identify = Identify()
.setOnce("androidStore", BuildConfig.STORE)
.set("launch_screen", sharedPrefs.getString("launch_screen", ""))
Amplitude.getInstance().identify(identify)
AmplitudeManager.initialize(this, sharedPrefs)
} catch (ignored: Resources.NotFoundException) {
}
}

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.data.implementation
import android.content.Context
import com.amplitude.api.Amplitude
import com.google.gson.JsonSyntaxException
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.HabiticaBaseApplication
@ -51,7 +50,6 @@ import com.habitrpg.shared.habitica.models.responses.FeedResponse
import com.habitrpg.shared.habitica.models.responses.Status
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import io.reactivex.rxjava3.functions.Consumer
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.Request
@ -59,7 +57,6 @@ import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.HttpException
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.io.IOException
@ -76,7 +73,7 @@ class ApiClientImpl(
private val analyticsManager: AnalyticsManager,
private val notificationsManager: NotificationsManager,
private val context: Context
) : Consumer<Throwable>, ApiClient {
): ApiClient {
private lateinit var retrofitAdapter: Retrofit
@ -157,7 +154,6 @@ class ApiClientImpl(
retrofitAdapter = Retrofit.Builder()
.client(client)
.baseUrl(server.toString())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(converter)
.build()
@ -211,7 +207,7 @@ class ApiClientImpl(
return process { apiService.loginApple(mapOf(Pair("code", authToken))) }
}
override fun accept(throwable: Throwable) {
fun accept(throwable: Throwable) {
val throwableClass = throwable.javaClass
if (SocketTimeoutException::class.java.isAssignableFrom(throwableClass)) {
return
@ -221,7 +217,7 @@ class ApiClientImpl(
this.showConnectionProblemDialog(R.string.internal_error_api)
} else if (throwableClass == SocketTimeoutException::class.java || UnknownHostException::class.java == throwableClass || IOException::class.java == throwableClass) {
this.showConnectionProblemDialog(R.string.network_error_no_network_body)
} else if (retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) || HttpException::class.java.isAssignableFrom(throwable.javaClass)) {
} else if (HttpException::class.java.isAssignableFrom(throwable.javaClass)) {
val error = throwable as HttpException
val res = getErrorResponse(error)
val status = error.code()
@ -319,7 +315,6 @@ class ApiClientImpl(
this.hostConfig.userID = userID ?: ""
this.hostConfig.apiKey = apiToken ?: ""
analyticsManager.setUserIdentifier(this.hostConfig.userID)
Amplitude.getInstance().userId = this.hostConfig.userID
}
override suspend fun getStatus(): Status? = process { apiService.getStatus() }

View file

@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.ContentResult
import com.habitrpg.android.habitica.models.WorldState
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface ContentLocalRepository : BaseLocalRepository {

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.Tag
import io.reactivex.rxjava3.core.Flowable
import kotlinx.coroutines.flow.Flow
interface TagLocalRepository : BaseLocalRepository {

View file

@ -5,7 +5,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Maybe
import kotlinx.coroutines.flow.Flow
interface TaskLocalRepository : BaseLocalRepository {
@ -26,7 +25,7 @@ interface TaskLocalRepository : BaseLocalRepository {
fun getTaskAtPosition(taskType: String, position: Int): Flow<Task>
fun updateIsdue(daily: TaskList): Maybe<TaskList>
fun updateIsdue(daily: TaskList): TaskList
fun updateTaskPositions(taskOrder: List<String>)
fun saveCompletedTodos(userId: String, tasks: MutableCollection<Task>)

View file

@ -1,12 +1,9 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.BaseLocalRepository
import com.habitrpg.android.habitica.extensions.filterMap
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.user.User
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.reactivex.rxjava3.core.Flowable
import io.realm.Realm
import io.realm.RealmObject
import io.realm.kotlin.deleteFromRealm
@ -115,15 +112,4 @@ abstract class RealmBaseLocalRepository internal constructor(override var realm:
.filter { it.isLoaded && it.isValid && !it.isEmpty() }
.map { it.firstOrNull() }
}
fun queryUserFlowable(userID: String): Flowable<User> {
return RxJavaBridge.toV3Flowable(
realm.where(User::class.java)
.equalTo("id", userID)
.findAll()
.asFlowable()
)
.filter { it.isLoaded && it.isValid && !it.isEmpty() }
.filterMap { it.first() }
}
}

View file

@ -8,8 +8,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
@ -227,15 +225,12 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filterNotNull()
}
override fun updateIsdue(daily: TaskList): Maybe<TaskList> {
return Flowable.just(realm.where(Task::class.java).equalTo("typeValue", TaskType.DAILY.value).findAll())
.firstElement()
.map { tasks ->
realm.beginTransaction()
tasks.filter { daily.tasks.containsKey(it.id) }.forEach { it.isDue = daily.tasks[it.id]?.isDue }
realm.commitTransaction()
daily
}
override fun updateIsdue(daily: TaskList): TaskList {
val tasks = realm.where(Task::class.java).equalTo("typeValue", TaskType.DAILY.value).findAll()
realm.beginTransaction()
tasks.filter { daily.tasks.containsKey(it.id) }.forEach { it.isDue = daily.tasks[it.id]?.isDue }
realm.commitTransaction()
return daily
}
override fun updateTaskPositions(taskOrder: List<String>) {

View file

@ -1,7 +1,6 @@
package com.habitrpg.android.habitica.data.local.implementation
import com.habitrpg.android.habitica.data.local.UserLocalRepository
import com.habitrpg.android.habitica.extensions.filterMap
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
@ -12,12 +11,10 @@ import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import hu.akarnokd.rxjava3.bridge.RxJavaBridge
import io.realm.Realm
import io.realm.kotlin.toFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull

View file

@ -1,8 +0,0 @@
package com.habitrpg.android.habitica.executors;
import io.reactivex.rxjava3.core.Scheduler;
public interface PostExecutionThread {
Scheduler getScheduler();
}

View file

@ -1,19 +0,0 @@
package com.habitrpg.android.habitica.executors;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Scheduler;
@Singleton
public class UIThread implements PostExecutionThread {
@Inject
public UIThread() {}
@Override
public Scheduler getScheduler() {
return AndroidSchedulers.mainThread();
}
}

View file

@ -1,13 +1,18 @@
package com.habitrpg.android.habitica.extensions
import io.reactivex.rxjava3.core.Completable
import java.util.concurrent.TimeUnit
import com.habitrpg.android.habitica.helpers.launchCatching
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlin.time.DurationUnit
import kotlin.time.toDuration
/**
* Created by phillip on 01.02.18.
*/
fun runDelayed(interval: Long, timeUnit: TimeUnit, function: () -> Unit) {
Completable.complete().delay(interval, timeUnit)
.subscribe(function)
fun runDelayed(interval: Long, timeUnit: DurationUnit, function: () -> Unit) {
MainScope().launchCatching {
delay(interval.toDuration(timeUnit))
function()
}
}

View file

@ -1,10 +0,0 @@
package com.habitrpg.android.habitica.extensions
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.functions.Consumer
fun <T : Any> Flowable<T>.subscribeWithErrorHandler(function: Consumer<T>): Disposable {
return subscribe(function, ExceptionHandler.rx())
}

View file

@ -1,63 +0,0 @@
package com.habitrpg.android.habitica.extensions
import com.habitrpg.common.habitica.extensions.Optional
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
fun <S : Any, T : Optional<S>> Flowable<T>.filterOptionalDoOnEmpty(function: () -> Unit): Flowable<S> {
return this.doOnNext { if (it.isEmpty) function() }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S : Any, T : Optional<S>> Flowable<T>.filterMapEmpty(): Flowable<S> {
return this.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S : Any, T : Any> Flowable<T>.filterMap(function: (T) -> S?): Flowable<S> {
return this.map { Optional(function(it)) }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S : Any, T : Optional<S>> Observable<T>.filterOptionalDoOnEmpty(function: () -> Unit): Observable<S> {
return this.doOnNext { if (it.isEmpty) function() }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S : Any, T : Optional<S>> Observable<T>.filterMapEmpty(): Observable<S> {
return this.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S : Any, T : Any> Observable<T>.filterMap(function: (T) -> S?): Observable<S> {
return this.map { Optional(function(it)) }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S, T : Optional<S>> Single<T>.filterOptionalDoOnEmpty(function: () -> Unit): Maybe<S> {
return this.doOnSuccess { if (it.isEmpty) function() }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S, T : Optional<S>> Single<T>.filterMapEmpty(): Maybe<S> {
return this.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S, T : Optional<S>> Maybe<T>.filterOptionalDoOnEmpty(function: () -> Unit): Maybe<S> {
return this.doOnSuccess { if (it.isEmpty) function() }
.filter { !it.isEmpty }
.map { it.assertedValue }
}
fun <S, T : Optional<S>> Maybe<T>.filterMapEmpty(): Maybe<S> {
return this.filter { !it.isEmpty }
.map { it.assertedValue }
}

View file

@ -1,9 +1,12 @@
package com.habitrpg.android.habitica.helpers
import com.amplitude.api.Amplitude
import android.content.Context
import android.content.SharedPreferences
import com.amplitude.android.Amplitude
import com.amplitude.android.Configuration
import com.amplitude.android.events.Identify
import com.habitrpg.android.habitica.BuildConfig
import org.json.JSONException
import org.json.JSONObject
import com.habitrpg.android.habitica.R
object AmplitudeManager {
var EVENT_CATEGORY_BEHAVIOUR = "behaviour"
@ -14,6 +17,8 @@ object AmplitudeManager {
var EVENT_HITTYPE_REMOVE_WIDGET = "remove"
var EVENT_HITTYPE_UPDATE_WIDGET = "update"
lateinit var amplitude: Amplitude
@JvmOverloads
fun sendEvent(
eventAction: String?,
@ -24,20 +29,20 @@ object AmplitudeManager {
if (BuildConfig.DEBUG) {
return
}
val eventProperties = JSONObject()
try {
eventProperties.put("eventAction", eventAction)
eventProperties.put("eventCategory", eventCategory)
eventProperties.put("hitType", hitType)
eventProperties.put("status", "displayed")
if (additionalData != null) {
for ((key, value) in additionalData) {
eventProperties.put(key, value)
}
val data = mutableMapOf<String, Any?>(
"eventAction" to eventAction,
"eventCategory" to eventCategory,
"hitType" to hitType,
"status" to "displayed"
)
if (additionalData != null) {
for ((key, value) in additionalData) {
data.put(key, value)
}
} catch (exception: JSONException) {
}
Amplitude.getInstance().logEvent(eventAction, eventProperties)
if (eventAction != null) {
amplitude.track(eventAction, data)
}
}
fun sendNavigationEvent(page: String) {
@ -45,4 +50,20 @@ object AmplitudeManager {
additionalData["page"] = page
sendEvent("navigated", EVENT_CATEGORY_NAVIGATION, EVENT_HITTYPE_PAGEVIEW, additionalData)
}
fun initialize(context: Context, sharedPrefs: SharedPreferences) {
amplitude = Amplitude(
Configuration(
context.getString(R.string.amplitude_app_id),
context
)
)
val identify = Identify()
.setOnce("androidStore", BuildConfig.STORE)
sharedPrefs.getString("launch_screen", "")?.let {
identify.set("launch_screen", it)
}
amplitude.identify(identify)
}
}

View file

@ -3,7 +3,6 @@ package com.habitrpg.android.habitica.helpers
import android.util.Log
import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import io.reactivex.rxjava3.functions.Consumer
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -30,11 +29,6 @@ class ExceptionHandler {
}
}
fun rx(): Consumer<Throwable> {
// Can't be turned into a lambda, because it then doesn't work for some reason.
return Consumer { reportError(it) }
}
fun reportError(throwable: Throwable) {
if (BuildConfig.DEBUG) {
try {
@ -46,7 +40,6 @@ class ExceptionHandler {
!HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
!retrofit2.HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
!EOFException::class.java.isAssignableFrom(throwable.javaClass) &&
!retrofit2.adapter.rxjava3.HttpException::class.java.isAssignableFrom(throwable.javaClass) &&
throwable !is ConnectionShutdownException
) {
instance.analyticsManager?.logException(throwable)

View file

@ -4,16 +4,15 @@ import android.annotation.SuppressLint
import android.content.Context
import android.os.Environment
import com.habitrpg.android.habitica.HabiticaBaseApplication
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.File
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.buffer
import okio.sink
import java.io.File
import java.io.IOException
// based on http://stackoverflow.com/questions/29838565/downloading-files-using-okhttp-okio-and-rxjava
class SoundFileLoader(private val context: Context) {
@ -26,52 +25,42 @@ class SoundFileLoader(private val context: Context) {
}
@SuppressLint("SetWorldReadable", "ReturnCount")
fun download(files: List<SoundFile>): Single<List<SoundFile>> {
return Observable.fromIterable(files)
.flatMap(
{ audioFile ->
suspend fun download(files: List<SoundFile>): List<SoundFile> {
return files.map { audioFile ->
withContext(Dispatchers.IO) {
val file = File(getFullAudioFilePath(audioFile))
if (file.exists() && file.length() > 5000) {
// Important, or else the MediaPlayer can't access this file
file.setReadable(true, false)
audioFile.file = file
return@flatMap Observable.just(audioFile)
return@withContext audioFile
}
val request = Request.Builder().url(audioFile.webUrl).build()
val response: Response
try {
response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException()
}
} catch (io: IOException) {
return@withContext audioFile
}
val fileObservable = Observable.create<SoundFile> { sub ->
val request = Request.Builder().url(audioFile.webUrl).build()
val response: Response
try {
response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException()
}
} catch (io: IOException) {
sub.onComplete()
return@create
}
try {
val sink = file.sink().buffer()
sink.writeAll(response.body!!.source())
sink.flush()
sink.close()
} catch (io: IOException) {
sub.onComplete()
return@create
}
file.setReadable(true, false)
audioFile.file = file
sub.onNext(audioFile)
sub.onComplete()
try {
val sink = file.sink().buffer()
sink.writeAll(response.body!!.source())
sink.flush()
sink.close()
} catch (io: IOException) {
return@withContext audioFile
}
fileObservable.subscribeOn(Schedulers.io())
},
5
)
.toList()
file.setReadable(true, false)
audioFile.file = file
return@withContext audioFile
}
}
}
private fun getFullAudioFilePath(soundFile: SoundFile): String =

View file

@ -1,7 +1,7 @@
package com.habitrpg.android.habitica.helpers
import com.habitrpg.android.habitica.HabiticaBaseApplication
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.MainScope
import javax.inject.Inject
class SoundManager {
@ -33,8 +33,9 @@ class SoundManager {
soundFiles.add(SoundFile(soundTheme, SoundPlusHabit))
soundFiles.add(SoundFile(soundTheme, SoundReward))
soundFiles.add(SoundFile(soundTheme, SoundTodo))
soundFileLoader.download(soundFiles)
.subscribe({}, ExceptionHandler.rx())
MainScope().launchCatching {
soundFileLoader.download(soundFiles)
}
}
fun loadAndPlayAudio(type: String) {
@ -48,14 +49,12 @@ class SoundManager {
val soundFiles = ArrayList<SoundFile>()
soundFiles.add(SoundFile(soundTheme, type))
soundFileLoader.download(soundFiles).observeOn(Schedulers.newThread()).subscribe(
{
val file = soundFiles[0]
loadedSoundFiles[type] = file
file.play()
},
ExceptionHandler.rx()
)
MainScope().launchCatching {
val newFiles = soundFileLoader.download(soundFiles)
val file = newFiles[0]
loadedSoundFiles[type] = file
file.play()
}
}
}

View file

@ -3,12 +3,11 @@ package com.habitrpg.android.habitica.interactors
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity
import javax.inject.Inject
class CheckClassSelectionUseCase @Inject constructor(postExecutionThread: PostExecutionThread) : FlowUseCase<CheckClassSelectionUseCase.RequestValues, Unit>() {
class CheckClassSelectionUseCase @Inject constructor() : FlowUseCase<CheckClassSelectionUseCase.RequestValues, Unit>() {
override suspend fun run(requestValues: RequestValues) {
val user = requestValues.user

View file

@ -1,21 +1,8 @@
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 callInteractor(requestValues: Q): T {

View file

@ -14,7 +14,6 @@ import com.habitrpg.common.habitica.helpers.KeyHelper
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.ref.WeakReference
import javax.inject.Singleton
@ -66,7 +65,6 @@ open class ApiModule {
fun providesMaintenanceApiService(gsonConverter: GsonConverterFactory): MaintenanceApiService {
val adapter = Retrofit.Builder()
.baseUrl("https://habitica-assets.s3.amazonaws.com/mobileApp/endpoint/")
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(gsonConverter)
.build()
return adapter.create(MaintenanceApiService::class.java)

View file

@ -7,8 +7,6 @@ import android.content.res.Resources
import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.executors.UIThread
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.SoundFileLoader
import com.habitrpg.android.habitica.helpers.SoundManager
@ -84,12 +82,6 @@ class AppModule(private val application: Application) {
return SoundManager()
}
@Provides
@Singleton
fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread {
return uiThread
}
@Provides
@Singleton
fun pushNotificationManager(

View file

@ -19,9 +19,9 @@ import com.habitrpg.android.habitica.ui.fragments.social.party.PartyInviteFragme
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
import kotlin.time.DurationUnit
class GroupInviteActivity : BaseActivity() {
@ -70,7 +70,7 @@ class GroupInviteActivity : BaseActivity() {
dismissKeyboard()
if (fragments.size > binding.viewPager.currentItem && fragments[binding.viewPager.currentItem].values.isNotEmpty()) {
showSnackbar(binding.snackbarView, "Invite Sent!", HabiticaSnackbar.SnackbarDisplayType.SUCCESS)
runDelayed(1, TimeUnit.SECONDS, this::finish)
runDelayed(1, DurationUnit.SECONDS, this::finish)
} else {
finish()
}

View file

@ -32,7 +32,6 @@ import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment
import com.habitrpg.common.habitica.api.HostConfig
import com.viewpagerindicator.IconPagerAdapter
import io.reactivex.rxjava3.core.BackpressureStrategy
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.Locale
@ -260,9 +259,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
else -> {
val fragment = WelcomeFragment()
welcomeFragment = fragment
welcomeFragment?.nameValidEvents?.toFlowable(BackpressureStrategy.DROP)?.subscribe {
setNextButtonEnabled(it)
}
welcomeFragment?.onNameValid = { setNextButtonEnabled(it == true) }
fragment
}
}
@ -283,9 +280,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
}
is WelcomeFragment -> {
welcomeFragment = item
item.nameValidEvents.toFlowable(BackpressureStrategy.DROP).subscribe {
setNextButtonEnabled(it)
}
item.onNameValid = { setNextButtonEnabled(it == true) }
}
}
return item

View file

@ -1,159 +0,0 @@
package com.habitrpg.android.habitica.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.views.PixelArtView
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
var gemBalance: Int = 0
var equipmentList: MutableList<Equipment> =
ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
var activeEquipment: String? = null
set(value) {
field = value
this.notifyDataSetChanged()
}
private val selectCustomizationEvents = PublishSubject.create<Equipment>()
private val unlockCustomizationEvents = PublishSubject.create<Equipment>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder {
val viewID: Int = R.layout.customization_grid_item
val view = LayoutInflater.from(parent.context).inflate(viewID, parent, false)
return EquipmentViewHolder(view)
}
override fun onBindViewHolder(
holder: androidx.recyclerview.widget.RecyclerView.ViewHolder,
position: Int
) {
(holder as EquipmentViewHolder).bind(equipmentList[position])
}
override fun getItemCount(): Int {
return equipmentList.size
}
override fun getItemViewType(position: Int): Int {
if (equipmentList.size <= position) return 0
return if (this.equipmentList[position].javaClass == CustomizationSet::class.java) {
0
} else {
1
}
}
fun setEquipment(newEquipmentList: List<Equipment>) {
this.equipmentList = newEquipmentList.toMutableList()
val emptyEquipment = Equipment()
equipmentList.add(0, emptyEquipment)
this.notifyDataSetChanged()
}
fun getSelectCustomizationEvents(): Flowable<Equipment> {
return selectCustomizationEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getUnlockCustomizationEvents(): Flowable<Equipment> {
return unlockCustomizationEvents.toFlowable(BackpressureStrategy.DROP)
}
internal inner class EquipmentViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val binding = CustomizationGridItemBinding.bind(itemView)
var equipment: Equipment? = null
init {
itemView.setOnClickListener(this)
}
fun bind(equipment: Equipment) {
this.equipment = equipment
binding.imageView.loadImage("shop_" + this.equipment?.key)
if (equipment.owned == true || equipment.value == 0.0) {
binding.buyButton.visibility = View.GONE
} else {
binding.buyButton.visibility = View.VISIBLE
binding.priceLabel.currency = "gems"
binding.priceLabel.value = if (equipment.gearSet == "animal") {
2.0
} else {
equipment.value
}
}
if (activeEquipment == equipment.key || (activeEquipment?.contains("base_0") == true && equipment.key?.isNotBlank() != true)) {
binding.wrapper.background = ContextCompat.getDrawable(itemView.context, R.drawable.layout_rounded_bg_window_tint_border)
} else {
binding.wrapper.background = ContextCompat.getDrawable(itemView.context, R.drawable.layout_rounded_bg_window)
}
}
override fun onClick(v: View) {
if (equipment?.owned != true && (equipment?.value ?: 0.0) > 0.0) {
val dialogContent = LayoutInflater.from(itemView.context).inflate(R.layout.dialog_purchase_customization, null) as LinearLayout
val imageView = dialogContent.findViewById<PixelArtView>(R.id.imageView)
imageView.loadImage("shop_" + this.equipment?.key)
val priceLabel = dialogContent.findViewById<TextView>(R.id.priceLabel)
priceLabel.text = if (equipment?.gearSet == "animal") {
2.0
} else {
equipment?.value ?: 0
}.toString()
(dialogContent.findViewById<View>(R.id.gem_icon) as? ImageView)?.setImageBitmap(
HabiticaIconsHelper.imageOfGem())
val dialog = HabiticaAlertDialog(itemView.context)
dialog.addButton(R.string.purchase_button, true) { _, _ ->
if (equipment?.value ?: 0.0 > gemBalance) {
MainNavigationController.navigate(R.id.gemPurchaseActivity, bundleOf(Pair("openSubscription", false)))
return@addButton
}
equipment?.let {
unlockCustomizationEvents.onNext(it)
}
}
dialog.setTitle(R.string.purchase_customization)
dialog.setAdditionalContentView(dialogContent)
dialog.addButton(R.string.reward_dialog_dismiss, false)
dialog.show()
return
}
if (equipment?.key == activeEquipment) {
return
}
equipment?.let {
selectCustomizationEvents.onNext(it)
}
}
}
}

View file

@ -14,9 +14,6 @@ import com.habitrpg.android.habitica.ui.views.promo.PromoMenuViewHolder
import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoView
import com.habitrpg.android.habitica.ui.views.promo.SubscriptionBuyGemsPromoViewHolder
import com.habitrpg.common.habitica.extensions.dpToPx
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -46,14 +43,11 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
notifyDataSetChanged()
}
private val itemSelectedEvents = PublishSubject.create<HabiticaDrawerItem>()
private val promoClosedSubject = PublishSubject.create<String>()
var itemSelectedEvents: ((HabiticaDrawerItem) -> Unit)? = null
var promoClosedSubject: ((String) -> Unit)? = null
var activePromo: HabiticaPromotion? = null
fun getItemSelectionEvents(): Flowable<HabiticaDrawerItem> = itemSelectedEvents.toFlowable(BackpressureStrategy.DROP)
fun getPromoCloseEvents(): Flowable<String> = promoClosedSubject.toFlowable(BackpressureStrategy.DROP)
fun getItemWithIdentifier(identifier: String): HabiticaDrawerItem? =
items.find { it.identifier == identifier }
@ -81,7 +75,7 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
val itemHolder = holder as? DrawerItemViewHolder
itemHolder?.tintColor = tintColor
itemHolder?.bind(drawerItem, drawerItem.transitionId == selectedItem)
itemHolder?.itemView?.setOnClickListener { itemSelectedEvents.onNext(drawerItem) }
itemHolder?.itemView?.setOnClickListener { itemSelectedEvents?.invoke(drawerItem) }
}
getItemViewType(position) == 1 -> {
(holder as? SectionHeaderViewHolder)?.backgroundTintColor = backgroundTintColor
@ -91,7 +85,7 @@ class NavigationDrawerAdapter(tintColor: Int, backgroundTintColor: Int) : Recycl
activePromo?.let { promo ->
(holder as? PromoMenuViewHolder)?.bind(promo)
(holder as? PromoMenuViewHolder)?.promoView?.binding?.closeButton?.setOnClickListener {
promoClosedSubject.onNext(promo.identifier)
promoClosedSubject?.invoke(promo.identifier)
}
}
}

View file

@ -10,20 +10,16 @@ import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.SkillListItemBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.extensions.loadImage
import io.realm.RealmList
class SkillsRecyclerViewAdapter : RecyclerView.Adapter<SkillsRecyclerViewAdapter.SkillViewHolder>() {
private val useSkillSubject = PublishSubject.create<Skill>()
val useSkillEvents: Flowable<Skill> = useSkillSubject.toFlowable(BackpressureStrategy.DROP)
var onUseSkill: ((Skill) -> Unit)? = null
var mana: Double = 0.0
set(value) {
@ -130,7 +126,7 @@ class SkillsRecyclerViewAdapter : RecyclerView.Adapter<SkillsRecyclerViewAdapter
override fun onClick(v: View) {
if ((skill?.lvl ?: 0) <= level) {
skill?.let { useSkillSubject.onNext(it) }
skill?.let { onUseSkill?.invoke(it) }
}
}

View file

@ -6,13 +6,11 @@ import com.habitrpg.android.habitica.models.inventory.StableSection
import com.habitrpg.android.habitica.models.user.OwnedMount
import com.habitrpg.android.habitica.ui.viewHolders.MountViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import io.reactivex.rxjava3.subjects.PublishSubject
class MountDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
var onEquip: ((String) -> Unit)? = null
private var ownedMounts: Map<String, OwnedMount>? = null
private val equipEvents = PublishSubject.create<String>()
var currentMount: String? = null
set(value) {
field = value

View file

@ -20,7 +20,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
import io.reactivex.rxjava3.subjects.PublishSubject
class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
var onFeed: ((Pet, Food?) -> Unit)? = null
@ -34,8 +33,6 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
field = value
notifyDataSetChanged()
}
private val equipEvents = PublishSubject.create<String>()
private val feedEvents = PublishSubject.create<Pair<Pet, Food?>>()
private var ownsSaddles: Boolean = false
private var itemList: List<Any> = ArrayList()

View file

@ -18,9 +18,6 @@ import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.BehaviorSubject
class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder>() {
@ -28,8 +25,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
private var shopIdentifier: String? = null
private var ownedItems: Map<String, OwnedItem> = HashMap()
private val changeClassSubject = BehaviorSubject.create<String>()
val changeClassEvents: Flowable<String> = changeClassSubject.toFlowable(BackpressureStrategy.DROP)
var changeClassEvents: ((String) -> Unit)? = null
var shopSpriteSuffix: String = ""
set(value) {
@ -128,7 +124,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
sectionHolder.notesView?.text = context.getString(R.string.class_gear_disclaimer)
if (user?.hasClass == true) {
sectionHolder.switchClassButton?.setOnClickListener {
changeClassSubject.onNext(selectedGearCategory)
changeClassEvents?.invoke(selectedGearCategory)
}
// TODO: Enable this again when we have a nicer design
sectionHolder.switchClassButton?.visibility = View.GONE

View file

@ -12,9 +12,7 @@ import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeFilterOptions
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.helpers.EmojiParser
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
import io.realm.OrderedRealmCollection
class ChallengesListViewAdapter(
@ -24,7 +22,7 @@ class ChallengesListViewAdapter(
private var unfilteredData: List<Challenge>? = null
private var challengeMemberships: List<ChallengeMembership>? = null
private val openChallengeFragmentEvents = PublishSubject.create<String>()
var onOpenChallengeFragment: ((String) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChallengeViewHolder {
return ChallengeViewHolder(parent.inflate(R.layout.challenge_item), viewUserChallengesOnly)
@ -36,7 +34,7 @@ class ChallengesListViewAdapter(
holder.itemView.setOnClickListener {
if (challenge.isManaged && challenge.isValid) {
challenge.id?.let {
openChallengeFragmentEvents.onNext(it)
onOpenChallengeFragment?.invoke(it)
}
}
}
@ -76,10 +74,6 @@ class ChallengesListViewAdapter(
}
}
fun getOpenDetailFragmentFlowable(): Flowable<String> {
return openChallengeFragmentEvents.toFlowable(BackpressureStrategy.DROP)
}
class ChallengeViewHolder internal constructor(
itemView: View,
private val viewUserChallengesOnly: Boolean

View file

@ -12,9 +12,6 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.DiffCallback
import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class ChatDiffCallback(oldList: List<BaseMainObject>, newList: List<BaseMainObject>) :
DiffCallback<ChatMessage>(oldList, newList) {
@ -39,11 +36,11 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base
private var expandedMessageId: String? = null
var onMessageLike: ((ChatMessage) -> Unit)? = null
private val userLabelClickEvents = PublishSubject.create<String>()
private val deleteMessageEvents = PublishSubject.create<ChatMessage>()
private val flagMessageEvents = PublishSubject.create<ChatMessage>()
private val replyMessageEvents = PublishSubject.create<String>()
private val copyMessageEvents = PublishSubject.create<ChatMessage>()
var onOpenProfile: ((String) -> Unit)? = null
var onDeleteMessage: ((ChatMessage) -> Unit)? = null
var onFlagMessage: ((ChatMessage) -> Unit)? = null
var onReply: ((String) -> Unit)? = null
var onCopyMessage: ((ChatMessage) -> Unit)? = null
override fun getDiffCallback(
oldList: List<ChatMessage>,
@ -83,12 +80,12 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base
expandedMessageId == message.id
)
chatHolder.onShouldExpand = { expandMessage(message, position) }
chatHolder.onLikeMessage = { onMessageLike?.invoke(it) }
chatHolder.onOpenProfile = { userLabelClickEvents.onNext(it) }
chatHolder.onReply = { replyMessageEvents.onNext(it) }
chatHolder.onCopyMessage = { copyMessageEvents.onNext(it) }
chatHolder.onFlagMessage = { flagMessageEvents.onNext(it) }
chatHolder.onDeleteMessage = { deleteMessageEvents.onNext(it) }
chatHolder.onLikeMessage = onMessageLike
chatHolder.onOpenProfile = onOpenProfile
chatHolder.onReply = onReply
chatHolder.onCopyMessage = onCopyMessage
chatHolder.onFlagMessage = onFlagMessage
chatHolder.onDeleteMessage = onDeleteMessage
}
}
@ -97,26 +94,6 @@ class ChatRecyclerViewAdapter(user: User?, private val isTavern: Boolean) : Base
return if (data[position].isSystemMessage) 0 else 1
}
fun getUserLabelClickFlowable(): Flowable<String> {
return userLabelClickEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getFlagMessageClickFlowable(): Flowable<ChatMessage> {
return flagMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getDeleteMessageFlowable(): Flowable<ChatMessage> {
return deleteMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getReplyMessageEvents(): Flowable<String> {
return replyMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getCopyMessageFlowable(): Flowable<ChatMessage> {
return copyMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
private fun expandMessage(message: ChatMessage, position: Int?) {
expandedMessageId = if (expandedMessageId == message.id) {
null

View file

@ -11,20 +11,17 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerIntroViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerMessageViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.ChatRecyclerViewHolder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class InboxAdapter(private var user: User?, private var replyToUser: Member?) : PagedListAdapter<ChatMessage, ChatRecyclerViewHolder>(DIFF_CALLBACK) {
private val FIRST_MESSAGE = 0
private val NORMAL_MESSAGE = 1
private var expandedMessageId: String? = null
private val userLabelClickEvents = PublishSubject.create<String>()
private val deleteMessageEvents = PublishSubject.create<ChatMessage>()
private val flagMessageEvents = PublishSubject.create<ChatMessage>()
private val replyMessageEvents = PublishSubject.create<String>()
private val copyMessageEvents = PublishSubject.create<ChatMessage>()
var onOpenProfile: ((String) -> Unit)? = null
var onDeleteMessage: ((ChatMessage) -> Unit)? = null
var onFlagMessage: ((ChatMessage) -> Unit)? = null
var onReply: ((String) -> Unit)? = null
var onCopyMessage: ((ChatMessage) -> Unit)? = null
private fun isPositionIntroMessage(position: Int): Boolean {
return (position == super.getItemCount() - 1)
@ -52,7 +49,7 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member?) :
if (firstMessage) {
val introHolder = holder as ChatRecyclerIntroViewHolder
introHolder.bind(replyToUser)
introHolder.onOpenProfile = { userLabelClickEvents.onNext(it) }
introHolder.onOpenProfile = onOpenProfile
} else {
val message: ChatMessage = getItem(position) ?: return
val messageHolder = holder as ChatRecyclerMessageViewHolder
@ -63,30 +60,14 @@ class InboxAdapter(private var user: User?, private var replyToUser: Member?) :
expandedMessageId == message.id
)
messageHolder.onShouldExpand = { expandMessage(message.id, position) }
messageHolder.onOpenProfile = { userLabelClickEvents.onNext(it) }
messageHolder.onReply = { replyMessageEvents.onNext(it) }
messageHolder.onCopyMessage = { copyMessageEvents.onNext(it) }
messageHolder.onFlagMessage = { flagMessageEvents.onNext(it) }
messageHolder.onDeleteMessage = { deleteMessageEvents.onNext(it) }
messageHolder.onOpenProfile = onOpenProfile
messageHolder.onReply = onReply
messageHolder.onCopyMessage = onCopyMessage
messageHolder.onFlagMessage = onFlagMessage
messageHolder.onDeleteMessage = onDeleteMessage
}
}
fun getUserLabelClickFlowable(): Flowable<String> {
return userLabelClickEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getFlagMessageClickFlowable(): Flowable<ChatMessage> {
return flagMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getDeleteMessageFlowable(): Flowable<ChatMessage> {
return deleteMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
fun getCopyMessageFlowable(): Flowable<ChatMessage> {
return copyMessageEvents.toFlowable(BackpressureStrategy.DROP)
}
private fun expandMessage(id: String, position: Int) {
if (isPositionIntroMessage(position))
return

View file

@ -10,14 +10,14 @@ class DailiesRecyclerViewHolder(layoutResource: Int, viewModel: TasksViewModel)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
DailyViewHolder(
getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) },
{ task, item -> checklistItemScoreSubject.onNext(Pair(task, item)) },
getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) },
{ task, item -> checklistItemScoreEvents?.invoke(task, item) },
{
task ->
taskOpenEventsSubject.onNext(task)
taskOpenEvents?.invoke(task.first, task.second)
}, {
task ->
brokenTaskEventsSubject.onNext(task)
brokenTaskEvents?.invoke(task)
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)

View file

@ -10,13 +10,13 @@ class HabitsRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
HabitViewHolder(
getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) },
getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) },
{
task ->
taskOpenEventsSubject.onNext(task)
taskOpenEvents?.invoke(task.first, task.second)
}, {
task ->
brokenTaskEventsSubject.onNext(task)
brokenTaskEvents?.invoke(task)
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)

View file

@ -19,10 +19,6 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.functions.Action
import io.reactivex.rxjava3.subjects.PublishSubject
import io.realm.OrderedRealmCollection
abstract class RealmBaseTasksRecyclerViewAdapter(
@ -50,18 +46,12 @@ abstract class RealmBaseTasksRecyclerViewAdapter(
}
}
private var errorButtonEventsSubject: PublishSubject<String> = PublishSubject.create()
override val errorButtonEvents: Flowable<String> = errorButtonEventsSubject.toFlowable(BackpressureStrategy.DROP)
protected var taskScoreEventsSubject: PublishSubject<Pair<Task, TaskDirection>> = PublishSubject.create()
override val taskScoreEvents: Flowable<Pair<Task, TaskDirection>> = taskScoreEventsSubject.toFlowable(BackpressureStrategy.DROP)
protected var checklistItemScoreSubject: PublishSubject<Pair<Task, ChecklistItem>> = PublishSubject.create()
override val checklistItemScoreEvents: Flowable<Pair<Task, ChecklistItem>> = checklistItemScoreSubject.toFlowable(BackpressureStrategy.DROP)
protected var taskOpenEventsSubject: PublishSubject<Pair<Task, View>> = PublishSubject.create()
override val taskOpenEvents: Flowable<Pair<Task, View>> = taskOpenEventsSubject.toFlowable(BackpressureStrategy.DROP)
protected var brokenTaskEventsSubject: PublishSubject<Task> = PublishSubject.create()
override val brokenTaskEvents: Flowable<Task> = brokenTaskEventsSubject.toFlowable(BackpressureStrategy.DROP)
protected var adventureGuideOpenSubject: PublishSubject<Boolean> = PublishSubject.create()
override val adventureGuideOpenEvents: Flowable<Boolean> = adventureGuideOpenSubject.toFlowable(BackpressureStrategy.DROP)
override var errorButtonEvents: ((String) -> Unit)? = null
override var taskScoreEvents: ((Task, TaskDirection) -> Unit)? = null
override var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)? = null
override var taskOpenEvents: ((Task, View) -> Unit)? = null
override var brokenTaskEvents: ((Task) -> Unit)? = null
override var adventureGuideOpenEvents: ((Boolean) -> Unit)? = null
override fun getItemId(index: Int): Long = index.toLong()
@ -82,11 +72,11 @@ abstract class RealmBaseTasksRecyclerViewAdapter(
holder.userID = user?.id
holder.isLocked = !viewModel.canScoreTask(item)
holder.bind(item, position, taskDisplayMode, viewModel.ownerID.value)
holder.errorButtonClicked = Action {
errorButtonEventsSubject.onNext("")
holder.errorButtonClicked = {
errorButtonEvents?.invoke("")
}
} else if (holder is AdventureGuideViewHolder) {
holder.itemView.setOnClickListener { adventureGuideOpenSubject.onNext(true) }
holder.itemView.setOnClickListener { adventureGuideOpenEvents?.invoke(true) }
user?.let { holder.update(it) }
}
}

View file

@ -14,9 +14,6 @@ import com.habitrpg.android.habitica.ui.viewHolders.ShopItemViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
class RewardsRecyclerViewAdapter(
private var customRewards: List<Task>?,
@ -34,19 +31,13 @@ class RewardsRecyclerViewAdapter(
override var showAdventureGuide: Boolean = false
private var inAppRewards: List<ShopItem>? = null
private val errorButtonEventsSubject: PublishSubject<String> = PublishSubject.create()
override val errorButtonEvents: Flowable<String> = errorButtonEventsSubject.toFlowable(BackpressureStrategy.DROP)
private var taskScoreEventsSubject: PublishSubject<Pair<Task, TaskDirection>> = PublishSubject.create()
override val taskScoreEvents: Flowable<Pair<Task, TaskDirection>> = taskScoreEventsSubject.toFlowable(BackpressureStrategy.LATEST)
private var checklistItemScoreSubject: PublishSubject<Pair<Task, ChecklistItem>> = PublishSubject.create()
override val checklistItemScoreEvents: Flowable<Pair<Task, ChecklistItem>> = checklistItemScoreSubject.toFlowable(BackpressureStrategy.DROP)
private var taskOpenEventsSubject: PublishSubject<Pair<Task, View>> = PublishSubject.create()
override val taskOpenEvents: Flowable<Pair<Task, View>> = taskOpenEventsSubject.toFlowable(BackpressureStrategy.LATEST)
private var brokenTaskEventsSubject: PublishSubject<Task> = PublishSubject.create()
override val brokenTaskEvents: Flowable<Task> = brokenTaskEventsSubject.toFlowable(BackpressureStrategy.DROP)
override val adventureGuideOpenEvents: Flowable<Boolean>? = null
private var purchaseCardSubject: PublishSubject<ShopItem> = PublishSubject.create()
val purchaseCardEvents: Flowable<ShopItem> = purchaseCardSubject.toFlowable(BackpressureStrategy.LATEST)
override var errorButtonEvents: ((String) -> Unit)? = null
override var taskScoreEvents: ((Task, TaskDirection) -> Unit)? = null
override var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)? = null
override var taskOpenEvents: ((Task, View) -> Unit)? = null
override var brokenTaskEvents: ((Task) -> Unit)? = null
override var adventureGuideOpenEvents: ((Boolean) -> Unit)? = null
var purchaseCardEvents: ((ShopItem) -> Unit)? = null
override var taskDisplayMode: String = "standard"
set(value) {
@ -78,16 +69,16 @@ class RewardsRecyclerViewAdapter(
getContentView(parent),
{ task, direction ->
if (task.value <= (user?.stats?.gp ?: 0.0)) {
taskScoreEventsSubject.onNext(Pair(task, direction))
taskScoreEvents?.invoke(task, direction)
}
},
{ task -> taskOpenEventsSubject.onNext(task) }, {
{ task -> taskOpenEvents?.invoke(task.first, task.second) }, {
task ->
brokenTaskEventsSubject.onNext(task)
brokenTaskEvents?.invoke(task)
}, viewModel)
} else {
val viewHolder = ShopItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_shopitem, parent, false))
viewHolder.purchaseCardAction = { purchaseCardSubject.onNext(it) }
viewHolder.purchaseCardAction = { purchaseCardEvents?.invoke(it) }
viewHolder
}
}

View file

@ -5,14 +5,13 @@ import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.core.Flowable
interface TaskRecyclerViewAdapter {
var user: User?
var showAdventureGuide: Boolean
var data: List<Task>
val errorButtonEvents: Flowable<String>
var errorButtonEvents: ((String) -> Unit)?
var taskDisplayMode: String
@ -24,9 +23,9 @@ interface TaskRecyclerViewAdapter {
fun updateUnfilteredData(data: List<Task>?)
val taskScoreEvents: Flowable<Pair<Task, TaskDirection>>
val checklistItemScoreEvents: Flowable<Pair<Task, ChecklistItem>>
val taskOpenEvents: Flowable<Pair<Task, View>>
val brokenTaskEvents: Flowable<Task>
val adventureGuideOpenEvents: Flowable<Boolean>?
var taskScoreEvents: ((Task, TaskDirection) -> Unit)?
var checklistItemScoreEvents: ((Task, ChecklistItem) -> Unit)?
var taskOpenEvents: ((Task, View) -> Unit)?
var brokenTaskEvents: ((Task) -> Unit)?
var adventureGuideOpenEvents: ((Boolean) -> Unit)?
}

View file

@ -10,14 +10,14 @@ class TodosRecyclerViewAdapter(layoutResource: Int, viewModel: TasksViewModel) :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == 0) {
TodoViewHolder(
getContentView(parent), { task, direction -> taskScoreEventsSubject.onNext(Pair(task, direction)) },
{ task, item -> checklistItemScoreSubject.onNext(Pair(task, item)) },
getContentView(parent), { task, direction -> taskScoreEvents?.invoke(task, direction) },
{ task, item -> checklistItemScoreEvents?.invoke(task, item) },
{
task ->
taskOpenEventsSubject.onNext(task)
taskOpenEvents?.invoke(task.first, task.second)
}, {
task ->
brokenTaskEventsSubject.onNext(task)
brokenTaskEvents?.invoke(task)
}, viewModel)
} else {
super.onCreateViewHolder(parent, viewType)

View file

@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
@ -32,8 +31,6 @@ abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(
protected var tutorialCanBeDeferred = true
var tutorialTexts: MutableList<String> = ArrayList()
protected var compositeSubscription: CompositeDisposable = CompositeDisposable()
open val displayedClassName: String?
get() = this.javaClass.simpleName
@ -51,8 +48,6 @@ abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
compositeSubscription = CompositeDisposable()
val additionalData = HashMap<String, Any>()
additionalData["page"] = this.javaClass.simpleName
AmplitudeManager.sendEvent("navigate", AmplitudeManager.EVENT_CATEGORY_NAVIGATION, AmplitudeManager.EVENT_HITTYPE_PAGEVIEW, additionalData)
@ -89,10 +84,6 @@ abstract class BaseDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(
override fun onDestroyView() {
binding = null
if (!compositeSubscription.isDisposed) {
compositeSubscription.dispose()
}
super.onDestroyView()
}

View file

@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
@ -34,8 +33,6 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
protected var tutorialCanBeDeferred = true
var tutorialTexts: List<String> = ArrayList()
protected var compositeSubscription: CompositeDisposable = CompositeDisposable()
var shouldInitializeComponent = true
open val displayedClassName: String?
@ -60,8 +57,6 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
compositeSubscription = CompositeDisposable()
binding = createBinding(inflater, container)
return binding?.root
}
@ -92,10 +87,6 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
override fun onDestroyView() {
binding = null
if (!compositeSubscription.isDisposed) {
compositeSubscription.dispose()
}
super.onDestroyView()
}

View file

@ -30,7 +30,6 @@ import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.WorldStateEvent
@ -47,7 +46,6 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.extensions.getThemeColor
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -97,8 +95,6 @@ class NavigationDrawerFragment : DialogFragment() {
private lateinit var adapter: NavigationDrawerAdapter
private var subscriptions: CompositeDisposable? = null
val isDrawerOpen: Boolean
get() = drawerLayout?.isDrawerOpen(GravityCompat.START) ?: false
@ -114,7 +110,6 @@ class NavigationDrawerFragment : DialogFragment() {
} else {
NavigationDrawerAdapter(0, 0)
}
subscriptions = CompositeDisposable()
HabiticaBaseApplication.userComponent?.inject(this)
super.onCreate(savedInstanceState)
@ -145,25 +140,15 @@ class NavigationDrawerFragment : DialogFragment() {
false
initializeMenuItems()
subscriptions?.add(
adapter.getItemSelectionEvents().subscribe(
{
adapter.itemSelectedEvents = {
setSelection(it.transitionId, it.bundle, true)
},
ExceptionHandler.rx()
)
)
subscriptions?.add(
adapter.getPromoCloseEvents().subscribe(
{
}
adapter.promoClosedSubject = {
sharedPreferences.edit {
putBoolean("hide$it", true)
}
updatePromo()
},
ExceptionHandler.rx()
)
)
}
lifecycleScope.launchCatching {
contentRepository.getWorldState()
@ -354,7 +339,6 @@ class NavigationDrawerFragment : DialogFragment() {
}
override fun onDestroy() {
subscriptions?.dispose()
socialRepository.close()
inventoryRepository.close()
userRepository.close()

View file

@ -36,7 +36,6 @@ import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.OpenedMysteryitemDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.EmptyItem
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -45,8 +44,6 @@ import javax.inject.Inject
class ItemDialogFragment : BaseDialogFragment<FragmentItemsDialogBinding>() {
var parentSubscription: CompositeDisposable? = null
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject

View file

@ -190,7 +190,6 @@ class ItemRecyclerFragment : BaseFragment<FragmentItemsBinding>(), SwipeRefreshL
}
fragment.isHatching = true
fragment.isFeeding = false
fragment.parentSubscription = compositeSubscription
parentFragmentManager.let { fragment.show(it, "hatchingDialog") }
}

View file

@ -93,9 +93,9 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
adapter?.context = context
binding?.recyclerView?.adapter = adapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
adapter?.changeClassEvents?.subscribe {
adapter?.changeClassEvents = {
showClassChangeDialog(it)
}?.let { compositeSubscription.add(it) }
}
}
if (binding?.recyclerView?.layoutManager == null) {

View file

@ -11,7 +11,6 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.getTranslatedType
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
@ -243,7 +242,6 @@ class PetDetailRecyclerFragment :
fragment.isHatching = false
fragment.itemType = "food"
fragment.itemTypeText = getString(R.string.food)
fragment.parentSubscription = compositeSubscription
parentFragmentManager.let { fragment.show(it, "feedDialog") }
}

View file

@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
abstract class BasePreferencesFragment : PreferenceFragmentCompat() {
@ -21,8 +20,6 @@ abstract class BasePreferencesFragment : PreferenceFragmentCompat() {
internal open var user: User? = null
internal val compositeSubscription = CompositeDisposable()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -34,7 +31,6 @@ abstract class BasePreferencesFragment : PreferenceFragmentCompat() {
override fun onDestroy() {
userRepository.close()
compositeSubscription.dispose()
super.onDestroy()
}

View file

@ -18,7 +18,6 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
@ -29,7 +28,7 @@ import javax.inject.Inject
class WelcomeFragment : BaseFragment<FragmentWelcomeBinding>() {
val nameValidEvents = PublishSubject.create<Boolean>()
var onNameValid: ((Boolean?) -> Unit)? = null
@Inject
lateinit var userRepository: UserRepository
@ -132,7 +131,7 @@ class WelcomeFragment : BaseFragment<FragmentWelcomeBinding>() {
binding?.issuesTextView?.visibility = View.VISIBLE
binding?.issuesTextView?.text = it?.issues?.joinToString("\n")
}
nameValidEvents.onNext(it?.isUsable)
onNameValid?.invoke(it?.isUsable)
}
}

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentSkillsBinding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.Skill
@ -52,7 +51,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
savedInstanceState: Bundle?
): View? {
adapter = SkillsRecyclerViewAdapter()
adapter?.useSkillEvents?.subscribeWithErrorHandler { onSkillSelected(it) }?.let { compositeSubscription.add(it) }
adapter?.onUseSkill = { onSkillSelected(it) }
this.tutorialStepIdentifier = "skills"
this.tutorialTexts = listOf(getString(R.string.tutorial_skills))

View file

@ -7,13 +7,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.databinding.FragmentChatBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
@ -25,11 +25,10 @@ import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.delay
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class ChatFragment() : BaseFragment<FragmentChatBinding>() {
@ -52,7 +51,6 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
private var navigatedOnceToFragment = false
private var isScrolledToBottom = true
private var isFirstRefresh = true
private var refreshDisposable: Disposable? = null
var autocompleteContext: String = ""
override fun injectFragment(component: UserComponent) {
@ -69,16 +67,11 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
chatAdapter = ChatRecyclerViewAdapter(null, true)
chatAdapter?.let { adapter ->
compositeSubscription.add(
adapter.getUserLabelClickFlowable().subscribe(
{ userId -> FullProfileActivity.open(userId) },
ExceptionHandler.rx()
)
)
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ this.showDeleteConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ this.showFlagConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getReplyMessageEvents().subscribe({ setReplyTo(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ this.copyMessageToClipboard(it) }, ExceptionHandler.rx()))
adapter.onOpenProfile = { userId -> FullProfileActivity.open(userId) }
adapter.onDeleteMessage = { this.showDeleteConfirmationDialog(it) }
adapter.onFlagMessage = { this.showFlagConfirmationDialog(it) }
adapter.onReply = { setReplyTo(it) }
adapter.onCopyMessage = { this.copyMessageToClipboard(it) }
adapter.onMessageLike = { viewModel?.likeMessage(it) }
}
@ -108,48 +101,18 @@ class ChatFragment() : BaseFragment<FragmentChatBinding>() {
}
viewModel?.user?.observe(
viewLifecycleOwner,
{
chatAdapter?.user = it
binding?.chatBarView?.hasAcceptedGuidelines = it?.flags?.communityGuidelinesAccepted == true
}
)
}
override fun onDestroyView() {
super.onDestroyView()
stopAutoRefreshing()
}
override fun onResume() {
super.onResume()
startAutoRefreshing()
}
override fun onPause() {
super.onPause()
stopAutoRefreshing()
}
private fun startAutoRefreshing() {
if (refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
viewLifecycleOwner
) {
chatAdapter?.user = it
binding?.chatBarView?.hasAcceptedGuidelines =
it?.flags?.communityGuidelinesAccepted == true
}
refreshDisposable = Observable.interval(30, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
refresh()
},
ExceptionHandler.rx()
)
refresh()
}
private fun stopAutoRefreshing() {
if (refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
refreshDisposable = null
lifecycleScope.launchWhenResumed {
while (true) {
refresh()
delay(30.toDuration(DurationUnit.SECONDS))
}
}
}

View file

@ -35,12 +35,8 @@ import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModelFactory
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.Companion.showSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@ -65,7 +61,6 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
private val viewModel: InboxViewModel by viewModels(factoryProducer = {
InboxViewModelFactory(replyToUserUUID, chatRoomUser)
})
private var refreshDisposable: Disposable? = null
override fun onCreateView(
inflater: LayoutInflater,
@ -108,17 +103,12 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
binding?.recyclerView?.adapter = chatAdapter
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
chatAdapter?.let { adapter ->
compositeSubscription.add(
adapter.getUserLabelClickFlowable().subscribe(
{
adapter.onOpenProfile = {
FullProfileActivity.open(it)
},
ExceptionHandler.rx()
)
)
compositeSubscription.add(adapter.getDeleteMessageFlowable().subscribe({ showDeleteConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getFlagMessageClickFlowable().subscribe({ showFlagConfirmationDialog(it) }, ExceptionHandler.rx()))
compositeSubscription.add(adapter.getCopyMessageFlowable().subscribe({ copyMessageToClipboard(it) }, ExceptionHandler.rx()))
}
adapter.onDeleteMessage = { showDeleteConfirmationDialog(it) }
adapter.onFlagMessage = { showFlagConfirmationDialog(it) }
adapter.onCopyMessage = { copyMessageToClipboard(it) }
}
}
@ -132,13 +122,19 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
binding?.chatBarView?.maxChatLength = configManager.maxChatLength()
binding?.chatBarView?.hasAcceptedGuidelines = true
lifecycleScope.launchWhenResumed {
while (true) {
refreshConversation()
delay(30.toDuration(DurationUnit.SECONDS))
}
}
}
override fun onResume() {
if (replyToUserUUID?.isNotBlank() != true && chatRoomUser?.isNotBlank() != true) {
parentFragmentManager.popBackStack()
}
startAutoRefreshing()
super.onResume()
}
@ -149,16 +145,6 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
super.onAttach(context)
}
override fun onDestroyView() {
super.onDestroyView()
stopAutoRefreshing()
}
override fun onPause() {
super.onPause()
stopAutoRefreshing()
}
override fun onDestroy() {
socialRepository.close()
super.onDestroy()
@ -187,28 +173,6 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
socialRepository.markSomePrivateMessagesAsRead(viewModel.user.value, messages)
}
private fun startAutoRefreshing() {
if (refreshDisposable != null && refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
}
refreshDisposable = Observable.interval(30, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
refreshConversation()
},
ExceptionHandler.rx()
)
refreshConversation()
}
private fun stopAutoRefreshing() {
if (refreshDisposable?.isDisposed != true) {
refreshDisposable?.dispose()
refreshDisposable = null
}
}
private fun refreshConversation() {
if (viewModel.memberID?.isNotBlank() != true) { return }
lifecycleScope.launch(ExceptionHandler.coroutine()) {

View file

@ -75,10 +75,7 @@ class ChallengeListFragment : BaseFragment<FragmentRefreshRecyclerviewBinding>()
super.onViewCreated(view, savedInstanceState)
challengeAdapter = ChallengesListViewAdapter(viewUserChallengesOnly, userId)
challengeAdapter?.getOpenDetailFragmentFlowable()
?.subscribe({ openDetailFragment(it) }, ExceptionHandler.rx())
?.let { compositeSubscription.add(it) }
challengeAdapter?.onOpenChallengeFragment = { openDetailFragment(it) }
binding?.refreshLayout?.setOnRefreshListener(this)
if (viewUserChallengesOnly) {

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.shops.ShopItem
@ -68,15 +67,12 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
}
}
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents?.subscribe(
{
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.purchaseCardEvents = {
selectedCard = it
val intent = Intent(activity, SkillMemberActivity::class.java)
cardSelectedResult.launch(intent)
},
ExceptionHandler.rx()
)?.let { compositeSubscription.add(it) }
recyclerAdapter?.brokenTaskEvents?.subscribeWithErrorHandler { showBrokenChallengeDialog(it) }?.let { compositeSubscription.add(it) }
}
recyclerAdapter?.brokenTaskEvents = { showBrokenChallengeDialog(it) }
viewModel.user.observe(viewLifecycleOwner) {
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.user = it

View file

@ -22,7 +22,6 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
@ -51,7 +50,6 @@ import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
@ -79,7 +77,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false)
}
private var recyclerSubscription: CompositeDisposable = CompositeDisposable()
var recyclerAdapter: TaskRecyclerViewAdapter? = null
var itemAnimator = SafeDefaultItemAnimator()
@ -113,13 +110,9 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
get() = this.taskType
private fun setInnerAdapter() {
if (binding?.recyclerView?.adapter != null && binding?.recyclerView?.adapter == recyclerAdapter && !recyclerSubscription.isDisposed) {
if (binding?.recyclerView?.adapter != null && binding?.recyclerView?.adapter == recyclerAdapter) {
return
}
if (!recyclerSubscription.isDisposed) {
recyclerSubscription.dispose()
}
recyclerSubscription = CompositeDisposable()
viewModel.let { viewModel ->
val adapter: BaseRecyclerViewAdapter<*, *>? = when (this.taskType) {
TaskType.HABIT -> HabitsRecyclerViewAdapter(R.layout.habit_item_card, viewModel)
@ -142,33 +135,28 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
}
context?.let { recyclerAdapter?.taskDisplayMode = configManager.taskDisplayMode(it) }
recyclerAdapter?.errorButtonEvents?.subscribe(
{
recyclerAdapter?.errorButtonEvents = {
lifecycleScope.launchCatching {
taskRepository.syncErroredTasks()
}
},
ExceptionHandler.rx()
)?.let { recyclerSubscription.add(it) }
recyclerAdapter?.taskOpenEvents?.subscribeWithErrorHandler {
openTaskForm(it.first)
}?.let { recyclerSubscription.add(it) }
recyclerAdapter?.taskScoreEvents
?.doOnNext {
playSound(it.second)
context?.let { it1 -> notificationsManager.dismissTaskNotification(it1, it.first) }
}?.subscribeWithErrorHandler { scoreTask(it.first, it.second) }
?.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 {
}
recyclerAdapter?.taskOpenEvents = { task, view ->
openTaskForm(task)
}
recyclerAdapter?.taskScoreEvents = { task, direction ->
playSound(direction)
context?.let { it1 -> notificationsManager.dismissTaskNotification(it1, task) }
scoreTask(task, direction)
}
recyclerAdapter?.checklistItemScoreEvents = { task, item ->
scoreChecklistItem(task, item)
}
recyclerAdapter?.brokenTaskEvents = { showBrokenChallengeDialog(it) }
recyclerAdapter?.adventureGuideOpenEvents = {
MainNavigationController.navigate(
R.id.adventureGuideActivity
)
}?.let { recyclerSubscription.add(it) }
}
viewModel.ownerID.observe(viewLifecycleOwner) {
canEditTasks = viewModel.isPersonalBoard

View file

@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.databinding.ChatItemBinding
import com.habitrpg.android.habitica.databinding.TavernChatIntroItemBinding
import com.habitrpg.android.habitica.extensions.getAgoString
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.user.User
@ -24,9 +23,10 @@ import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.helpers.MarkdownParser
import com.habitrpg.common.habitica.helpers.setParsedMarkdown
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
open class ChatRecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
@ -185,17 +185,13 @@ class ChatRecyclerMessageViewHolder(
binding.messageText.setParsedMarkdown(chatMessage?.parsedText)
if (msg.parsedText == null) {
binding.messageText.text = chatMessage?.text
Maybe.just(chatMessage?.text ?: "")
.map { MarkdownParser.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ parsedText ->
chatMessage?.parsedText = parsedText
binding.messageText.setParsedMarkdown(parsedText)
},
ExceptionHandler.rx()
)
MainScope().launch(Dispatchers.IO) {
val parsedText = MarkdownParser.parseMarkdown(chatMessage?.text ?: "")
withContext(Dispatchers.Main) {
chatMessage?.parsedText = parsedText
binding.messageText.setParsedMarkdown(parsedText)
}
}
}
val username = user?.formattedUsername

View file

@ -14,7 +14,6 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.viewHolders.BindableViewHolder
import com.habitrpg.android.habitica.ui.views.EllipsisTextView
@ -23,13 +22,10 @@ import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.helpers.MarkdownParser
import com.habitrpg.common.habitica.helpers.setParsedMarkdown
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.functions.Action
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class BaseTaskViewHolder constructor(
itemView: View,
@ -42,7 +38,7 @@ abstract class BaseTaskViewHolder constructor(
var task: Task? = null
var movingFromPosition: Int? = null
var errorButtonClicked: Action? = null
var errorButtonClicked: (() -> Unit)? = null
var userID: String? = null
var isLocked = false
protected var context: Context
@ -101,7 +97,7 @@ abstract class BaseTaskViewHolder constructor(
titleTextView.setOnClickListener { onTouch(it, null) }
notesTextView?.setOnClickListener { onTouch(it, null) }
errorIconView?.setOnClickListener { errorButtonClicked?.run() }
errorIconView?.setOnClickListener { errorButtonClicked?.invoke() }
notesTextView?.movementMethod = LinkMovementMethod.getInstance()
titleTextView.movementMethod = LinkMovementMethod.getInstance()
@ -169,17 +165,13 @@ abstract class BaseTaskViewHolder constructor(
} else {
titleTextView.text = data.text
if (data.text.isNotEmpty()) {
Single.just(data.text)
.map { MarkdownParser.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ parsedText ->
data.parsedText = parsedText
titleTextView.setParsedMarkdown(parsedText)
},
ExceptionHandler.rx()
)
scope.launch(Dispatchers.IO) {
val parsedText = MarkdownParser.parseMarkdown(data.text)
withContext(Dispatchers.Main) {
data.parsedText = parsedText
titleTextView.setParsedMarkdown(parsedText)
}
}
}
}
if (displayMode != "minimal") {
@ -196,17 +188,13 @@ abstract class BaseTaskViewHolder constructor(
if (notes.isEmpty()) {
return@let
}
Single.just(notes)
.map { MarkdownParser.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ parsedNotes ->
data.parsedNotes = parsedNotes
notesTextView?.setParsedMarkdown(parsedNotes)
},
ExceptionHandler.rx()
)
scope.launch(Dispatchers.IO) {
val parsedNotes = MarkdownParser.parseMarkdown(notes)
withContext(Dispatchers.Main) {
data.parsedNotes = parsedNotes
notesTextView?.setParsedMarkdown(parsedNotes)
}
}
}
}
}

View file

@ -22,9 +22,10 @@ import com.habitrpg.common.habitica.helpers.MarkdownParser
import com.habitrpg.common.habitica.helpers.setParsedMarkdown
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class ChecklistedViewHolder(
itemView: View,
@ -131,16 +132,12 @@ abstract class ChecklistedViewHolder(
textView?.text = item.text
textView?.setTextColor(ContextCompat.getColor(context, if (item.completed) R.color.text_dimmed else R.color.text_secondary))
if (item.text != null) {
Observable.just(item.text ?: "")
.map { MarkdownParser.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
textView?.setParsedMarkdown(it)
},
ExceptionHandler.rx()
)
MainScope().launch(Dispatchers.IO) {
val parsedText = MarkdownParser.parseMarkdown(item.text ?: "")
withContext(Dispatchers.Main) {
textView?.setParsedMarkdown(parsedText)
}
}
}
val checkmark = itemView?.findViewById<ImageView>(R.id.checkmark)
checkmark?.drawable?.setTintMode(PorterDuff.Mode.SRC_ATOP)

View file

@ -33,7 +33,6 @@ import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.helpers.KeyHelper
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
import com.willowtreeapps.signinwithapplebutton.SignInWithAppleConfiguration
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.MainScope
import javax.inject.Inject
@ -52,8 +51,6 @@ class AuthenticationViewModel() {
@JvmField
var keyHelper: KeyHelper? = null
private var compositeSubscription = CompositeDisposable()
var googleEmail: String? = null
init {

View file

@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.invitations.PartyInvite
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.MutableStateFlow
@ -55,11 +54,8 @@ class MainUserViewModel(private val providedUserID: String, val userRepository:
fun onCleared() {
userRepository.close()
disposable.clear()
}
internal val disposable = CompositeDisposable()
fun updateUser(path: String, value: Any) {
MainScope().launch(ExceptionHandler.coroutine()) {
userRepository.updateUser(path, value)

View file

@ -20,7 +20,6 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.realm.Case
import io.realm.OrderedRealmCollection
import io.realm.RealmQuery
@ -30,8 +29,6 @@ import java.util.Date
import javax.inject.Inject
class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider {
private var compositeSubscription: CompositeDisposable = CompositeDisposable()
override fun inject(component: UserComponent) {
component.inject(this)
}

View file

@ -11,7 +11,6 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding
import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Animal
@ -22,8 +21,7 @@ import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.loadImage
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.MainScope
import java.util.Locale
import javax.inject.Inject
@ -162,14 +160,9 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
DataBindingUtils.loadImage(context, imageName) {
val resources = context.resources ?: return@loadImage
val drawable = if (hasMount) it else BitmapDrawable(resources, it.toBitmap().extractAlpha())
Observable.just(drawable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
binding.petView.bitmap = drawable.toBitmap()
},
ExceptionHandler.rx()
)
MainScope().launchCatching {
binding.petView.bitmap = drawable.toBitmap()
}
}
}

View file

@ -40,8 +40,6 @@ import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientG
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientSubscriberGemsDialog
import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
@ -221,7 +219,6 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
}
}
private val compositeSubscription: CompositeDisposable = CompositeDisposable()
var shopIdentifier: String? = null
private var user: User? = null
var isPinned: Boolean = false
@ -313,13 +310,9 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
userRepository.close()
inventoryRepository.close()
limitedTextViewJob?.cancel()
if (!compositeSubscription.isDisposed) {
compositeSubscription.dispose()
}
super.dismiss()
}
@OptIn(DelicateCoroutinesApi::class)
private fun onBuyButtonClicked() {
if (shopItem.isValid && !shopItem.locked) {
val gemsLeft = if (shopItem.limitedNumberLeft != null) shopItem.limitedNumberLeft else 0
@ -368,7 +361,7 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
)
HapticFeedbackManager.tap(contentView)
val snackbarText = arrayOf("")
val observable: (suspend () -> Unit)
val observable: (suspend () -> Any?)
if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType || shopItem.currency == "hourglasses") {
observable = if (shopItem.purchaseType == "gear") {
{ inventoryRepository.purchaseMysterySet(shopItem.key) }
@ -408,10 +401,8 @@ class PurchaseDialog(context: Context, component: UserComponent?, val item: Shop
observable = { inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key, quantity) }
}
lifecycleScope.launchCatching {
observable()
val text = if (snackbarText[0].isNotEmpty()) {
snackbarText[0]
} else {
val result = observable() ?: return@launchCatching
val text = snackbarText[0].ifEmpty {
context.getString(R.string.successful_purchase, shopItem.text)
}
val rightTextColor = when (item.currency) {

View file

@ -20,6 +20,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.QuestCollectBinding
import com.habitrpg.android.habitica.databinding.QuestProgressBinding
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.inventory.QuestProgressCollect
@ -32,8 +33,7 @@ import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.setMarkdown
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.MainScope
class QuestProgressView : LinearLayout {
private val binding = QuestProgressBinding.inflate(context.layoutInflater, this, true)
@ -181,22 +181,17 @@ class QuestProgressView : LinearLayout {
val iconView = ImageView(context)
if (strike.wasHit) {
DataBindingUtils.loadImage(context, "rage_strike_${strike.key}") {
Observable.just(it)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ drawable ->
val bitmap = drawable.toBitmap()
val displayDensity = resources.displayMetrics.density
val width = bitmap.width * displayDensity
val height = bitmap.height * displayDensity
val scaledImage = Bitmap.createScaledBitmap(bitmap, width.toInt(), height.toInt(), false)
iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeActive(context, scaledImage))
iconView.setOnClickListener {
showActiveStrikeAlert(strike.key)
}
},
ExceptionHandler.rx()
)
MainScope().launchCatching {
val bitmap = it.toBitmap()
val displayDensity = resources.displayMetrics.density
val width = bitmap.width * displayDensity
val height = bitmap.height * displayDensity
val scaledImage = Bitmap.createScaledBitmap(bitmap, width.toInt(), height.toInt(), false)
iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeActive(context, scaledImage))
iconView.setOnClickListener {
showActiveStrikeAlert(strike.key)
}
}
}
} else {
iconView.setImageBitmap(HabiticaIconsHelper.imageOfRageStrikeInactive())

View file

@ -14,7 +14,6 @@ import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.layoutInflater
import io.reactivex.rxjava3.disposables.Disposable
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@ -25,8 +24,6 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale
@Inject
lateinit var userRepository: UserRepository
var subscription: Disposable? = null
private val allocatedPoints: Int
get() {
var value = 0
@ -137,11 +134,6 @@ class BulkAllocateStatsDialog(context: Context, component: UserComponent?) : Ale
}
}
override fun dismiss() {
subscription?.dispose()
super.dismiss()
}
@SuppressLint("SetTextI18n")
private fun updateTitle() {
binding.allocatedTitle.text = "$allocatedPoints/$pointsToAllocate"

View file

@ -2,7 +2,6 @@ package com.habitrpg.android.habitica.proxy
import android.content.Context
import android.os.Bundle
import com.amplitude.api.Amplitude
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
@ -16,7 +15,7 @@ class AnalyticsManagerImpl(context: Context) : AnalyticsManager {
override fun setUserIdentifier(identifier: String) {
FirebaseCrashlytics.getInstance().setUserId(identifier)
Amplitude.getInstance().userId = identifier
AmplitudeManager.amplitude.userId = identifier
}
override fun setUserProperty(identifier: String, value: String) {

View file

@ -7,20 +7,20 @@ buildscript {
app_version_name = ''
app_version_code = 0
amplitude_version = '3.35.1'
amplitude_version = '1.5.1'
appcompat_version = '1.5.1'
coil_version = '2.1.0'
compose_version = '1.3.0'
coil_version = '2.2.2'
compose_version = '1.3.1'
core_ktx_version = '1.9.0'
coroutines_version = '1.6.4'
daggerhilt_version = '2.42'
daggerhilt_version = '2.44.2'
firebase_bom = '30.2.0'
kotlin_version = '1.7.20'
kotlin_version = '1.7.21'
lifecycle_version = '2.5.1'
markwon_version = '4.6.2'
moshi_version = '1.13.0'
moshi_version = '1.14.0'
navigation_version = '2.5.3'
okhttp_version = '4.9.3'
okhttp_version = '4.10.0'
play_wearables_version = '18.0.0'
play_auth_version = '20.3.0'
preferences_version = '1.2.0'