diff --git a/app/src/main/java/org/oppia/app/home/HomeActivity.kt b/app/src/main/java/org/oppia/app/home/HomeActivity.kt index 2e8437a791d..4ff34fce56f 100644 --- a/app/src/main/java/org/oppia/app/home/HomeActivity.kt +++ b/app/src/main/java/org/oppia/app/home/HomeActivity.kt @@ -1,5 +1,6 @@ package org.oppia.app.home +import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AlertDialog @@ -9,10 +10,20 @@ import org.oppia.app.profile.ProfileActivity import org.oppia.app.topic.TopicActivity import javax.inject.Inject +const val KEY_HOME_PROFILE_ID = "KEY_HOME_PROFILE_ID" + /** The central activity for all users entering the app. */ class HomeActivity : InjectableAppCompatActivity(), RouteToTopicListener { @Inject lateinit var homeActivityPresenter: HomeActivityPresenter + companion object { + fun createHomeActivity(context: Context, profileId: Int?): Intent { + val intent = Intent(context, HomeActivity::class.java) + intent.putExtra(KEY_HOME_PROFILE_ID, profileId) + return intent + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityComponent.inject(this) diff --git a/app/src/main/java/org/oppia/app/home/HomeActivityPresenter.kt b/app/src/main/java/org/oppia/app/home/HomeActivityPresenter.kt index 154824cc82b..842b3c5ebb1 100644 --- a/app/src/main/java/org/oppia/app/home/HomeActivityPresenter.kt +++ b/app/src/main/java/org/oppia/app/home/HomeActivityPresenter.kt @@ -9,6 +9,8 @@ import org.oppia.app.activity.ActivityScope import org.oppia.app.drawer.NavigationDrawerFragment import javax.inject.Inject +const val TAG_HOME_FRAGMENT = "HOME_FRAGMENT" + /** The presenter for [HomeActivity]. */ @ActivityScope class HomeActivityPresenter @Inject constructor(private val activity: AppCompatActivity) { @@ -20,7 +22,8 @@ class HomeActivityPresenter @Inject constructor(private val activity: AppCompatA if (getHomeFragment() == null) { activity.supportFragmentManager.beginTransaction().add( R.id.home_fragment_placeholder, - HomeFragment() + HomeFragment(), + TAG_HOME_FRAGMENT ).commitNow() } } diff --git a/app/src/main/java/org/oppia/app/home/HomeFragmentPresenter.kt b/app/src/main/java/org/oppia/app/home/HomeFragmentPresenter.kt index 545404f4fd8..6646bcd6d53 100644 --- a/app/src/main/java/org/oppia/app/home/HomeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/app/home/HomeFragmentPresenter.kt @@ -18,10 +18,13 @@ import org.oppia.app.home.topiclist.TopicListAdapter import org.oppia.app.home.topiclist.TopicSummaryClickListener import org.oppia.app.home.topiclist.TopicSummaryViewModel import org.oppia.app.model.OngoingStoryList +import org.oppia.app.model.Profile +import org.oppia.app.model.ProfileId import org.oppia.app.model.TopicList import org.oppia.app.model.TopicSummary import org.oppia.app.model.UserAppHistory import org.oppia.domain.UserAppHistoryController +import org.oppia.domain.profile.ProfileManagementController import org.oppia.domain.topic.TopicListController import org.oppia.util.data.AsyncResult import org.oppia.util.logging.Logger @@ -32,6 +35,7 @@ import javax.inject.Inject class HomeFragmentPresenter @Inject constructor( private val activity: AppCompatActivity, private val fragment: Fragment, + private val profileManagementController: ProfileManagementController, private val userAppHistoryController: UserAppHistoryController, private val topicListController: TopicListController, private val logger: Logger @@ -44,6 +48,10 @@ class HomeFragmentPresenter @Inject constructor( private lateinit var allTopicsViewModel: AllTopicsViewModel private lateinit var topicListAdapter: TopicListAdapter private lateinit var binding: HomeFragmentBinding + private var internalProfileId: Int = -1 + private lateinit var profileId: ProfileId + private lateinit var profileName: String + fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? { binding = HomeFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false) // NB: Both the view model and lifecycle owner must be set in order to correctly bind LiveData elements to @@ -57,6 +65,9 @@ class HomeFragmentPresenter @Inject constructor( itemList.add(allTopicsViewModel) topicListAdapter = TopicListAdapter(activity, itemList, promotedStoryList) + internalProfileId = activity.intent.getIntExtra(KEY_HOME_PROFILE_ID, -1) + profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build() + val homeLayoutManager = GridLayoutManager(activity.applicationContext, 2) homeLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { @@ -79,12 +90,35 @@ class HomeFragmentPresenter @Inject constructor( } userAppHistoryController.markUserOpenedApp() + subscribeToProfileLiveData() subscribeToUserAppHistory() subscribeToOngoingStoryList() subscribeToTopicList() return binding.root } + private val profileLiveData: LiveData by lazy { + getProfileData() + } + + private fun getProfileData(): LiveData { + return Transformations.map(profileManagementController.getProfile(profileId), ::processGetProfileResult) + } + + private fun subscribeToProfileLiveData() { + profileLiveData.observe(activity, Observer { result -> + profileName = result.name + setProfileName() + }) + } + + private fun processGetProfileResult(profileResult: AsyncResult): Profile { + if (profileResult.isFailure()) { + logger.e("HomeFragment", "Failed to retrieve profile", profileResult.getErrorOrNull()!!) + } + return profileResult.getOrDefault(Profile.getDefaultInstance()) + } + private val topicListSummaryResultLiveData: LiveData> by lazy { topicListController.getTopicList() } @@ -108,6 +142,7 @@ class HomeFragmentPresenter @Inject constructor( getUserAppHistory().observe(fragment, Observer { result -> userAppHistoryViewModel = UserAppHistoryViewModel() userAppHistoryViewModel.setAlreadyAppOpened(result.alreadyOpenedApp) + setProfileName() itemList[0] = userAppHistoryViewModel topicListAdapter.notifyItemChanged(0) }) @@ -125,6 +160,12 @@ class HomeFragmentPresenter @Inject constructor( return appHistoryResult.getOrDefault(UserAppHistory.getDefaultInstance()) } + private fun setProfileName() { + if (::userAppHistoryViewModel.isInitialized && ::profileName.isInitialized) { + userAppHistoryViewModel.profileName = "$profileName!" + } + } + private val ongoingStoryListSummaryResultLiveData: LiveData> by lazy { topicListController.getOngoingStoryList() } diff --git a/app/src/main/java/org/oppia/app/home/UserAppHistoryViewModel.kt b/app/src/main/java/org/oppia/app/home/UserAppHistoryViewModel.kt index 6b43dd7787f..b4012f329dc 100644 --- a/app/src/main/java/org/oppia/app/home/UserAppHistoryViewModel.kt +++ b/app/src/main/java/org/oppia/app/home/UserAppHistoryViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel /** [ViewModel] for user app usage history. */ class UserAppHistoryViewModel : HomeItemViewModel() { var isAppAlreadyOpened = ObservableField(false) + var profileName : String = "" fun setAlreadyAppOpened(alreadyOpenedApp: Boolean) = isAppAlreadyOpened.set(alreadyOpenedApp) } diff --git a/app/src/main/java/org/oppia/app/profile/PinPasswordActivityPresenter.kt b/app/src/main/java/org/oppia/app/profile/PinPasswordActivityPresenter.kt index d230cf6bf70..1564d58112d 100644 --- a/app/src/main/java/org/oppia/app/profile/PinPasswordActivityPresenter.kt +++ b/app/src/main/java/org/oppia/app/profile/PinPasswordActivityPresenter.kt @@ -64,7 +64,7 @@ class PinPasswordActivityPresenter @Inject constructor( profileManagementController.loginToProfile(ProfileId.newBuilder().setInternalId(profileId).build()) .observe(activity, Observer { if (it.isSuccess()) { - activity.startActivity(Intent(activity, HomeActivity::class.java)) + activity.startActivity((HomeActivity.createHomeActivity(activity, profileId))) } }) } else { diff --git a/app/src/main/java/org/oppia/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/app/profile/ProfileChooserFragmentPresenter.kt index 926a6738eb5..31c1ab0dc56 100644 --- a/app/src/main/java/org/oppia/app/profile/ProfileChooserFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/app/profile/ProfileChooserFragmentPresenter.kt @@ -1,7 +1,6 @@ package org.oppia.app.profile import android.content.Context -import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -112,7 +111,7 @@ class ProfileChooserFragmentPresenter @Inject constructor( if (model.profile.pin.isEmpty()) { profileManagementController.loginToProfile(model.profile.id).observe(fragment, Observer { if (it.isSuccess()) { - activity.startActivity(Intent(fragment.context, HomeActivity::class.java)) + activity.startActivity((HomeActivity.createHomeActivity(activity, model.profile.id.internalId))) } }) } else { diff --git a/app/src/main/res/layout/welcome.xml b/app/src/main/res/layout/welcome.xml index 5d20dd6ccd7..87b37c36a0a 100644 --- a/app/src/main/res/layout/welcome.xml +++ b/app/src/main/res/layout/welcome.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:profile="http://schemas.android.com/tools"> @@ -18,24 +19,33 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="28dp" - android:layout_marginEnd="28dp" - android:layout_marginBottom="8dp" android:fontFamily="sans-serif" + android:textColor="@color/oppiaPrimaryText" + android:textSize="24sp" android:text="@{viewModel.isAppAlreadyOpened().get() ? @string/welcome_back_text : @string/welcome_text}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/app/src/sharedTest/java/org/oppia/app/home/HomeActivityTest.kt b/app/src/sharedTest/java/org/oppia/app/home/HomeActivityTest.kt index 706d7dd1e31..8f1b607211c 100644 --- a/app/src/sharedTest/java/org/oppia/app/home/HomeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/app/home/HomeActivityTest.kt @@ -2,6 +2,7 @@ package org.oppia.app.home import android.app.Application import android.content.Context +import android.content.Intent import android.os.Handler import android.os.Looper import android.view.View @@ -24,8 +25,8 @@ import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -37,6 +38,7 @@ import dagger.Component import dagger.Module import dagger.Provides import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asCoroutineDispatcher import org.hamcrest.CoreMatchers.containsString import org.hamcrest.Matcher @@ -54,9 +56,11 @@ import org.oppia.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView import org.oppia.app.topic.TopicActivity import org.oppia.app.utility.OrientationChangeAction.Companion.orientationLandscape import org.oppia.domain.UserAppHistoryController +import org.oppia.domain.profile.ProfileManagementController +import org.oppia.domain.profile.ProfileTestHelper +import org.oppia.domain.topic.FRACTIONS_STORY_ID_0 import org.oppia.domain.topic.FRACTIONS_TOPIC_ID import org.oppia.domain.topic.TEST_TOPIC_ID_0 -import org.oppia.domain.topic.FRACTIONS_STORY_ID_0 import org.oppia.util.logging.EnableConsoleLog import org.oppia.util.logging.EnableFileLog import org.oppia.util.logging.GlobalLogLevel @@ -66,17 +70,25 @@ import org.oppia.util.threading.BlockingDispatcher import java.util.concurrent.AbstractExecutorService import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import javax.inject.Inject import javax.inject.Singleton /** Tests for [HomeActivity]. */ @RunWith(AndroidJUnit4::class) class HomeActivityTest { + @Inject lateinit var profileTestHelper: ProfileTestHelper + @Inject + lateinit var context: Context + @Before + @ExperimentalCoroutinesApi fun setUp() { Intents.init() + setUpTestApplicationComponent() IdlingRegistry.getInstance().register(MainThreadExecutor.countingResource) simulateNewAppInstance() + profileTestHelper.initializeProfiles() } @After @@ -85,6 +97,26 @@ class HomeActivityTest { Intents.release() } + private fun setUpTestApplicationComponent() { + DaggerHomeActivityTest_TestApplicationComponent.builder() + .setApplication(ApplicationProvider.getApplicationContext()) + .build() + .inject(this) + } + + @Test + fun testHomeActivity_recyclerViewIndex0_withProfileId0_displayProfileName_profileNameDisplayedSuccessfully() { + launch(createHomeActivityIntent(0)).use { + onView( + atPositionOnView( + R.id.home_recycler_view, + 0, + R.id.profile_name_textview + ) + ).check(matches(withText("Sean!"))) + } + } + @Test fun testHomeActivity_firstOpen_hasWelcomeString() { launch(HomeActivity::class.java).use { @@ -145,7 +177,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_displaysRecentlyPlayedStoriesText() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.recently_played_stories_text_view)).check( matches( @@ -157,7 +189,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_displaysViewAllText() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.view_all_text_view)).check( matches( @@ -169,7 +201,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_clickViewAll_opensContinuePlayingActivity() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.view_all_text_view)).perform(click()) intended(hasComponent(ContinuePlayingActivity::class.java.name)) @@ -178,7 +210,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_promotedCard_chapterNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView( Matchers.allOf( withId(R.id.promoted_story_list_recycler_view), @@ -192,7 +224,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_promotedCard_storyNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.story_name_text_view)).check( matches( @@ -205,7 +237,7 @@ class HomeActivityTest { @Test @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported. fun testHomeActivity_recyclerViewIndex1_configurationChange_promotedCard_storyNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(isRoot()).perform(orientationLandscape()) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.story_name_text_view)).check( @@ -218,7 +250,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_clickPromotedStory_opensTopicActivity() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView( Matchers.allOf( @@ -236,7 +268,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex1_promotedCard_topicNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(1)) onView(atPositionOnView(R.id.home_recycler_view, 1, R.id.topic_name_text_view)).check( matches( @@ -248,7 +280,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex3_topicSummary_topicNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(3)) onView(atPositionOnView(R.id.home_recycler_view, 3, R.id.topic_name_text_view)).check( matches( @@ -260,7 +292,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex3_topicSummary_lessonCountIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(3)) onView(atPositionOnView(R.id.home_recycler_view, 3, R.id.lesson_count_text_view)).check( matches( @@ -272,7 +304,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex4_topicSummary_topicNameIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(4)) onView(atPositionOnView(R.id.home_recycler_view, 4, R.id.topic_name_text_view)).check( matches( @@ -284,7 +316,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex4_topicSummary_lessonCountIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(4)) onView(atPositionOnView(R.id.home_recycler_view, 4, R.id.lesson_count_text_view)).check( matches( @@ -297,7 +329,7 @@ class HomeActivityTest { @Test @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported. fun testHomeActivity_recyclerViewIndex4_topicSummary_configurationChange_lessonCountIsCorrect() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(isRoot()).perform(orientationLandscape()) onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(4)) onView(atPositionOnView(R.id.home_recycler_view, 4, R.id.lesson_count_text_view)).check( @@ -310,7 +342,7 @@ class HomeActivityTest { @Test fun testHomeActivity_recyclerViewIndex3_clickTopicSummary_opensTopicActivity() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { onView(withId(R.id.home_recycler_view)).perform(scrollToPosition(3)) onView(atPosition(R.id.home_recycler_view, 3)).perform(click()) intended(hasComponent(TopicActivity::class.java.name)) @@ -320,7 +352,7 @@ class HomeActivityTest { @Test fun testHomeActivity_onBackPressed_showsExitToProfileChooserDialog() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { pressBack() onView(withText(R.string.home_activity_back_dialog_message)).check(matches(isDisplayed())) } @@ -328,13 +360,17 @@ class HomeActivityTest { @Test fun testHomeActivity_onBackPressed_clickExit_checkOpensProfileActivity() { - launch(HomeActivity::class.java).use { + launch(createHomeActivityIntent(0)).use { pressBack() onView(withText(R.string.home_activity_back_dialog_exit)).perform(click()) intended(hasComponent(ProfileActivity::class.java.name)) } } + private fun createHomeActivityIntent(profileId: Int): Intent { + return HomeActivity.createHomeActivity(ApplicationProvider.getApplicationContext(), profileId) + } + private fun simulateNewAppInstance() { // Simulate a fresh app install by clearing any potential on-disk caches using an isolated app history controller. createTestRootComponent().getUserAppHistoryController().clearUserAppHistory() @@ -452,8 +488,9 @@ class HomeActivityTest { fun build(): TestApplicationComponent } - fun getUserAppHistoryController(): UserAppHistoryController + fun getProfileManagementController(): ProfileManagementController + fun inject(homeActivityTest: HomeActivityTest) } // TODO(#59): Move this to a general-purpose testing library that replaces all CoroutineExecutors with an diff --git a/gradlew b/gradlew old mode 100644 new mode 100755