From fa56d1a32ed3bce54498379e2a9ace4a62f896d6 Mon Sep 17 00:00:00 2001 From: JishnuGoyal <64526117+JishnuGoyal@users.noreply.github.com> Date: Sun, 1 May 2022 01:14:16 +0530 Subject: [PATCH 001/138] first approach --- .../ExplorationCheckpointController.kt | 1 + .../spotlight/SpotlightStateController.kt | 127 ++++++++++++++++++ model/src/main/proto/spotlight.proto | 47 +++++++ 3 files changed, 175 insertions(+) create mode 100644 domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt create mode 100644 model/src/main/proto/spotlight.proto diff --git a/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt b/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt index 21bf8e7767b..c1e3acf365d 100644 --- a/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt +++ b/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt @@ -16,6 +16,7 @@ import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProviders.Companion.transformAsync import javax.inject.Inject import javax.inject.Singleton +import org.oppia.android.app.model.OnboardingActivitySpotlight private const val CACHE_NAME = "exploration_checkpoint_database" private const val RETRIEVE_EXPLORATION_CHECKPOINT_DATA_PROVIDER_ID = diff --git a/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt b/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt new file mode 100644 index 00000000000..150154abc86 --- /dev/null +++ b/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt @@ -0,0 +1,127 @@ +package org.oppia.android.domain.spotlight + +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.Deferred +import org.oppia.android.app.model.ProfileId +import org.oppia.android.app.model.SpotlightCheckpointDatabase +import org.oppia.android.app.model.SpotlightState +import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.util.data.AsyncResult +import org.oppia.android.util.data.DataProvider +import org.oppia.android.util.data.DataProviders +import org.oppia.android.util.data.DataProviders.Companion.transformAsync + +private const val SPOTLIGHT_STATE_DATA_PROVIDER_ID = "spotlight_state_data_provider_id" +private const val CACHE_NAME = "spotlight_checkpoint_database" +private const val RECORD_SPOTLIGHT_CHECKPOINT_DATA_PROVIDER_ID = + "record_spotlight_checkpoint_provider_id" +private const val RETRIEVE_SPOTLIGHT_CHECKPOINT_DATA_PROVIDER_ID = + "retrieve_spotlight_checkpoint_provider_id" + +@Singleton +class SpotlightStateController @Inject constructor( + private val cacheStoreFactory: PersistentCacheStore.Factory, + private val oppiaLogger: OppiaLogger, + private val dataProviders: DataProviders, +) { + + class SpotlightStateNotFoundException(message: String) : Exception(message) + + private val cacheStoreMap = + mutableMapOf>() + + private fun recordSpotlightStateAsync( + profileId: ProfileId, + spotlightState: SpotlightState, + spotlightActivity: SpotlightActivity + ): Deferred { + return retrieveCacheStore(profileId).storeDataWithCustomChannelAsync( + updateInMemoryCache = true + ) { + val spotlightCheckpointDatabaseBuilder = it.toBuilder() + + val checkpoint = spotlightCheckpointDatabaseBuilder + .setOnboardingSpotlightCheckpoint( + + ) + + + val spotlightCheckpointDatabase = spotlightCheckpointDatabaseBuilder.build() + + Pair(spotlightCheckpointDatabase, checkpoint) + } + } + + fun recordSpotlightState( + profileId: ProfileId, + spotlightState: SpotlightState, + spotlightActivity: SpotlightActivity + ): DataProvider { + val deferred = recordSpotlightStateAsync( + profileId, + spotlightState, + spotlightActivity + ) + return dataProviders.createInMemoryDataProviderAsync( + RECORD_SPOTLIGHT_CHECKPOINT_DATA_PROVIDER_ID + ) { + return@createInMemoryDataProviderAsync AsyncResult.Success(deferred.await()) + } + } + + fun retrieveSpotlightState( + profileId: ProfileId, + explorationId: String, + spotlightActivity: SpotlightActivity + ): DataProvider { + return retrieveCacheStore(profileId) + .transformAsync( + RETRIEVE_SPOTLIGHT_CHECKPOINT_DATA_PROVIDER_ID + ) { + + val checkpoint = it.onboardingSpotlightCheckpoint.spotlightState + + if (checkpoint != null) { + AsyncResult.Success(checkpoint) + } else { + AsyncResult.Failure(SpotlightStateNotFoundException("State not found ")) + } + + } + } + + private fun retrieveCacheStore( + profileId: ProfileId + ): PersistentCacheStore { + val cacheStore = if (profileId in cacheStoreMap) { + cacheStoreMap[profileId]!! + } else { + val cacheStore = + cacheStoreFactory.createPerProfile( + CACHE_NAME, + SpotlightCheckpointDatabase.getDefaultInstance(), + profileId + ) + cacheStoreMap[profileId] = cacheStore + cacheStore + } + + cacheStore.primeCacheAsync().invokeOnCompletion { throwable -> + throwable?.let { + oppiaLogger.e( + "SpotlightCheckpointController", + "Failed to prime cache ahead of data retrieval for SpotlightCheckpointController.", + it + ) + } + } + return cacheStore + } +} + +enum class SpotlightActivity { + ONBOARDING_ACTIVITY, + PROFILE_ACTIVITY +} \ No newline at end of file diff --git a/model/src/main/proto/spotlight.proto b/model/src/main/proto/spotlight.proto new file mode 100644 index 00000000000..1ea480badbd --- /dev/null +++ b/model/src/main/proto/spotlight.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package model; + +option java_package = "org.oppia.android.app.model"; +option java_multiple_files = true; + +message OnboardingActivitySpotlight{ + SpotlightState spotlight_state = 1; + + enum LastScreenViewed{ + ONBOARDING_SCREEN_1 = 0; + ONBOARDING_SCREEN_2 = 1; + ONBOARDING_SCREEN_3 = 2; + ONBOARDING_SCREEN_4 = 3; + } + + LastScreenViewed last_screen_viewed = 2; +} + +enum SpotlightState{ + SPOTLIGHT_STATE_COMPLETED = 0; + SPOTLIGHT_STATE_PARTIAL = 1; + SPOTLIGHT_STATE_DISMISSED = 2; + SPOTLIGHT_STATE_UNSPECIFIED = 3; +} + +message ProfileActivitySpotlight{ + SpotlightState spotlight_state = 1; + + enum LastScreenViewed{ + PROFILE_SCREEN_1 = 0; + PROFILE_SCREEN_2 = 1; + PROFILE_SCREEN_3 = 2; + } + + LastScreenViewed last_screen_viewed = 2; +} + +message SpotlightCheckpointDatabase { + OnboardingActivitySpotlight onboarding_spotlight_checkpoint = 1; + ProfileActivitySpotlight profile_spotlight_checkpoint = 2; +} + + + +} \ No newline at end of file From 230420a40425a84359137d271c3e6d88bafa607d Mon Sep 17 00:00:00 2001 From: JishnuGoyal <64526117+JishnuGoyal@users.noreply.github.com> Date: Sun, 1 May 2022 16:11:06 +0530 Subject: [PATCH 002/138] before starting top down. --- .../spotlight/SpotlightStateController.kt | 11 +++-- model/src/main/proto/spotlight.proto | 48 ++++++++----------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt b/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt index 150154abc86..8e1307635cd 100644 --- a/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt +++ b/domain/src/main/java/org/oppia/android/domain/spotlight/SpotlightStateController.kt @@ -42,10 +42,15 @@ class SpotlightStateController @Inject constructor( ) { val spotlightCheckpointDatabaseBuilder = it.toBuilder() - val checkpoint = spotlightCheckpointDatabaseBuilder - .setOnboardingSpotlightCheckpoint( + val checkpoint : SpotlightState = when (spotlightActivity) { + SpotlightActivity.ONBOARDING_ACTIVITY -> { + spotlightCheckpointDatabaseBuilder.onboardingSpotlightCheckpoint.spotlightState + } + SpotlightActivity.PROFILE_ACTIVITY -> { + spotlightCheckpointDatabaseBuilder.profileSpotlightCheckpoint.spotlightState + } + } - ) val spotlightCheckpointDatabase = spotlightCheckpointDatabaseBuilder.build() diff --git a/model/src/main/proto/spotlight.proto b/model/src/main/proto/spotlight.proto index 1ea480badbd..85173ff4b42 100644 --- a/model/src/main/proto/spotlight.proto +++ b/model/src/main/proto/spotlight.proto @@ -5,43 +5,33 @@ package model; option java_package = "org.oppia.android.app.model"; option java_multiple_files = true; -message OnboardingActivitySpotlight{ - SpotlightState spotlight_state = 1; +message SpotlightCheckpointDatabase{ + OnboardingSpotlightCheckpoint onboarding_spotlight_checkpoint = 1 ; + ProfileSpotlightCheckpoint profile_spotlight_checkpoint = 2 ; +} - enum LastScreenViewed{ - ONBOARDING_SCREEN_1 = 0; - ONBOARDING_SCREEN_2 = 1; - ONBOARDING_SCREEN_3 = 2; - ONBOARDING_SCREEN_4 = 3; +message OnboardingSpotlightCheckpoint{ + SpotlightState spotlight_state = 1 ; + enum LastScreenViewed { + ONBOARDING1 = 0; + ONBOARDING2 = 1; } - LastScreenViewed last_screen_viewed = 2; } -enum SpotlightState{ - SPOTLIGHT_STATE_COMPLETED = 0; - SPOTLIGHT_STATE_PARTIAL = 1; - SPOTLIGHT_STATE_DISMISSED = 2; - SPOTLIGHT_STATE_UNSPECIFIED = 3; -} - -message ProfileActivitySpotlight{ - SpotlightState spotlight_state = 1; - - enum LastScreenViewed{ - PROFILE_SCREEN_1 = 0; - PROFILE_SCREEN_2 = 1; - PROFILE_SCREEN_3 = 2; +message ProfileSpotlightCheckpoint{ + SpotlightState spotlight_state = 1 ; + enum LastScreenViewed { + PROFILE1 = 0; + PROFILE2 = 1; } - LastScreenViewed last_screen_viewed = 2; } -message SpotlightCheckpointDatabase { - OnboardingActivitySpotlight onboarding_spotlight_checkpoint = 1; - ProfileActivitySpotlight profile_spotlight_checkpoint = 2; +enum SpotlightState{ + SPOTLIGHT_STATE_UNKNOWN = 0 ; + SPOTLIGHT_STATE_PARTIAL = 1 ; + SPOTLIGHT_STATE_DISMISSED = 2 ; + SPOTLIGHT_STATE_COMPLETED = 3 ; } - - -} \ No newline at end of file From 38e9372e3f6001f32c7eac4967cc9e0e7beb88f6 Mon Sep 17 00:00:00 2001 From: JishnuGoyal <64526117+JishnuGoyal@users.noreply.github.com> Date: Mon, 2 May 2022 03:18:12 +0530 Subject: [PATCH 003/138] binding for spotlight onboarding --- .../onboarding/OnboardingFragmentPresenter.kt | 62 +++++++++++++++++++ .../main/res/layout/onboarding_fragment.xml | 1 + app/src/main/res/layout/overlay.xml | 60 ++++++++++++++++++ .../ExplorationCheckpointController.kt | 1 - 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/layout/overlay.xml diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt index f359a4348c7..d19b4cc0968 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt @@ -3,11 +3,18 @@ package org.oppia.android.app.onboarding import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.animation.DecelerateInterpolator import android.widget.ImageView import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.viewpager2.widget.ViewPager2 +import com.takusemba.spotlight.OnSpotlightListener +import com.takusemba.spotlight.OnTargetListener +import com.takusemba.spotlight.Spotlight +import com.takusemba.spotlight.Target +import com.takusemba.spotlight.shape.Circle +import java.util.* import org.oppia.android.R import org.oppia.android.app.fragment.FragmentScope import org.oppia.android.app.recyclerview.BindableAdapter @@ -18,6 +25,7 @@ import org.oppia.android.databinding.OnboardingSlideBinding import org.oppia.android.databinding.OnboardingSlideFinalBinding import org.oppia.android.util.statusbar.StatusBarColor import javax.inject.Inject +import org.oppia.android.databinding.OverlayBinding /** The presenter for [OnboardingFragment]. */ @FragmentScope @@ -30,6 +38,7 @@ class OnboardingFragmentPresenter @Inject constructor( ) : OnboardingNavigationListener { private val dotsList = ArrayList() private lateinit var binding: OnboardingFragmentBinding + private lateinit var overlayBinding: OverlayBinding fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? { binding = OnboardingFragmentBinding.inflate( @@ -46,6 +55,7 @@ class OnboardingFragmentPresenter @Inject constructor( } setUpViewPager() addDots() + startSpotlight(inflater, container) return binding.root } @@ -213,4 +223,56 @@ class OnboardingFragmentPresenter @Inject constructor( dotsList[index].alpha = alphaValue } } + + private fun startSpotlight(inflater: LayoutInflater, container: ViewGroup?) { + val targets = ArrayList() + +// val firstRoot = FrameLayout(fragment.requireContext()) +// val first = fragment.layoutInflater.inflate(overlayBinding, firstRoot) + + overlayBinding = OverlayBinding.inflate(inflater, container, false) + + val firstTarget = Target.Builder() + .setAnchor(binding.onboardingFragmentNextImageView) + .setShape(Circle(100f)) + .setOverlay(overlayBinding.root) + .setOnTargetListener(object : OnTargetListener { + override fun onStarted() { + + } + + override fun onEnded() { + + } + }) + .build() + + targets.add(firstTarget) + + // create spotlight + val spotlight = Spotlight.Builder(activity) + .setTargets(targets) + .setBackgroundColorRes(R.color.spotlightBackground) + .setDuration(1000L) + .setAnimation(DecelerateInterpolator(2f)) + .setOnSpotlightListener(object : OnSpotlightListener { + override fun onStarted() { + + + } + + override fun onEnded() { + + + } + }) + .build() + + spotlight.start() + + + + binding.onboardingFragmentConstraintLayout?.setOnClickListener { spotlight.finish() } + } } + diff --git a/app/src/main/res/layout/onboarding_fragment.xml b/app/src/main/res/layout/onboarding_fragment.xml index 3fa73866ebb..0a1988f1448 100644 --- a/app/src/main/res/layout/onboarding_fragment.xml +++ b/app/src/main/res/layout/onboarding_fragment.xml @@ -16,6 +16,7 @@ diff --git a/app/src/main/res/layout/overlay.xml b/app/src/main/res/layout/overlay.xml new file mode 100644 index 00000000000..b482d81711b --- /dev/null +++ b/app/src/main/res/layout/overlay.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + +