This commit is contained in:
Phillip Thelen 2022-04-04 14:12:35 +02:00
parent 100966cdfa
commit 8dc433e7bf
14 changed files with 59 additions and 106 deletions

View file

@ -106,7 +106,7 @@ class UserRepositoryImpl(localRepository: UserLocalRepository, apiClient: ApiCli
.map<Map<String, Any>> { tutorialSteps -> .map<Map<String, Any>> { tutorialSteps ->
val updateData = HashMap<String, Any>() val updateData = HashMap<String, Any>()
for (step in tutorialSteps) { for (step in tutorialSteps) {
updateData["flags.tutorial." + step.tutorialGroup + "." + step.identifier] = false updateData[step.flagPath] = false
} }
updateData updateData
} }

View file

@ -15,17 +15,6 @@ class RealmTutorialLocalRepository(realm: Realm) : RealmBaseLocalRepository(real
.findAll() .findAll()
.asFlowable() .asFlowable()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && realmObject.isNotEmpty() } .filter { realmObject -> realmObject.isLoaded && realmObject.isValid && realmObject.isNotEmpty() }
.map { steps ->
return@map if (steps.isEmpty()) {
val step = TutorialStep()
step.identifier = key
val list = ArrayList<TutorialStep>()
list.add(step)
list
} else {
steps
}
}
.map { steps -> steps.first() } .map { steps -> steps.first() }
) )
} }

View file

@ -22,13 +22,18 @@ open class TutorialStep : RealmObject(), BaseMainObject {
var wasCompleted: Boolean = false var wasCompleted: Boolean = false
var displayedOn: Date? = null var displayedOn: Date? = null
fun shouldDisplay(): Boolean = val shouldDisplay: Boolean
!this.wasCompleted && (this.displayedOn == null || Date().time - (displayedOn?.time ?: 0) > 86400000) get() {
return !this.wasCompleted && (this.displayedOn == null || Date().time - (displayedOn?.time
?: 0) > 86400000)
}
override val realmClass: Class<out RealmModel> override val realmClass: Class<out RealmModel>
get() = TutorialStep::class.java get() = TutorialStep::class.java
override val primaryIdentifier: String? override val primaryIdentifier: String?
get() = key get() = key
override val primaryIdentifierName: String override val primaryIdentifierName: String
get() = "key" get() = "key"
val flagPath: String
get() = "flags.tutorial.$tutorialGroup.$identifier"
} }

View file

@ -8,7 +8,7 @@ import com.habitrpg.android.habitica.databinding.OverlayTutorialBinding
import com.habitrpg.android.habitica.extensions.layoutInflater import com.habitrpg.android.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.TutorialStep
class TutorialView(context: Context, var step: TutorialStep, var onReaction: OnTutorialReaction?) : FrameLayout(context) { class TutorialView(context: Context, val step: TutorialStep, private val onReaction: OnTutorialReaction) : FrameLayout(context) {
private val binding = OverlayTutorialBinding.inflate(context.layoutInflater, this, true) private val binding = OverlayTutorialBinding.inflate(context.layoutInflater, this, true)
private var tutorialTexts: List<String> = emptyList() private var tutorialTexts: List<String> = emptyList()
private var currentTextIndex: Int = 0 private var currentTextIndex: Int = 0
@ -35,6 +35,10 @@ class TutorialView(context: Context, var step: TutorialStep, var onReaction: OnT
} }
fun setTutorialTexts(texts: List<String>) { fun setTutorialTexts(texts: List<String>) {
if (texts.size == 1) {
setTutorialText(texts.first())
return
}
tutorialTexts = texts tutorialTexts = texts
currentTextIndex = -1 currentTextIndex = -1
displayNextTutorialText() displayNextTutorialText()
@ -55,17 +59,17 @@ class TutorialView(context: Context, var step: TutorialStep, var onReaction: OnT
binding.speechBubbleView.setHasMoreContent(true) binding.speechBubbleView.setHasMoreContent(true)
} }
} else { } else {
this.onReaction?.onTutorialCompleted(this.step) onReaction.onTutorialCompleted(step)
} }
} }
private fun completeButtonClicked() { private fun completeButtonClicked() {
this.onReaction?.onTutorialCompleted(this.step) onReaction.onTutorialCompleted(step)
(parent as? ViewGroup)?.removeView(this) (parent as? ViewGroup)?.removeView(this)
} }
private fun dismissButtonClicked() { private fun dismissButtonClicked() {
this.onReaction?.onTutorialDeferred(this.step) onReaction.onTutorialDeferred(step)
(parent as? ViewGroup)?.removeView(this) (parent as? ViewGroup)?.removeView(this)
} }
@ -75,7 +79,6 @@ class TutorialView(context: Context, var step: TutorialStep, var onReaction: OnT
interface OnTutorialReaction { interface OnTutorialReaction {
fun onTutorialCompleted(step: TutorialStep) fun onTutorialCompleted(step: TutorialStep)
fun onTutorialDeferred(step: TutorialStep) fun onTutorialDeferred(step: TutorialStep)
} }
} }

View file

@ -15,6 +15,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.view.children
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.findNavController import androidx.navigation.findNavController
@ -64,11 +65,10 @@ import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
import com.habitrpg.android.habitica.widget.TodoListWidgetProvider import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, SnackbarActivity { open class MainActivity : BaseActivity(), SnackbarActivity {
private var launchScreen: String? = null private var launchScreen: String? = null
@Inject @Inject
@ -98,7 +98,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack
val viewModel: MainActivityViewModel by viewModels() val viewModel: MainActivityViewModel by viewModels()
private var faintDialog: HabiticaAlertDialog? = null private var faintDialog: HabiticaAlertDialog? = null
private var sideAvatarView: AvatarView? = null private var sideAvatarView: AvatarView? = null
private var activeTutorialView: TutorialView? = null
private var drawerFragment: NavigationDrawerFragment? = null private var drawerFragment: NavigationDrawerFragment? = null
var drawerToggle: ActionBarDrawerToggle? = null var drawerToggle: ActionBarDrawerToggle? = null
private var resumeFromActivity = false private var resumeFromActivity = false
@ -397,9 +396,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack
} }
override fun onBackPressed() { override fun onBackPressed() {
if (this.activeTutorialView != null) {
this.removeActiveTutorialView()
}
if (drawerFragment?.isDrawerOpen == true) { if (drawerFragment?.isDrawerOpen == true) {
drawerFragment?.closeDrawer() drawerFragment?.closeDrawer()
} else { } else {
@ -479,47 +475,19 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction, Snack
viewModel.retrieveUser(forced) viewModel.retrieveUser(forced)
} }
fun displayTutorialStep(step: TutorialStep, text: String, canBeDeferred: Boolean) {
removeActiveTutorialView()
val view = TutorialView(this, step, this)
this.activeTutorialView = view
view.setTutorialText(text)
view.onReaction = this
view.setCanBeDeferred(canBeDeferred)
binding.overlayFrameLayout.addView(view)
viewModel.logTutorialStatus(step, false)
}
fun displayTutorialStep(step: TutorialStep, texts: List<String>, canBeDeferred: Boolean) { fun displayTutorialStep(step: TutorialStep, texts: List<String>, canBeDeferred: Boolean) {
removeActiveTutorialView() val view = TutorialView(this, step, viewModel)
val view = TutorialView(this, step, this)
this.activeTutorialView = view
view.setTutorialTexts(texts) view.setTutorialTexts(texts)
view.onReaction = this
view.setCanBeDeferred(canBeDeferred) view.setCanBeDeferred(canBeDeferred)
binding.overlayFrameLayout.children.forEach {
if (it is TutorialView) {
binding.overlayFrameLayout.removeView(it)
}
}
binding.overlayFrameLayout.addView(view) binding.overlayFrameLayout.addView(view)
viewModel.logTutorialStatus(step, false) viewModel.logTutorialStatus(step, false)
} }
override fun onTutorialCompleted(step: TutorialStep) {
viewModel.updateUser("flags.tutorial." + step.tutorialGroup + "." + step.identifier, true)
binding.overlayFrameLayout.removeView(this.activeTutorialView)
this.removeActiveTutorialView()
viewModel.logTutorialStatus(step, true)
}
override fun onTutorialDeferred(step: TutorialStep) {
taskRepository.modify(step) { it.displayedOn = Date() }
this.removeActiveTutorialView()
}
private fun removeActiveTutorialView() {
if (this.activeTutorialView != null) {
binding.overlayFrameLayout.removeView(this.activeTutorialView)
this.activeTutorialView = null
}
}
private fun checkMaintenance() { private fun checkMaintenance() {
viewModel.ifNeedsMaintenance { maintenanceResponse -> viewModel.ifNeedsMaintenance { maintenanceResponse ->
if (maintenanceResponse.activeMaintenance) { if (maintenanceResponse.activeMaintenance) {

View file

@ -27,7 +27,6 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment() {
lateinit var tutorialRepository: TutorialRepository lateinit var tutorialRepository: TutorialRepository
var tutorialStepIdentifier: String? = null var tutorialStepIdentifier: String? = null
var tutorialText: String? = null
protected var tutorialCanBeDeferred = true protected var tutorialCanBeDeferred = true
var tutorialTexts: MutableList<String> = ArrayList() var tutorialTexts: MutableList<String> = ArrayList()
@ -72,13 +71,9 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
Consumer { step -> Consumer { step ->
if (step != null && step.isValid && step.isManaged && step.shouldDisplay()) { if (step.isValid && step.isManaged && step.shouldDisplay()) {
val mainActivity = activity as? MainActivity ?: return@Consumer val mainActivity = activity as? MainActivity ?: return@Consumer
if (tutorialText != null) { mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
mainActivity.displayTutorialStep(step, tutorialText ?: "", tutorialCanBeDeferred)
} else {
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
}
} }
}, },
RxErrorHandler.handleEmptyError() RxErrorHandler.handleEmptyError()

View file

@ -14,7 +14,6 @@ import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.MainActivity
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.functions.Consumer
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -29,9 +28,8 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
var tutorialStepIdentifier: String? = null var tutorialStepIdentifier: String? = null
var tutorialText: String? = null
protected var tutorialCanBeDeferred = true protected var tutorialCanBeDeferred = true
var tutorialTexts: MutableList<String> = ArrayList() var tutorialTexts: List<String> = ArrayList()
protected var compositeSubscription: CompositeDisposable = CompositeDisposable() protected var compositeSubscription: CompositeDisposable = CompositeDisposable()
@ -69,27 +67,21 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
} }
private fun showTutorialIfNeeded() { private fun showTutorialIfNeeded() {
if (view != null) { tutorialStepIdentifier?.let { identifier ->
if (this.tutorialStepIdentifier != null) { compositeSubscription.add(
compositeSubscription.add( tutorialRepository.getTutorialStep(identifier)
tutorialRepository.getTutorialStep(this.tutorialStepIdentifier ?: "").firstElement() .firstElement()
.delay(1, TimeUnit.SECONDS) .delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe({ step ->
Consumer { step -> if (step.isValid && step.isManaged && step.shouldDisplay) {
if (step != null && step.isValid && step.isManaged && step.shouldDisplay()) { val mainActivity = activity as? MainActivity ?: return@subscribe
val mainActivity = activity as? MainActivity ?: return@Consumer mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred)
if (tutorialText != null) { }
mainActivity.displayTutorialStep(step, tutorialText ?: "", tutorialCanBeDeferred) },
} else { RxErrorHandler.handleEmptyError()
mainActivity.displayTutorialStep(step, tutorialTexts, tutorialCanBeDeferred) )
} )
}
},
RxErrorHandler.handleEmptyError()
)
)
}
} }
} }

View file

@ -68,7 +68,7 @@ class StatsFragment : BaseMainFragment<FragmentStatsBinding>() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
tutorialStepIdentifier = "stats" tutorialStepIdentifier = "stats"
tutorialText = getString(R.string.tutorial_stats) tutorialTexts = listOf(getString(R.string.tutorial_stats))
this.hidesToolbar = true this.hidesToolbar = true
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }

View file

@ -50,7 +50,7 @@ class SkillsFragment : BaseMainFragment<FragmentSkillsBinding>() {
adapter?.useSkillEvents?.subscribeWithErrorHandler { onSkillSelected(it) }?.let { compositeSubscription.add(it) } adapter?.useSkillEvents?.subscribeWithErrorHandler { onSkillSelected(it) }?.let { compositeSubscription.add(it) }
this.tutorialStepIdentifier = "skills" this.tutorialStepIdentifier = "skills"
this.tutorialText = getString(R.string.tutorial_skills) this.tutorialTexts = listOf(getString(R.string.tutorial_skills))
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }

View file

@ -42,7 +42,7 @@ class TavernFragment : BaseMainFragment<FragmentViewpagerBinding>() {
this.usesTabLayout = true this.usesTabLayout = true
this.hidesToolbar = true this.hidesToolbar = true
this.tutorialStepIdentifier = "tavern" this.tutorialStepIdentifier = "tavern"
this.tutorialText = getString(R.string.tutorial_tavern) this.tutorialTexts = listOf(getString(R.string.tutorial_tavern))
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }

View file

@ -77,7 +77,7 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
viewModel.loadPartyID() viewModel.loadPartyID()
this.tutorialStepIdentifier = "party" this.tutorialStepIdentifier = "party"
this.tutorialText = getString(R.string.tutorial_party) this.tutorialTexts = listOf(getString(R.string.tutorial_party))
viewModel.retrieveGroup {} viewModel.retrieveGroup {}
} }

View file

@ -329,7 +329,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
if (fragment?.tutorialTexts != null && context != null) { if (fragment?.tutorialTexts != null && context != null) {
val finalText = context?.getString(R.string.tutorial_tasks_complete) val finalText = context?.getString(R.string.tutorial_tasks_complete)
if (!fragment.tutorialTexts.contains(finalText) && finalText != null) { if (!fragment.tutorialTexts.contains(finalText) && finalText != null) {
fragment.tutorialTexts.add(finalText) fragment.tutorialTexts = fragment.tutorialTexts + finalText
} }
} }
} }

View file

@ -328,15 +328,6 @@ class TeamBoardFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchVi
tab?.badgeCount = 0 tab?.badgeCount = 0
} }
} }
if (activeTutorialFragments.size == 1) {
val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0]))
if (fragment?.tutorialTexts != null && context != null) {
val finalText = context?.getString(R.string.tutorial_tasks_complete)
if (!fragment.tutorialTexts.contains(finalText) && finalText != null) {
fragment.tutorialTexts.add(finalText)
}
}
}
}, },
RxErrorHandler.handleEmptyError() RxErrorHandler.handleEmptyError()
) )

View file

@ -17,13 +17,14 @@ import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.responses.MaintenanceResponse import com.habitrpg.android.habitica.models.responses.MaintenanceResponse
import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.TutorialView
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import io.realm.kotlin.isValid import io.realm.kotlin.isValid
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
class MainActivityViewModel : BaseViewModel() { class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction {
@Inject @Inject
internal lateinit var hostConfig: HostConfig internal lateinit var hostConfig: HostConfig
@Inject @Inject
@ -102,6 +103,15 @@ class MainActivityViewModel : BaseViewModel() {
} }
} }
override fun onTutorialCompleted(step: TutorialStep) {
updateUser("flags.tutorial." + step.tutorialGroup + "." + step.identifier, true)
logTutorialStatus(step, true)
}
override fun onTutorialDeferred(step: TutorialStep) {
taskRepository.modify(step) { it.displayedOn = Date() }
}
fun logTutorialStatus(step: TutorialStep, complete: Boolean) { fun logTutorialStatus(step: TutorialStep, complete: Boolean) {
val additionalData = HashMap<String, Any>() val additionalData = HashMap<String, Any>()
additionalData["eventLabel"] = step.identifier + "-android" additionalData["eventLabel"] = step.identifier + "-android"