From 009fc24fbdf6d9b17ca2bdf7932bcb300b61f59b Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 28 Jan 2022 16:24:13 +0100 Subject: [PATCH] Begin moving logic out of main activity --- .../habitica/HabiticaBaseApplication.kt | 12 - .../habitica/components/UserComponent.java | 3 + .../habitica/data/ContentRepository.kt | 9 +- .../android/habitica/data/UserRepository.kt | 2 +- .../implementation/ContentRepositoryImpl.kt | 18 +- .../data/implementation/UserRepositoryImpl.kt | 7 +- .../habitica/interactors/FeedPetUseCase.kt | 83 +++++ .../habitica/interactors/HatchPetUseCase.kt | 54 +++ .../habitica/modules/RepositoryModule.java | 25 -- .../habitica/modules/RepositoryModule.kt | 37 ++ .../habitica/modules/UserRepositoryModule.kt | 14 +- .../habitica/proxy/AnalyticsManager.kt | 1 + .../implementation/EmptyAnalyticsManager.kt | 6 +- .../habitica/ui/activities/BaseActivity.kt | 5 +- .../habitica/ui/activities/IntroActivity.kt | 2 +- .../habitica/ui/activities/MainActivity.kt | 343 ++++-------------- .../ui/activities/NotificationsActivity.kt | 6 +- .../habitica/ui/fragments/BaseFragment.kt | 3 + .../habitica/ui/fragments/BaseMainFragment.kt | 4 +- .../inventory/items/ItemDialogFragment.kt | 25 +- .../inventory/items/ItemRecyclerFragment.kt | 14 +- .../fragments/inventory/shops/ShopFragment.kt | 2 +- .../stable/PetDetailRecyclerFragment.kt | 14 +- .../preferences/PreferencesFragment.kt | 4 +- .../purchases/GiftPurchaseGemsFragment.kt | 2 - .../purchases/SubscriptionFragment.kt | 2 - .../ui/fragments/social/ChatFragment.kt | 27 +- .../social/InboxMessageListFragment.kt | 23 +- .../ui/fragments/social/TavernFragment.kt | 12 +- .../social/guilds/GuildDetailFragment.kt | 20 +- .../fragments/social/guilds/GuildFragment.kt | 12 +- .../fragments/social/party/PartyFragment.kt | 6 +- .../tasks/TaskRecyclerViewFragment.kt | 2 +- .../habitica/ui/viewmodels/BaseViewModel.kt | 7 +- .../habitica/ui/viewmodels/GroupViewModel.kt | 4 + .../ui/viewmodels/MainActivityViewModel.kt | 162 +++++++++ .../ui/views/dialogs/PetSuggestHatchDialog.kt | 21 +- .../habitica/proxy/AnalyticsManagerImpl.kt | 4 + 38 files changed, 569 insertions(+), 428 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt index 8bc8103d1..456990987 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabiticaBaseApplication.kt @@ -31,7 +31,6 @@ import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.installations.FirebaseInstallations import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings -import com.habitrpg.android.habitica.api.HostConfig import com.habitrpg.android.habitica.components.AppComponent import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.ApiClient @@ -42,14 +41,12 @@ import com.habitrpg.android.habitica.modules.UserModule import com.habitrpg.android.habitica.modules.UserRepositoryModule import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.BaseActivity -import com.habitrpg.android.habitica.ui.activities.IntroActivity import com.habitrpg.android.habitica.ui.activities.LoginActivity import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import io.realm.Realm import io.realm.RealmConfiguration import java.lang.ref.WeakReference -import java.util.Locale import javax.inject.Inject // contains all HabiticaApplicationLogic except dagger componentInitialisation @@ -295,15 +292,6 @@ abstract class HabiticaBaseApplication : Application(), Application.ActivityLife userComponent = component?.plus(UserModule(), UserRepositoryModule()) } - fun checkUserAuthentication(context: Context, hostConfig: HostConfig?): Boolean { - if (hostConfig?.apiKey == null || hostConfig.apiKey == "" || hostConfig.userID == "") { - startActivity(IntroActivity::class.java, context) - return false - } - - return true - } - private fun startActivity(activityClass: Class<*>, context: Context) { val intent = Intent(context, activityClass) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java index 47622c126..041ce5192 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/UserComponent.java @@ -101,6 +101,7 @@ import com.habitrpg.android.habitica.ui.fragments.tasks.TeamBoardFragment; import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel; import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel; import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel; +import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel; import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel; import com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment.EquipmentOverviewViewModel; import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog; @@ -346,4 +347,6 @@ public interface UserComponent { void inject(@NotNull AccountPreferenceFragment accountPreferenceFragment); void inject(@NotNull AuthenticationViewModel authenticationViewModel); + + void inject(@NotNull MainActivityViewModel mainActivityViewModel); } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt index 1c4d3a7a5..173357d9c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ContentRepository.kt @@ -1,15 +1,12 @@ package com.habitrpg.android.habitica.data -import android.content.Context import com.habitrpg.android.habitica.models.ContentResult import com.habitrpg.android.habitica.models.WorldState import io.reactivex.rxjava3.core.Flowable -interface ContentRepository { +interface ContentRepository: BaseRepository { + fun retrieveContent(forced: Boolean = false): Flowable - fun retrieveContent(context: Context?): Flowable - fun retrieveContent(context: Context?, forced: Boolean): Flowable - - fun retrieveWorldState(context: Context?): Flowable + fun retrieveWorldState(): Flowable fun getWorldState(): Flowable } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt index b53bc6b43..298c576e2 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt @@ -28,7 +28,7 @@ interface UserRepository : BaseRepository { fun retrieveUser(withTasks: Boolean): Flowable fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): Flowable - fun revive(user: User): Flowable + fun revive(): Flowable fun resetTutorial() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt index 81440854f..fd4c8cb9e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ContentRepositoryImpl.kt @@ -12,24 +12,20 @@ import io.reactivex.rxjava3.core.Flowable import io.realm.RealmList import java.util.* -abstract class ContentRepositoryImpl(localRepository: T, apiClient: ApiClient) : BaseRepositoryImpl(localRepository, apiClient), ContentRepository { +class ContentRepositoryImpl(localRepository: T, apiClient: ApiClient, context: Context) : BaseRepositoryImpl(localRepository, apiClient), ContentRepository { + + private val mysteryItem = SpecialItem.makeMysteryItem(context) private var lastContentSync = 0L private var lastWorldStateSync = 0L - override fun retrieveContent(context: Context?): Flowable { - return retrieveContent(context, false) - } - - override fun retrieveContent(context: Context?, forced: Boolean): Flowable { + override fun retrieveContent(forced: Boolean): Flowable { val now = Date().time return if (forced || now - this.lastContentSync > 300000) { lastContentSync = now apiClient.content.doOnNext { - context?.let { context -> - it.special = RealmList() - it.special.add(SpecialItem.makeMysteryItem(context)) - } + it.special = RealmList() + it.special.add(mysteryItem) localRepository.saveContent(it) } } else { @@ -37,7 +33,7 @@ abstract class ContentRepositoryImpl(localRepository } } - override fun retrieveWorldState(context: Context?): Flowable { + override fun retrieveWorldState(): Flowable { val now = Date().time return if (now - this.lastWorldStateSync > 3600000) { lastWorldStateSync = now diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt index 696116cb2..19156f165 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt @@ -94,9 +94,10 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli } } - override fun revive(user: User): Flowable = - apiClient.revive().map { newUser -> mergeUser(user, newUser) } - .flatMap { retrieveUser(false, true) } + override fun revive(): Flowable = zipWithLiveUser(apiClient.revive()) { newUser, user -> + mergeUser(user, newUser) + } + .flatMap { retrieveUser(false, true) } override fun resetTutorial() { localRepository.getTutorialSteps() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt new file mode 100644 index 000000000..c51167637 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/FeedPetUseCase.kt @@ -0,0 +1,83 @@ +package com.habitrpg.android.habitica.interactors + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.executors.PostExecutionThread +import com.habitrpg.android.habitica.models.inventory.Food +import com.habitrpg.android.habitica.models.inventory.Pet +import com.habitrpg.android.habitica.models.responses.FeedResponse +import com.habitrpg.android.habitica.ui.activities.BaseActivity +import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils +import com.habitrpg.android.habitica.ui.views.SnackbarActivity +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import io.reactivex.rxjava3.core.Flowable +import javax.inject.Inject + +class FeedPetUseCase @Inject +constructor( + private val inventoryRepository: InventoryRepository, + postExecutionThread: PostExecutionThread +) : UseCase(postExecutionThread) { + override fun buildUseCaseObservable(requestValues: FeedPetUseCase.RequestValues): Flowable { + return inventoryRepository.feedPet(requestValues.pet, requestValues.food) + .doOnNext { feedResponse -> + (requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse.message) + if (feedResponse.value == -1) { + val mountWrapper = + View.inflate( + requestValues.context, + R.layout.pet_imageview, + null + ) as? FrameLayout + val mountImageView = + mountWrapper?.findViewById(R.id.pet_imageview) as? ImageView + + DataBindingUtils.loadImage( + mountImageView, + "Mount_Icon_" + requestValues.pet.key + ) + val dialog = HabiticaAlertDialog(requestValues.context) + dialog.setTitle( + requestValues.context.getString( + R.string.evolved_pet_title, + requestValues.pet.text + ) + ) + dialog.setAdditionalContentView(mountWrapper) + dialog.addButton(R.string.onwards, true) + dialog.addButton(R.string.share, false) { hatchingDialog, _ -> + val message = + requestValues.context.getString( + R.string.share_raised, + requestValues.pet.text + ) + val mountImageSideLength = 99 + val sharedImage = Bitmap.createBitmap( + mountImageSideLength, + mountImageSideLength, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(sharedImage) + mountImageView?.drawable?.setBounds( + 0, + 0, + mountImageSideLength, + mountImageSideLength + ) + mountImageView?.drawable?.draw(canvas) + requestValues.context.shareContent("raisedPet", message, sharedImage) + hatchingDialog.dismiss() + } + dialog.enqueue() + } + } + } + + class RequestValues(val pet: Pet, val food: Food, val context: BaseActivity) : + UseCase.RequestValues +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt new file mode 100644 index 000000000..6d63e4b40 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt @@ -0,0 +1,54 @@ +package com.habitrpg.android.habitica.interactors + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.executors.PostExecutionThread +import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.models.inventory.Egg +import com.habitrpg.android.habitica.models.inventory.HatchingPotion +import com.habitrpg.android.habitica.models.user.Items +import com.habitrpg.android.habitica.ui.activities.BaseActivity +import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils +import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import io.reactivex.rxjava3.core.Flowable +import javax.inject.Inject + +class HatchPetUseCase @Inject +constructor(private val inventoryRepository: InventoryRepository, postExecutionThread: PostExecutionThread) : UseCase(postExecutionThread) { + override fun buildUseCaseObservable(requestValues: RequestValues): Flowable { + return inventoryRepository.hatchPet(requestValues.egg, requestValues.potion) { + val petWrapper = View.inflate(requestValues.context, R.layout.pet_imageview, null) as? FrameLayout + val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? ImageView + + DataBindingUtils.loadImage(petImageView, "stable_Pet-" + requestValues.egg.key + "-" + requestValues.potion.key) + val potionName = requestValues.potion.text + val eggName = requestValues.egg.text + val dialog = HabiticaAlertDialog(requestValues.context) + dialog.setTitle(requestValues.context.getString(R.string.hatched_pet_title, potionName, eggName)) + dialog.setAdditionalContentView(petWrapper) + dialog.addButton(R.string.equip, true) { _, _ -> + inventoryRepository.equip(null, "pet", requestValues.egg.key + "-" + requestValues.potion.key) + .subscribe({}, RxErrorHandler.handleEmptyError()) + } + dialog.addButton(R.string.share, false) { hatchingDialog, _ -> + val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName) + val petImageSideLength = 140 + val sharedImage = Bitmap.createBitmap(petImageSideLength, petImageSideLength, Bitmap.Config.ARGB_8888) + val canvas = Canvas(sharedImage) + petImageView?.drawable?.setBounds(0, 0, petImageSideLength, petImageSideLength) + petImageView?.drawable?.draw(canvas) + requestValues.context.shareContent("hatchedPet", message, sharedImage) + hatchingDialog.dismiss() + } + dialog.setExtraCloseButtonVisibility(View.VISIBLE) + dialog.enqueue() + } + } + + class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: BaseActivity) : UseCase.RequestValues +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java deleted file mode 100644 index 8db9c8a58..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.habitrpg.android.habitica.modules; - -import com.habitrpg.android.habitica.data.ApiClient; -import com.habitrpg.android.habitica.data.ContentRepository; -import com.habitrpg.android.habitica.data.implementation.ContentRepositoryImpl; -import com.habitrpg.android.habitica.data.local.ContentLocalRepository; -import com.habitrpg.android.habitica.data.local.implementation.RealmContentLocalRepository; - -import dagger.Module; -import dagger.Provides; -import io.realm.Realm; - -@Module -public class RepositoryModule { - @Provides - public Realm providesRealm() { - return Realm.getDefaultInstance(); - } - - @Provides - public ContentLocalRepository providesContentLocalRepository(Realm realm) { return new RealmContentLocalRepository(realm); } - - @Provides - public ContentRepository providesContentRepository(ContentLocalRepository contentLocalRepository, ApiClient apiClient) { return new ContentRepositoryImpl(contentLocalRepository, apiClient) {}; } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.kt new file mode 100644 index 000000000..f7943388d --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.kt @@ -0,0 +1,37 @@ +package com.habitrpg.android.habitica.modules + +import android.content.Context +import com.habitrpg.android.habitica.data.local.ContentLocalRepository +import com.habitrpg.android.habitica.data.local.implementation.RealmContentLocalRepository +import com.habitrpg.android.habitica.data.ApiClient +import com.habitrpg.android.habitica.data.ContentRepository +import com.habitrpg.android.habitica.data.implementation.ContentRepositoryImpl +import dagger.Module +import dagger.Provides +import io.realm.Realm + +@Module +open class RepositoryModule { + @Provides + open fun providesRealm(): Realm? { + return Realm.getDefaultInstance() + } + + @Provides + fun providesContentLocalRepository(realm: Realm?): ContentLocalRepository { + return RealmContentLocalRepository(realm!!) + } + + @Provides + fun providesContentRepository( + contentLocalRepository: ContentLocalRepository, + apiClient: ApiClient, + context: Context + ): ContentRepository { + return ContentRepositoryImpl( + contentLocalRepository, + apiClient, + context + ) + } +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt index e32c8ae34..93e4e5510 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt @@ -65,15 +65,15 @@ class UserRepositoryModule { @Provides fun providesTaskRepository( - localRepository: TaskLocalRepository?, - apiClient: ApiClient?, - @Named(AppModule.NAMED_USER_ID) userId: String?, - appConfigManager: AppConfigManager?, - analyticsManager: AnalyticsManager? + localRepository: TaskLocalRepository, + apiClient: ApiClient, + @Named(AppModule.NAMED_USER_ID) userId: String, + appConfigManager: AppConfigManager, + analyticsManager: AnalyticsManager ): TaskRepository { return TaskRepositoryImpl( - localRepository!!, - apiClient!!, + localRepository, + apiClient, userId!!, appConfigManager!!, analyticsManager!! diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/AnalyticsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/AnalyticsManager.kt index f4a134001..8bd54c8f9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/AnalyticsManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/AnalyticsManager.kt @@ -5,6 +5,7 @@ import android.os.Bundle interface AnalyticsManager { fun logException(t: Throwable) fun setUserIdentifier(identifier: String) + fun setUserProperty(identifier: String, value: String) fun logError(msg: String) fun logEvent(eventName: String, data: Bundle) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt index 459e63550..3fb0f220f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt @@ -5,7 +5,7 @@ import com.habitrpg.android.habitica.proxy.AnalyticsManager class EmptyAnalyticsManager : AnalyticsManager { - override fun logException(e: Throwable) { + override fun logException(t: Throwable) { // pass } @@ -13,6 +13,10 @@ class EmptyAnalyticsManager : AnalyticsManager { // pass } + override fun setUserProperty(identifier: String, value: String) { + // pass + } + override fun logError(msg: String) { // pass } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt index 2e26d20b0..4a3a68a03 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt @@ -128,7 +128,6 @@ abstract class BaseActivity : AppCompatActivity() { internal open fun loadTheme(sharedPreferences: SharedPreferences, forced: Boolean = false) { val theme = forcedTheme ?: sharedPreferences.getString("theme_name", "purple") - val modernHeaderStyle = overrideModernHeader ?: sharedPreferences.getBoolean("modern_header_style", true) if (theme != currentTheme || forced) { if (forcedIsNight ?: isNightMode) { setTheme( @@ -165,10 +164,8 @@ abstract class BaseActivity : AppCompatActivity() { } else { getThemeColor(R.attr.colorPrimaryDark) } - if (!(forcedIsNight ?: isNightMode) && modernHeaderStyle) { + if (!(forcedIsNight ?: isNightMode)) { window.updateStatusBarColor(getThemeColor(R.attr.headerBackgroundColor), true) - } else { - window.updateStatusBarColor(getThemeColor(R.attr.statusBarBackground), false) } if (currentTheme != null && theme != currentTheme) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt index c5086066e..8f242f2ed 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/IntroActivity.kt @@ -44,7 +44,7 @@ class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChan binding.skipButton.setOnClickListener(this) binding.finishButton.setOnClickListener(this) - compositeSubscription.add(contentRepository.retrieveContent(this).subscribe({ }, RxErrorHandler.handleEmptyError())) + compositeSubscription.add(contentRepository.retrieveContent().subscribe({ }, RxErrorHandler.handleEmptyError())) window.statusBarColor = ContextCompat.getColor(this, R.color.black_20_alpha) window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 67cf0842f..8ab31d03c 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -5,37 +5,30 @@ import android.app.NotificationManager import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Intent -import android.content.SharedPreferences import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.os.Build import android.os.Bundle -import android.util.Log import android.view.* import android.widget.FrameLayout import android.widget.ImageView +import androidx.activity.viewModels import androidx.appcompat.app.ActionBarDrawerToggle -import androidx.core.content.edit import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavDestination import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment -import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.perf.FirebasePerformance -import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R -import com.habitrpg.android.habitica.api.HostConfig -import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.* import com.habitrpg.android.habitica.data.local.UserQuestStatus import com.habitrpg.android.habitica.databinding.ActivityMainBinding +import com.habitrpg.android.habitica.databinding.DialogFaintBinding import com.habitrpg.android.habitica.extensions.* import com.habitrpg.android.habitica.helpers.* -import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase import com.habitrpg.android.habitica.interactors.NotifyUserUseCase @@ -52,6 +45,7 @@ import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel import com.habitrpg.android.habitica.ui.TutorialView import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils +import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.android.habitica.ui.views.* import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType @@ -63,9 +57,6 @@ import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider import com.habitrpg.android.habitica.widget.TodoListWidgetProvider import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.functions.Consumer -import io.reactivex.rxjava3.schedulers.Schedulers -import io.realm.kotlin.isValid import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -78,14 +69,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack @Inject internal lateinit var soundManager: SoundManager @Inject - internal lateinit var maintenanceService: MaintenanceApiService - @Inject - internal lateinit var hostConfig: HostConfig - @Inject - internal lateinit var sharedPreferences: SharedPreferences - @Inject - internal lateinit var pushNotificationManager: PushNotificationManager - @Inject internal lateinit var checkClassSelectionUseCase: CheckClassSelectionUseCase @Inject internal lateinit var displayItemDropUseCase: DisplayItemDropUseCase @@ -98,20 +81,16 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack @Inject internal lateinit var inventoryRepository: InventoryRepository @Inject - internal lateinit var contentRepository: ContentRepository - @Inject - internal lateinit var taskAlarmManager: TaskAlarmManager - @Inject internal lateinit var appConfigManager: AppConfigManager lateinit var binding: ActivityMainBinding val snackbarContainer: ViewGroup get() = binding.snackbarContainer - var user: User? = null private var avatarInHeader: AvatarWithBarsViewModel? = null - var notificationsViewModel: NotificationsViewModel? = null + val notificationsViewModel: NotificationsViewModel by viewModels() + val viewModel: MainActivityViewModel by viewModels() private var faintDialog: HabiticaAlertDialog? = null private var sideAvatarView: AvatarView? = null private var activeTutorialView: TutorialView? = null @@ -121,9 +100,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack private var userQuestStatus = UserQuestStatus.NO_QUEST private var lastNotificationOpen: Long? = null - val userID: String - get() = user?.id ?: "" - val isAppBarExpanded: Boolean get() = binding.appbar.height - binding.appbar.bottom == 0 @@ -147,7 +123,10 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack launchTrace?.start() super.onCreate(savedInstanceState) - if (!HabiticaBaseApplication.checkUserAuthentication(this, hostConfig)) { + if (!viewModel.isAuthenticated) { + val intent = Intent(this, IntroActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) return } @@ -156,36 +135,24 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack avatarInHeader = AvatarWithBarsViewModel(this, binding.avatarWithBars, userRepository) sideAvatarView = AvatarView(this, showBackground = true, showMount = false, showPet = false) - compositeSubscription.add( - userRepository.getUser() - .subscribe( - { newUser -> - this@MainActivity.user = newUser - this@MainActivity.setUserData() - }, - RxErrorHandler.handleEmptyError() - ) - ) + viewModel.user.observe(this) { + setUserData(it) + } compositeSubscription.add( userRepository.getUserQuestStatus().subscribeWithErrorHandler { userQuestStatus = it } ) - val viewModel = ViewModelProvider(this).get(NotificationsViewModel::class.java) - notificationsViewModel = viewModel - val drawerLayout = findViewById(R.id.drawer_layout) - drawerFragment = supportFragmentManager.findFragmentById(R.id.navigation_drawer) as? NavigationDrawerFragment - - drawerFragment?.setUp(R.id.navigation_drawer, drawerLayout, viewModel) + drawerFragment?.setUp(R.id.navigation_drawer, drawerLayout, notificationsViewModel) drawerToggle = object : ActionBarDrawerToggle( - this, /* host Activity */ - drawerLayout, /* DrawerLayout object */ - R.string.navigation_drawer_open, /* "open drawer" description */ - R.string.navigation_drawer_close /* "close drawer" description */ + this, + drawerLayout, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close ) {} // Set the drawer toggle as the DrawerListener drawerToggle?.let { drawerLayout.addDrawerListener(it) } @@ -193,8 +160,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack private var isOpeningDrawer: Boolean? = null override fun onDrawerSlide(drawerView: View, slideOffset: Float) { - val modernHeaderStyle = sharedPreferences.getBoolean("modern_header_style", true) - if (!isUsingNightModeResources() && modernHeaderStyle) { + if (!isUsingNightModeResources()) { if (slideOffset < 0.5f && isOpeningDrawer == null) { window.updateStatusBarColor(getThemeColor(R.attr.colorPrimaryDark), false) isOpeningDrawer = true @@ -207,18 +173,15 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack override fun onDrawerOpened(drawerView: View) { hideKeyboard() - val modernHeaderStyle = sharedPreferences.getBoolean("modern_header_style", true) - if (!isUsingNightModeResources() && modernHeaderStyle) { + if (!isUsingNightModeResources()) { window.updateStatusBarColor(getThemeColor(R.attr.colorPrimaryDark), false) } isOpeningDrawer = null - drawerFragment?.updatePromo() } override fun onDrawerClosed(drawerView: View) { - val modernHeaderStyle = sharedPreferences.getBoolean("modern_header_style", true) - if (!isUsingNightModeResources() && modernHeaderStyle) { + if (!isUsingNightModeResources()) { window.updateStatusBarColor(getThemeColor(R.attr.headerBackgroundColor), true) } isOpeningDrawer = null @@ -233,11 +196,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack setupNotifications() setupBottomnavigationLayoutListener() - try { - taskAlarmManager.scheduleAllSavedAlarms(sharedPreferences.getBoolean("preventDailyReminder", false)) - } catch (e: Exception) { - analyticsManager.logException(e) - } + viewModel.onCreate() } override fun setTitle(title: CharSequence?) { @@ -249,32 +208,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack } private fun updateToolbarTitle(destination: NavDestination, arguments: Bundle?) { - title = if (destination.id == R.id.promoInfoFragment) { - "" - } else if (destination.id == R.id.petDetailRecyclerFragment || destination.id == R.id.mountDetailRecyclerFragment) { - arguments?.getString("type") - } else if (destination.label.isNullOrEmpty() && user?.isValid == true) { - user?.profile?.name - } else if (destination.label != null) { - destination.label - } else { - "" - } - if (destination.id == R.id.petDetailRecyclerFragment || destination.id == R.id.mountDetailRecyclerFragment) { - compositeSubscription.add( - inventoryRepository.getItem("egg", arguments?.getString("type") ?: "").firstElement().subscribe( - { - if (!it.isValid()) return@subscribe - binding.toolbarTitle.text = if (destination.id == R.id.petDetailRecyclerFragment) { - (it as? Egg)?.text - } else { - (it as? Egg)?.mountText - } - }, - RxErrorHandler.handleEmptyError() - ) - ) - } + viewModel.getToolbarTitle(destination.id, destination.label, arguments?.getString("type")) { title = it } drawerFragment?.setSelection(destination.id, null, false) } @@ -312,12 +246,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle?.syncState() - launchScreen = sharedPreferences.getString("launch_screen", "") - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - Log.e("RESTORED:", savedInstanceState.toString()) + launchScreen = viewModel.launchScreen } override fun onConfigurationChanged(newConfig: Configuration) { @@ -338,13 +267,15 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack override fun onResume() { super.onResume() + viewModel.onResume() + val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navigationController = navHostFragment.navController MainNavigationController.setup(navigationController) navigationController.addOnDestinationChangedListener { _, destination, arguments -> updateToolbarTitle(destination, arguments) } if (launchScreen == "/party") { - if (user == null || user?.party?.id != null) { + if (viewModel.isUserInParty) { MainNavigationController.navigate(R.id.partyFragment) } } @@ -356,12 +287,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack } resumeFromActivity = false - // Track when the app was last opened, so that we can use this to send out special reminders after a week of inactivity - sharedPreferences.edit { - putLong("lastAppLaunch", Date().time) - putBoolean("preventDailyReminder", false) - } - if ((intent.hasExtra("notificationIdentifier") || intent.hasExtra("openURL")) && lastNotificationOpen != intent.getLongExtra("notificationTimeStamp", 0)) { lastNotificationOpen = intent.getLongExtra("notificationTimeStamp", 0) val identifier = intent.getStringExtra("notificationIdentifier") ?: "" @@ -421,51 +346,40 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack findNavController(R.id.nav_host_fragment).navigate(transitionId) } - private fun setUserData() { + private fun setUserData(user: User?) { if (user != null) { - val preferences = user?.preferences + val preferences = user.preferences preferences?.language?.let { apiClient.setLanguageCode(it) } - if (preferences?.language != sharedPreferences.getString("language", "en")) { - sharedPreferences.edit { - putString("language", preferences?.language) - } + if (preferences?.language != viewModel.preferenceLanguage) { + viewModel.preferenceLanguage = preferences?.language } preferences?.sound?.let { soundManager.soundTheme = it } displayDeathDialogIfNeeded() - YesterdailyDialog.showDialogIfNeeded(this, user?.id, userRepository, taskRepository) + YesterdailyDialog.showDialogIfNeeded(this, user.id, userRepository, taskRepository) - if (user?.flags?.verifiedUsername == false && isActivityVisible) { + if (user.flags?.verifiedUsername == false && isActivityVisible) { val intent = Intent(this, VerifyUsernameActivity::class.java) startActivity(intent) } - val quest = user?.party?.quest + val quest = user.party?.quest if (quest?.completed?.isNotBlank() == true) { compositeSubscription.add( - inventoryRepository.getQuestContent(user?.party?.quest?.completed ?: "").firstElement().subscribe( + inventoryRepository.getQuestContent(user.party?.quest?.completed ?: "").firstElement().subscribe( { QuestCompletedDialog.showWithQuest(this, it) - userRepository.updateUser("party.quest.completed", "").subscribe({}, RxErrorHandler.handleEmptyError()) + viewModel.updateUser("party.quest.completed", "") }, RxErrorHandler.handleEmptyError() ) ) } - if (user?.flags?.welcomed == false) { - compositeSubscription.add(userRepository.updateUser("flags.welcomed", true).subscribe({}, RxErrorHandler.handleEmptyError())) - } - - try { - val navigationController = findNavController(R.id.nav_host_fragment) - if (binding.toolbarTitle.text?.isNotBlank() != true) { - navigationController.currentDestination?.let { updateToolbarTitle(it, null) } - } - } catch (e: java.lang.IllegalStateException) { - // Has no Navcontroller right now. + if (user.flags?.welcomed == false) { + viewModel.updateUser("flags.welcomed", true) } } } @@ -491,44 +405,10 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack inventoryRepository.close() super.onDestroy() } - - fun feedPet(pet: Pet, food: Food) { - compositeSubscription.add( - this.inventoryRepository.feedPet(pet, food) - .subscribe( - { feedResponse -> - HabiticaSnackbar.showSnackbar(snackbarContainer, feedResponse.message, SnackbarDisplayType.NORMAL) - if (feedResponse.value == -1) { - val mountWrapper = View.inflate(this, R.layout.pet_imageview, null) as? FrameLayout - val mountImageView = mountWrapper?.findViewById(R.id.pet_imageview) as? ImageView - - DataBindingUtils.loadImage(mountImageView, "Mount_Icon_" + pet.key) - val dialog = HabiticaAlertDialog(this@MainActivity) - dialog.setTitle(getString(R.string.evolved_pet_title, pet.text)) - dialog.setAdditionalContentView(mountWrapper) - dialog.addButton(R.string.onwards, true) - dialog.addButton(R.string.share, false) { hatchingDialog, _ -> - val message = getString(R.string.share_raised, pet.text) - val mountImageSideLength = 99 - val sharedImage = Bitmap.createBitmap(mountImageSideLength, mountImageSideLength, Bitmap.Config.ARGB_8888) - val canvas = Canvas(sharedImage) - mountImageView?.drawable?.setBounds(0, 0, mountImageSideLength, mountImageSideLength) - mountImageView?.drawable?.draw(canvas) - shareContent("raisedPet", message, sharedImage) - hatchingDialog.dismiss() - } - dialog.enqueue() - } - }, - RxErrorHandler.handleEmptyError() - ) - ) - } - // endregion internal fun displayTaskScoringResponse(data: TaskScoringResult?) { - if (user != null && data != null) { + if (viewModel.user.value != null && data != null) { val damageValue = when (userQuestStatus) { UserQuestStatus.QUEST_BOSS -> data.questDamage else -> 0.0 @@ -537,7 +417,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack notifyUserUseCase.observable( NotifyUserUseCase.RequestValues( this, snackbarContainer, - user, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level + viewModel.user.value, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, damageValue, data.hasLeveledUp, data.level ) ) .subscribe({ }, RxErrorHandler.handleEmptyError()) @@ -552,31 +432,22 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack } private fun displayDeathDialogIfNeeded() { - if (user?.stats?.hp ?: 1.0 > 0) { + if (!viewModel.isUserFainted) { return } if (this.faintDialog == null && !this.isFinishing) { - - val customView = View.inflate(this, R.layout.dialog_faint, null) - if (customView != null) { - val hpBarView = customView.findViewById(R.id.hpBar) as? ValueBar - - hpBarView?.setLightBackground(true) - hpBarView?.setIcon(HabiticaIconsHelper.imageOfHeartLightBg()) - - val dialogAvatarView = customView.findViewById(R.id.avatarView) as? AvatarView - user?.let { dialogAvatarView?.setAvatar(it) } - } + val binding = DialogFaintBinding.inflate(this.layoutInflater) + binding.hpBar.setLightBackground(true) + binding.hpBar.setIcon(HabiticaIconsHelper.imageOfHeartLightBg()) + viewModel.user.value?.let { binding.avatarView.setAvatar(it) } this.faintDialog = HabiticaAlertDialog(this) faintDialog?.setTitle(R.string.faint_header) - faintDialog?.setAdditionalContentView(customView) + faintDialog?.setAdditionalContentView(binding.root) faintDialog?.addButton(R.string.faint_button, true) { _, _ -> faintDialog = null - user?.let { - userRepository.revive(it).subscribe({ }, RxErrorHandler.handleEmptyError()) - } + userRepository.revive().subscribe({ }, RxErrorHandler.handleEmptyError()) } soundManager.loadAndPlayAudio(SoundManager.SoundDeath) this.faintDialog?.enqueue() @@ -593,23 +464,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack } protected fun retrieveUser(forced: Boolean = false) { - if (hostConfig.hasAuthentication()) { - compositeSubscription.add( - contentRepository.retrieveWorldState(this) - .flatMap { userRepository.retrieveUser(true, forced) } - .doOnNext { user1 -> - FirebaseAnalytics.getInstance(this).setUserProperty("has_party", if (user1.party?.id?.isNotEmpty() == true) "true" else "false") - FirebaseAnalytics.getInstance(this).setUserProperty("is_subscribed", if (user1.isSubscribed) "true" else "false") - FirebaseAnalytics.getInstance(this).setUserProperty("checkin_count", user1.loginIncentives.toString()) - FirebaseAnalytics.getInstance(this).setUserProperty("level", user1.stats?.lvl?.toString() ?: "") - pushNotificationManager.setUser(user1) - pushNotificationManager.addPushDeviceUsingStoredToken() - } - .flatMap { userRepository.retrieveTeamPlans() } - .flatMap { contentRepository.retrieveContent(this) } - .subscribe({ }, RxErrorHandler.handleEmptyError()) - ) - } + viewModel.retrieveUser(forced) } fun displayTutorialStep(step: TutorialStep, text: String, canBeDeferred: Boolean) { @@ -620,12 +475,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack view.onReaction = this view.setCanBeDeferred(canBeDeferred) binding.overlayFrameLayout.addView(view) - - val additionalData = HashMap() - additionalData["eventLabel"] = step.identifier + "-android" - additionalData["eventValue"] = step.identifier ?: "" - additionalData["complete"] = false - AmplitudeManager.sendEvent("tutorial", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) + viewModel.logTutorialStatus(step, false) } fun displayTutorialStep(step: TutorialStep, texts: List, canBeDeferred: Boolean) { @@ -636,27 +486,15 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack view.onReaction = this view.setCanBeDeferred(canBeDeferred) binding.overlayFrameLayout.addView(view) - - val additionalData = HashMap() - additionalData["eventLabel"] = step.identifier + "-android" - additionalData["eventValue"] = step.identifier ?: "" - additionalData["complete"] = false - AmplitudeManager.sendEvent("tutorial", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) + viewModel.logTutorialStatus(step, false) } + override fun onTutorialCompleted(step: TutorialStep) { - compositeSubscription.add( - userRepository.updateUser("flags.tutorial." + step.tutorialGroup + "." + step.identifier, true) - .subscribe({ }, RxErrorHandler.handleEmptyError()) - ) + viewModel.updateUser("flags.tutorial." + step.tutorialGroup + "." + step.identifier, true) binding.overlayFrameLayout.removeView(this.activeTutorialView) this.removeActiveTutorialView() - - val additionalData = HashMap() - additionalData["eventLabel"] = step.identifier + "-android" - additionalData["eventValue"] = step.identifier ?: "" - additionalData["complete"] = true - AmplitudeManager.sendEvent("tutorial", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) + viewModel.logTutorialStatus(step, true) } override fun onTutorialDeferred(step: TutorialStep) { @@ -672,36 +510,25 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack } private fun checkMaintenance() { - compositeSubscription.add( - this.maintenanceService.maintenanceStatus - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - Consumer { maintenanceResponse -> - if (maintenanceResponse == null) { - return@Consumer - } - if (maintenanceResponse.activeMaintenance) { - val intent = createMaintenanceIntent(maintenanceResponse, false) + viewModel.ifNeedsMaintenance { maintenanceResponse -> + if (maintenanceResponse.activeMaintenance) { + val intent = createMaintenanceIntent(maintenanceResponse, false) + startActivity(intent) + } else { + if (maintenanceResponse.minBuild != null) { + try { + val packageInfo = packageManager.getPackageInfo(packageName, 0) + @Suppress("DEPRECATION") + if (packageInfo.versionCode < maintenanceResponse.minBuild) { + val intent = createMaintenanceIntent(maintenanceResponse, true) startActivity(intent) - } else { - if (maintenanceResponse.minBuild != null) { - try { - val packageInfo = packageManager.getPackageInfo(packageName, 0) - @Suppress("DEPRECATION") - if (packageInfo.versionCode < maintenanceResponse.minBuild) { - val intent = createMaintenanceIntent(maintenanceResponse, true) - startActivity(intent) - } - } catch (e: PackageManager.NameNotFoundException) { - RxErrorHandler.reportError(e) - } - } } - }, - RxErrorHandler.handleEmptyError() - ) - ) + } catch (e: PackageManager.NameNotFoundException) { + RxErrorHandler.reportError(e) + } + } + } + } } private fun createMaintenanceIntent(maintenanceResponse: MaintenanceResponse, isDeprecationNotice: Boolean): Intent { @@ -737,36 +564,4 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack ) } } - - fun hatchPet(potion: HatchingPotion, egg: Egg) { - compositeSubscription.add( - this.inventoryRepository.hatchPet(egg, potion) { - val petWrapper = View.inflate(this, R.layout.pet_imageview, null) as? FrameLayout - val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? ImageView - - DataBindingUtils.loadImage(petImageView, "stable_Pet-" + egg.key + "-" + potion.key) - val potionName = potion.text - val eggName = egg.text - val dialog = HabiticaAlertDialog(this) - dialog.setTitle(getString(R.string.hatched_pet_title, potionName, eggName)) - dialog.setAdditionalContentView(petWrapper) - dialog.addButton(R.string.equip, true) { _, _ -> - inventoryRepository.equip(user, "pet", egg.key + "-" + potion.key) - .subscribe({}, RxErrorHandler.handleEmptyError()) - } - dialog.addButton(R.string.share, false) { hatchingDialog, _ -> - val message = getString(R.string.share_hatched, potionName, eggName) - val petImageSideLength = 140 - val sharedImage = Bitmap.createBitmap(petImageSideLength, petImageSideLength, Bitmap.Config.ARGB_8888) - val canvas = Canvas(sharedImage) - petImageView?.drawable?.setBounds(0, 0, petImageSideLength, petImageSideLength) - petImageView?.drawable?.draw(canvas) - shareContent("hatchedPet", message, sharedImage) - hatchingDialog.dismiss() - } - dialog.setExtraCloseButtonVisibility(View.VISIBLE) - dialog.enqueue() - }.subscribe({ }, RxErrorHandler.handleEmptyError()) - ) - } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt index d274c3c5f..f0f5ecdab 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt @@ -7,8 +7,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.widget.* +import androidx.activity.viewModels import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.InventoryRepository @@ -30,7 +30,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget @Inject lateinit var socialRepository: SocialRepository - lateinit var viewModel: NotificationsViewModel + val viewModel: NotificationsViewModel by viewModels() var inflater: LayoutInflater? = null @@ -50,8 +50,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater - viewModel = ViewModelProvider(this).get(NotificationsViewModel::class.java) - compositeSubscription.add( viewModel.getNotifications().subscribe( { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt index 297a8a210..7bcd7e6f1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt @@ -10,6 +10,7 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.data.TutorialRepository import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.ui.activities.MainActivity import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -25,6 +26,8 @@ abstract class BaseFragment : Fragment() { @Inject lateinit var tutorialRepository: TutorialRepository + @Inject + lateinit var analyticsManager: AnalyticsManager var tutorialStepIdentifier: String? = null var tutorialText: String? = null diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt index ad80245e6..9fe7288b5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt @@ -44,8 +44,8 @@ abstract class BaseMainFragment : BaseFragment() { override fun onAttach(context: Context) { super.onAttach(context) - if (getActivity()?.javaClass == MainActivity::class.java) { - user = activity?.user + if (activity is MainActivity) { + user = activity?.viewModel?.user?.value } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt index 722b4da21..27d2f0368 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt @@ -16,9 +16,12 @@ import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.interactors.FeedPetUseCase +import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.* import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment @@ -36,6 +39,11 @@ class ItemDialogFragment : BaseDialogFragment(), SwipeRefr lateinit var socialRepository: SocialRepository @Inject lateinit var userRepository: UserRepository + @Inject + lateinit var hatchPetUseCase: HatchPetUseCase + @Inject + lateinit var feedPetUseCase: FeedPetUseCase + var adapter: ItemRecyclerAdapter? = null var itemType: String? = null var itemTypeText: String? = null @@ -194,7 +202,14 @@ class ItemDialogFragment : BaseDialogFragment(), SwipeRefr } private fun feedPet(food: Food) { - feedingPet?.let { (activity as? MainActivity)?.feedPet(it, food) } + val pet = feedingPet ?: return + (activity as? BaseActivity)?.let { + compositeSubscription.add(feedPetUseCase.observable( + FeedPetUseCase.RequestValues( + pet, food, + it + )).subscribeWithErrorHandler {}) + } } override fun onResume() { @@ -225,7 +240,13 @@ class ItemDialogFragment : BaseDialogFragment(), SwipeRefr private fun hatchPet(potion: HatchingPotion, egg: Egg) { dismiss() - (activity as? MainActivity)?.hatchPet(potion, egg) + (activity as? BaseActivity)?.let { + compositeSubscription.add(hatchPetUseCase.observable( + HatchPetUseCase.RequestValues( + potion, egg, + it + )).subscribeWithErrorHandler {}) + } } private fun loadItems() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt index 1aec01f32..52e1a3262 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt @@ -15,9 +15,12 @@ import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.MainNavigationController import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.interactors.HatchPetUseCase +import com.habitrpg.android.habitica.interactors.NotifyUserUseCase import com.habitrpg.android.habitica.models.inventory.* import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.User +import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseFragment @@ -35,6 +38,9 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL lateinit var socialRepository: SocialRepository @Inject lateinit var userRepository: UserRepository + @Inject + internal lateinit var hatchPetUseCase: HatchPetUseCase + var adapter: ItemRecyclerAdapter? = null var itemType: String? = null var itemTypeText: String? = null @@ -178,7 +184,13 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL } private fun hatchPet(potion: HatchingPotion, egg: Egg) { - (activity as? MainActivity)?.hatchPet(potion, egg) + (activity as? BaseActivity)?.let { + compositeSubscription.add(hatchPetUseCase.observable( + HatchPetUseCase.RequestValues( + potion, egg, + it + )).subscribeWithErrorHandler {}) + } } private fun loadItems() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt index 6593deb66..f007fb066 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt @@ -149,7 +149,7 @@ open class ShopFragment : BaseMainFragment() currencyView.hourglassVisibility = View.GONE - context?.let { FirebaseAnalytics.getInstance(it).logEvent("open_shop", bundleOf(Pair("shopIdentifier", shopIdentifier))) } + context?.let { analyticsManager.logEvent("open_shop", bundleOf(Pair("shopIdentifier", shopIdentifier))) } } private fun showClassChangeDialog(classIdentifier: String) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt index 3c0b3b076..a0d655fa4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt @@ -10,10 +10,14 @@ 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.RxErrorHandler +import com.habitrpg.android.habitica.interactors.FeedPetUseCase +import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.* import com.habitrpg.android.habitica.models.user.OwnedMount import com.habitrpg.android.habitica.models.user.OwnedPet +import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.adapter.inventory.PetDetailRecyclerAdapter import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment @@ -28,6 +32,8 @@ class PetDetailRecyclerFragment : @Inject lateinit var inventoryRepository: InventoryRepository + @Inject + lateinit var feedPetUseCase: FeedPetUseCase var adapter: PetDetailRecyclerAdapter = PetDetailRecyclerAdapter() private var animalType: String? = null @@ -192,7 +198,13 @@ class PetDetailRecyclerFragment : private fun showFeedingDialog(pet: Pet, food: Food?) { if (food != null) { - activity?.feedPet(pet, food) + (activity as? BaseActivity)?.let { + compositeSubscription.add(feedPetUseCase.observable( + FeedPetUseCase.RequestValues( + pet, food, + it + )).subscribeWithErrorHandler {}) + } return } val fragment = ItemDialogFragment() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt index 221d3ffb9..1c289f462 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt @@ -139,7 +139,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare (activity as? SnackbarActivity)?.showSnackbar( content = context?.getString(R.string.reloading_content) ) - contentRepository.retrieveContent(context, true).subscribe( + contentRepository.retrieveContent(true).subscribe( { (activity as? SnackbarActivity)?.showSnackbar( content = context?.getString(R.string.reloaded_content), @@ -220,7 +220,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare } userRepository.updateLanguage(languageHelper.languageCode ?: "en") - .flatMap { contentRepository.retrieveContent(context, true) } + .flatMap { contentRepository.retrieveContent(true) } .subscribe({ }, RxErrorHandler.handleEmptyError()) val intent = Intent(activity, MainActivity::class.java) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt index 27ca992de..bca9e0724 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GiftPurchaseGemsFragment.kt @@ -23,8 +23,6 @@ import javax.inject.Inject class GiftPurchaseGemsFragment : BaseFragment() { - @Inject - lateinit var analyticsManager: AnalyticsManager @Inject lateinit var socialRepository: SocialRepository diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt index 2fa5b1f31..e26ee7a06 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt @@ -45,8 +45,6 @@ class SubscriptionFragment : BaseFragment() { return FragmentSubscriptionBinding.inflate(inflater, container, false) } - @Inject - lateinit var analyticsManager: AnalyticsManager @Inject lateinit var userRepository: UserRepository @Inject diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index 0be93d0fc..d2f4c577b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -31,7 +31,11 @@ import io.reactivex.rxjava3.disposables.Disposable import java.util.concurrent.TimeUnit import javax.inject.Inject -class ChatFragment : BaseFragment() { +class ChatFragment() : BaseFragment() { + + constructor(viewModel: GroupViewModel) : this() { + this.viewModel = viewModel + } override var binding: FragmentChatBinding? = null @@ -67,9 +71,7 @@ class ChatFragment : BaseFragment() { chatAdapter?.let { adapter -> compositeSubscription.add( adapter.getUserLabelClickFlowable().subscribe( - { userId -> - FullProfileActivity.open(userId) - }, + { userId -> FullProfileActivity.open(userId) }, RxErrorHandler.handleEmptyError() ) ) @@ -95,12 +97,7 @@ class ChatFragment : BaseFragment() { } }) - viewModel?.chatmessages?.observe( - viewLifecycleOwner, - { - setChatMessages(it) - } - ) + viewModel?.chatmessages?.observe(viewLifecycleOwner, { setChatMessages(it) }) binding?.chatBarView?.onCommunityGuidelinesAccepted = { viewModel?.updateUser("flags.communityGuidelinesAccepted", true) @@ -131,7 +128,7 @@ class ChatFragment : BaseFragment() { } private fun startAutoRefreshing() { - if (refreshDisposable != null && refreshDisposable?.isDisposed != true) { + if (refreshDisposable?.isDisposed != true) { refreshDisposable?.dispose() } refreshDisposable = Observable.interval(30, TimeUnit.SECONDS) @@ -219,11 +216,7 @@ class ChatFragment : BaseFragment() { private fun sendChatMessage(chatText: String) { viewModel?.postGroupChat( chatText, - { - binding?.recyclerView?.scrollToPosition(0) - } - ) { - binding?.chatBarView?.message = chatText - } + { binding?.recyclerView?.scrollToPosition(0) } + ) { binding?.chatBarView?.message = chatText } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt index 5603a0ca5..aa548b3a0 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt @@ -6,7 +6,7 @@ import android.content.Context import android.os.Bundle import android.view.* import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import com.habitrpg.android.habitica.MainNavDirections import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.UserComponent @@ -51,7 +51,9 @@ class InboxMessageListFragment : BaseMainFragment + viewModel.memberID?.let { userID -> socialRepository.postPrivateMessage(userID, chatText) .delay(200, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { binding?.recyclerView?.scrollToPosition(0) - viewModel?.invalidateDataSource() + viewModel.invalidateDataSource() }, { error -> RxErrorHandler.reportError(error) - if (binding != null) { - val alert = HabiticaAlertDialog(binding!!.chatBarView.context) + binding?.let { + val alert = HabiticaAlertDialog(it.chatBarView.context) alert.setTitle("You cannot reply to this conversation") alert.setMessage("This user is unable to receive your private message") alert.addOkButton() diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt index 815e81bb2..1b6e1518c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt @@ -3,7 +3,7 @@ package com.habitrpg.android.habitica.ui.fragments.social import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.tabs.TabLayoutMediator import com.habitrpg.android.habitica.R @@ -27,10 +27,7 @@ class TavernFragment : BaseMainFragment() { return FragmentViewpagerBinding.inflate(inflater, container, false) } - internal lateinit var viewModel: GroupViewModel - - internal var tavernDetailFragment = TavernDetailFragment() - private var chatFragment: ChatFragment? = null + internal val viewModel: GroupViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, @@ -48,7 +45,6 @@ class TavernFragment : BaseMainFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(this).get(GroupViewModel::class.java) viewModel.groupViewType = GroupViewType.GUILD viewModel.setGroupID(Group.TAVERN_ID) @@ -90,9 +86,7 @@ class TavernFragment : BaseMainFragment() { TavernDetailFragment() } 1 -> { - chatFragment = ChatFragment() - chatFragment?.viewModel = viewModel - chatFragment ?: Fragment() + ChatFragment(viewModel) } else -> Fragment() } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt index 093539c71..5659932e6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt @@ -28,7 +28,7 @@ import com.habitrpg.android.habitica.ui.helpers.setMarkdown import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel import com.habitrpg.android.habitica.ui.views.HabiticaIcons import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar +import com.habitrpg.android.habitica.ui.views.SnackbarActivity import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -75,10 +75,7 @@ class GuildDetailFragment : BaseFragment() { } binding?.joinButton?.setOnClickListener { viewModel?.joinGroup { - val activity = activity as? MainActivity - if (activity != null) { - HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.joined_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) - } + (this.activity as? SnackbarActivity)?.showSnackbar(title = getString(R.string.joined_guild)) } } binding?.inviteButton?.setOnClickListener { @@ -86,7 +83,7 @@ class GuildDetailFragment : BaseFragment() { sendInvitesResult.launch(intent) } binding?.leaderWrapper?.setOnClickListener { - viewModel?.getGroupData()?.value?.leaderID?.let { leaderID -> + viewModel?.leaderID?.let { leaderID -> val profileDirections = MainNavDirections.openProfileActivity(leaderID) MainNavigationController.navigate(profileDirections) } @@ -98,7 +95,7 @@ class GuildDetailFragment : BaseFragment() { return } binding?.leaderAvatarView?.setAvatar(leader) - binding?.leaderProfileName?.username = leader.profile?.name + binding?.leaderProfileName?.username = leader.displayName binding?.leaderProfileName?.tier = leader.contributor?.level ?: 0 binding?.leaderUsername?.text = leader.formattedUsername } @@ -140,7 +137,7 @@ class GuildDetailFragment : BaseFragment() { } private fun getGroupChallenges(): List { - var groupChallenges = mutableListOf() + val groupChallenges = mutableListOf() userRepository.getUser(userId).forEach { it.challenges?.forEach { challengeRepository.getChallenge(it.challengeID).forEach { @@ -156,7 +153,7 @@ class GuildDetailFragment : BaseFragment() { internal fun leaveGuild() { val context = context if (context != null) { - var groupChallenges = getGroupChallenges() + val groupChallenges = getGroupChallenges() lifecycleScope.launch(Dispatchers.Main) { delay(500) if (groupChallenges.isNotEmpty()) { @@ -188,10 +185,7 @@ class GuildDetailFragment : BaseFragment() { } private fun showLeaveSnackbar() { - val activity = activity as? MainActivity - if (activity != null) { - HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.left_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) - } + (this.activity as? MainActivity)?.showSnackbar(title = getString(R.string.left_guild)) } override fun injectFragment(component: UserComponent) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt index 44c08b6ed..99a65ddbe 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt @@ -6,7 +6,7 @@ import android.os.Bundle import android.view.* import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator @@ -23,7 +23,7 @@ import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType class GuildFragment : BaseMainFragment() { - internal lateinit var viewModel: GroupViewModel + internal val viewModel: GroupViewModel by viewModels() private var guildInformationFragment: GuildDetailFragment? = null private var chatFragment: ChatFragment? = null @@ -51,7 +51,6 @@ class GuildFragment : BaseMainFragment() { showsBackButton = true super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(this).get(GroupViewModel::class.java) viewModel.groupViewType = GroupViewType.GUILD viewModel.getGroupData().observe(viewLifecycleOwner, { setGroup(it) }) viewModel.getIsMemberData().observe(viewLifecycleOwner, { activity?.invalidateOptionsMenu() }) @@ -95,10 +94,9 @@ class GuildFragment : BaseMainFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - val guild = viewModel.getGroupData().value - if (this.activity != null && guild != null) { + if (this.activity != null) { if (viewModel.isMember) { - if (this.user != null && this.user?.id == guild.leaderID) { + if (viewModel.isLeader) { this.activity?.menuInflater?.inflate(R.menu.guild_admin, menu) } else { this.activity?.menuInflater?.inflate(R.menu.guild_member, menu) @@ -219,7 +217,7 @@ class GuildFragment : BaseMainFragment() { private fun setGroup(group: Group?) { this.activity?.invalidateOptionsMenu() - if (group?.privacy == "public") { + if (viewModel.isPublicGuild) { chatFragment?.autocompleteContext = "publicGuild" } else { chatFragment?.autocompleteContext = "privateGuild" diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt index 3f5ae0d76..62eea83b3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt @@ -6,7 +6,7 @@ import android.os.Bundle import android.view.* import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator @@ -28,7 +28,7 @@ class PartyFragment : BaseMainFragment() { private var chatFragment: ChatFragment? = null private var viewPagerAdapter: FragmentStateAdapter? = null - internal lateinit var viewModel: PartyViewModel + internal val viewModel: PartyViewModel by viewModels() override var binding: FragmentViewpagerBinding? = null @@ -49,8 +49,6 @@ class PartyFragment : BaseMainFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(this) - .get(PartyViewModel::class.java) viewModel.groupViewType = GroupViewType.PARTY viewModel.getGroupData().observe( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index 0ae114889..4fb5c43c1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -439,7 +439,7 @@ open class TaskRecyclerViewFragment : BaseFragment taskFilterHelper.setActiveFilter(TaskType.TODO, Task.FILTER_ACTIVE) TaskType.DAILY -> { - val user = (activity as? MainActivity)?.user + val user = (activity as? MainActivity)?.viewModel?.user?.value if (user?.isValid == true && user.preferences?.dailyDueDefaultView == true) { taskFilterHelper.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt index 23551b00d..7d61bde65 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/BaseViewModel.kt @@ -15,6 +15,11 @@ import javax.inject.Inject abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() { + val isUserFainted: Boolean + get() = (user.value?.stats?.hp ?: 1.0) == 0.0 + val isUserInParty: Boolean + get() = user.value?.hasParty == true + @Inject lateinit var userRepository: UserRepository @@ -42,7 +47,7 @@ abstract class BaseViewModel(initializeComponent: Boolean = true) : ViewModel() internal val disposable = CompositeDisposable() - private fun loadUserFromLocal() { + internal fun loadUserFromLocal() { disposable.add(userRepository.getUser().observeOn(AndroidSchedulers.mainThread()) .subscribe({ _user.value = it }, RxErrorHandler.handleEmptyError())) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt index 71b1fb0bf..ceee471c5 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt @@ -109,6 +109,10 @@ open class GroupViewModel(initializeComponent: Boolean) : BaseViewModel(initiali get() = isMemberData.value ?: false val leaderID: String? get() = group.value?.leaderID + val isLeader: Boolean + get() = user.value?.id == leaderID + val isPublicGuild: Boolean + get() = group.value?.privacy == "public" fun getGroupData(): LiveData = group fun getLeaderData(): LiveData = leader diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt new file mode 100644 index 000000000..198e3ed12 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt @@ -0,0 +1,162 @@ +package com.habitrpg.android.habitica.ui.viewmodels + +import android.content.SharedPreferences +import androidx.core.content.edit +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.api.HostConfig +import com.habitrpg.android.habitica.api.MaintenanceApiService +import com.habitrpg.android.habitica.components.UserComponent +import com.habitrpg.android.habitica.data.ContentRepository +import com.habitrpg.android.habitica.data.InventoryRepository +import com.habitrpg.android.habitica.data.TaskRepository +import com.habitrpg.android.habitica.helpers.AmplitudeManager +import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.helpers.TaskAlarmManager +import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager +import com.habitrpg.android.habitica.models.TutorialStep +import com.habitrpg.android.habitica.models.inventory.Egg +import com.habitrpg.android.habitica.models.responses.MaintenanceResponse +import com.habitrpg.android.habitica.proxy.AnalyticsManager +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import io.realm.kotlin.isValid +import java.util.* +import javax.inject.Inject + +class MainActivityViewModel: BaseViewModel() { + @Inject + internal lateinit var hostConfig: HostConfig + @Inject + internal lateinit var pushNotificationManager: PushNotificationManager + @Inject + internal lateinit var sharedPreferences: SharedPreferences + @Inject + internal lateinit var contentRepository: ContentRepository + @Inject + internal lateinit var taskRepository: TaskRepository + @Inject + internal lateinit var inventoryRepository: InventoryRepository + @Inject + internal lateinit var taskAlarmManager: TaskAlarmManager + @Inject + internal lateinit var analyticsManager: AnalyticsManager + @Inject + internal lateinit var maintenanceService: MaintenanceApiService + + override fun inject(component: UserComponent) { + component.inject(this) + } + + val isAuthenticated: Boolean + get() = hostConfig.apiKey.isNotBlank() && hostConfig.userID.isNotBlank() + val launchScreen: String? + get() = sharedPreferences.getString("launch_screen", "") + var preferenceLanguage: String? + get() = sharedPreferences.getString("language", "en") + set(value) { + sharedPreferences.edit { + putString("language", value) + } + } + + override fun onCleared() { + taskRepository.close() + inventoryRepository.close() + contentRepository.close() + super.onCleared() + } + + fun onCreate() { + try { + taskAlarmManager.scheduleAllSavedAlarms(sharedPreferences.getBoolean("preventDailyReminder", false)) + } catch (e: Exception) { + analyticsManager.logException(e) + } + } + + fun onResume() { + // Track when the app was last opened, so that we can use this to send out special reminders after a week of inactivity + sharedPreferences.edit { + putLong("lastAppLaunch", Date().time) + putBoolean("preventDailyReminder", false) + } + } + + fun retrieveUser(forced: Boolean = false) { + if (hostConfig.hasAuthentication()) { + disposable.add( + contentRepository.retrieveWorldState() + .flatMap { userRepository.retrieveUser(true, forced) } + .doOnNext { user1 -> + analyticsManager.setUserProperty("has_party", if (user1.party?.id?.isNotEmpty() == true) "true" else "false") + analyticsManager.setUserProperty("is_subscribed", if (user1.isSubscribed) "true" else "false") + analyticsManager.setUserProperty("checkin_count", user1.loginIncentives.toString()) + analyticsManager.setUserProperty("level", user1.stats?.lvl?.toString() ?: "") + pushNotificationManager.setUser(user1) + pushNotificationManager.addPushDeviceUsingStoredToken() + } + .flatMap { userRepository.retrieveTeamPlans() } + .flatMap { contentRepository.retrieveContent() } + .subscribe({ }, RxErrorHandler.handleEmptyError()) + ) + } + } + + + fun logTutorialStatus(step: TutorialStep, complete: Boolean) { + val additionalData = HashMap() + additionalData["eventLabel"] = step.identifier + "-android" + additionalData["eventValue"] = step.identifier ?: "" + additionalData["complete"] = complete + AmplitudeManager.sendEvent( + "tutorial", + AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, + AmplitudeManager.EVENT_HITTYPE_EVENT, + additionalData + ) + } + + fun ifNeedsMaintenance(onResult: ((MaintenanceResponse) -> Unit)) { + disposable.add( + this.maintenanceService.maintenanceStatus + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( { maintenanceResponse -> + if (maintenanceResponse == null) { + return@subscribe + } + onResult(maintenanceResponse) + }, + RxErrorHandler.handleEmptyError() + ) + ) + } + + fun getToolbarTitle(id: Int, label: CharSequence?, eggType: String?, onSuccess: ((CharSequence?) -> Unit)) { + if (id == R.id.petDetailRecyclerFragment || id == R.id.mountDetailRecyclerFragment) { + disposable.add( + inventoryRepository.getItem("egg", eggType ?: "").firstElement().subscribe( + { + if (!it.isValid()) return@subscribe + onSuccess( + if (id == R.id.petDetailRecyclerFragment) { + (it as? Egg)?.text + } else { + (it as? Egg)?.mountText + } + ) + }, + RxErrorHandler.handleEmptyError() + ) + ) + } else { + onSuccess( + if (id == R.id.promoInfoFragment) { + "" + } else if (label.isNullOrEmpty() && user.value?.isValid == true) { + user.value?.profile?.name + } else label ?: "" + ) + } + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt index 54000224b..1358370e3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt @@ -8,11 +8,14 @@ import android.widget.LinearLayout import androidx.core.graphics.drawable.toBitmap import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding +import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler import com.habitrpg.android.habitica.helpers.RxErrorHandler +import com.habitrpg.android.habitica.interactors.HatchPetUseCase import com.habitrpg.android.habitica.models.inventory.Animal import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.Item +import com.habitrpg.android.habitica.ui.activities.BaseActivity import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils import com.habitrpg.android.habitica.ui.views.CurrencyView @@ -20,9 +23,13 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Observable import java.util.* +import javax.inject.Inject class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { + @Inject + lateinit var hatchPetUseCase: HatchPetUseCase + private lateinit var binding: DialogPetSuggestHatchBinding init { @@ -75,7 +82,7 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { addButton(R.string.hatch, true, false) { _, _ -> val thisPotion = potion ?: return@addButton val thisEgg = egg ?: return@addButton - (getActivity() as? MainActivity)?.hatchPet(thisPotion, thisEgg) + hatchPet(thisPotion, thisEgg) } if (hasMount) { setTitle(R.string.hatch_your_pet) @@ -134,7 +141,7 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { .flatMap { activity.userRepository.retrieveUser(true, forced = true) } .subscribe( { - (getActivity() as? MainActivity)?.hatchPet(thisPotion, thisEgg) + hatchPet(thisPotion, thisEgg) }, RxErrorHandler.handleEmptyError() ) @@ -159,6 +166,16 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) { } } + private fun hatchPet(potion: HatchingPotion, egg: Egg) { + (getActivity() as? BaseActivity)?.let { + hatchPetUseCase.observable( + HatchPetUseCase.RequestValues( + potion, egg, + it + )).subscribeWithErrorHandler {} + } + } + private fun getItemPrice(pet: Animal, item: Item?, hasUnlocked: Boolean): Int { if (pet.type == "drop" || (pet.type == "quest" && hasUnlocked)) { return item?.value ?: 0 diff --git a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt b/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt index 2ed352819..666fb780d 100644 --- a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt +++ b/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt @@ -19,6 +19,10 @@ class AnalyticsManagerImpl(context: Context) : AnalyticsManager { Amplitude.getInstance().userId = identifier } + override fun setUserProperty(identifier: String, value: String) { + firebaseAnalytics.setUserProperty(identifier, value) + } + override fun logError(msg: String) { FirebaseCrashlytics.getInstance().log(msg) }