diff --git a/BUILD.bazel b/BUILD.bazel
index 5eb1f397a91..f0db399e20c 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -124,7 +124,7 @@ package_group(
{
"flavor": "oppia_kitkat",
"main_dex_list": "//:config/kitkat_main_dex_class_list.txt",
- "min_sdk_version": 19,
+ "min_sdk_version": 21,
"multidex": "manual_main_dex",
"target_sdk_version": 33,
},
diff --git a/app/BUILD.bazel b/app/BUILD.bazel
index 0ac39dc4ebf..7ddcebe5300 100644
--- a/app/BUILD.bazel
+++ b/app/BUILD.bazel
@@ -602,6 +602,7 @@ android_library(
"//model/src/main/proto:thumbnail_java_proto_lite",
"//model/src/main/proto:version_java_proto_lite",
"//third_party:androidx_annotation_annotation",
+ "//third_party:androidx_compose_ui_ui",
"//third_party:androidx_constraintlayout_constraintlayout",
"//third_party:androidx_core_core",
"//third_party:androidx_databinding_databinding-adapters",
@@ -763,6 +764,7 @@ kt_android_library(
custom_package = "org.oppia.android.app.ui",
enable_data_binding = 1,
manifest = "src/main/AppAndroidManifest.xml",
+ plugins = ["//tools/kotlin:jetpack_compose_compiler_plugin"],
visibility = ["//visibility:public"],
deps = [
":binding_adapters",
@@ -787,6 +789,15 @@ kt_android_library(
"//domain/src/main/java/org/oppia/android/domain/survey:gating_controller",
"//domain/src/main/java/org/oppia/android/domain/survey:survey_controller",
"//model/src/main/proto:arguments_java_proto_lite",
+ "//third_party:androidx_activity_activity-compose",
+ "//third_party:androidx_annotation_annotation",
+ "//third_party:androidx_appcompat_appcompat",
+ "//third_party:androidx_compose_foundation_foundation",
+ "//third_party:androidx_compose_foundation_foundation-layout",
+ "//third_party:androidx_compose_material_material",
+ "//third_party:androidx_compose_runtime_runtime",
+ "//third_party:androidx_compose_ui_ui",
+ "//third_party:androidx_core_core-ktx",
"//third_party:androidx_databinding_databinding-adapters",
"//third_party:androidx_databinding_databinding-common",
"//third_party:androidx_databinding_databinding-runtime",
@@ -800,6 +811,7 @@ kt_android_library(
"//third_party:com_github_takusemba_spotlight",
"//third_party:com_google_android_flexbox_flexbox",
"//third_party:javax_annotation_javax_annotation-api_jar",
+ "//third_party:org_jetbrains_kotlin_kotlin-stdlib-jdk8_jar",
"//utility",
"//utility/src/main/java/org/oppia/android/util/extensions:bundle_extensions",
"//utility/src/main/java/org/oppia/android/util/parser/image:image_loader",
@@ -898,6 +910,7 @@ TEST_DEPS = [
"//testing/src/main/java/org/oppia/android/testing/threading:test_module",
"//testing/src/main/java/org/oppia/android/testing/time:test_module",
"//third_party:androidx_annotation_annotation",
+ "//third_party:androidx_compose_ui_ui-test-junit4",
"//third_party:androidx_core_core",
"//third_party:androidx_databinding_databinding-adapters",
"//third_party:androidx_databinding_databinding-common",
@@ -956,7 +969,7 @@ MIGRATED_TESTS = [
filtered_tests = MIGRATED_TESTS,
manifest_values = {
"applicationId": "org.oppia.android",
- "minSdkVersion": "19",
+ "minSdkVersion": "21",
"targetSdkVersion": "30",
"versionCode": "0",
"versionName": "0.1-alpha",
@@ -972,7 +985,7 @@ MIGRATED_TESTS = [
filtered_tests = MIGRATED_TESTS,
manifest_values = {
"applicationId": "org.oppia.android",
- "minSdkVersion": "19",
+ "minSdkVersion": "21",
"targetSdkVersion": "30",
"versionCode": "0",
"versionName": "0.1-alpha",
diff --git a/app/build.gradle b/app/build.gradle
index 05fb3d39c25..13fae2de07a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,10 +6,10 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 33
- buildToolsVersion "29.0.2"
+ buildToolsVersion "30.0.2"
defaultConfig {
applicationId "org.oppia.android"
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -30,6 +30,15 @@ android {
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
+ useIR = true
+ freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerVersion kotlin_version
}
buildTypes {
release {
@@ -147,6 +156,13 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(
'androidx.appcompat:appcompat:1.0.2',
+ "androidx.compose.foundation:foundation:$compose_version",
+ "androidx.compose.foundation:foundation-layout:$compose_version",
+ "androidx.compose.material:material:$compose_version",
+ "androidx.compose.runtime:runtime:$compose_version",
+ "androidx.compose.runtime:runtime-livedata:$compose_version",
+ "androidx.compose.ui:ui:$compose_version",
+ "androidx.compose.ui:ui-tooling:$compose_version",
'androidx.constraintlayout:constraintlayout:1.1.3',
'androidx.core:core-ktx:1.0.2',
'androidx.legacy:legacy-support-v4:1.0.0',
@@ -164,6 +180,7 @@ dependencies {
'com.github.bumptech.glide:glide:4.11.0',
'com.google.android.flexbox:flexbox:3.0.0',
'com.google.android.material:material:1.3.0',
+ "com.google.android.material:compose-theme-adapter:$compose_version",
'com.google.dagger:dagger:2.41',
'com.google.firebase:firebase-analytics:17.5.0',
'com.google.firebase:firebase-analytics-ktx:17.5.0',
@@ -189,6 +206,7 @@ dependencies {
'org.glassfish.jaxb:jaxb-runtime:2.3.2',
)
testImplementation(
+ "androidx.compose.ui:ui-test-junit4:$compose_version",
'androidx.test:core:1.2.0',
'androidx.test.espresso:espresso-contrib:3.1.0',
'androidx.test.espresso:espresso-core:3.2.0',
@@ -207,6 +225,7 @@ dependencies {
project(":testing"),
)
androidTestImplementation(
+ "androidx.compose.ui:ui-test-junit4:$compose_version",
'androidx.test:core:1.2.0',
'androidx.test.espresso:espresso-contrib:3.1.0',
'androidx.test.espresso:espresso-core:3.2.0',
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b85115c35a1..235bb4a479f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -332,11 +332,19 @@
+
+
diff --git a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
index 36ee8d8c1fd..60d24efbd8e 100644
--- a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
+++ b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
@@ -6,6 +6,7 @@ import dagger.Subcomponent
import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity
import org.oppia.android.app.administratorcontrols.appversion.AppVersionActivity
import org.oppia.android.app.administratorcontrols.learneranalytics.ProfileAndDeviceIdActivity
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.completedstorylist.CompletedStoryListActivity
import org.oppia.android.app.devoptions.DeveloperOptionsActivity
import org.oppia.android.app.devoptions.forcenetworktype.ForceNetworkTypeActivity
@@ -218,4 +219,5 @@ interface ActivityComponentImpl :
fun inject(walkthroughActivity: WalkthroughActivity)
fun inject(surveyActivity: SurveyActivity)
fun inject(colorBindingAdaptersTestActivity: ColorBindingAdaptersTestActivity)
+ fun inject(classroomListActivity: ClassroomListActivity)
}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt
new file mode 100644
index 00000000000..4ba06da7d58
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt
@@ -0,0 +1,115 @@
+package org.oppia.android.app.classroom
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponentImpl
+import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
+import org.oppia.android.app.activity.route.ActivityRouter
+import org.oppia.android.app.drawer.ExitProfileDialogFragment
+import org.oppia.android.app.drawer.TAG_SWITCH_PROFILE_DIALOG
+import org.oppia.android.app.home.RouteToRecentlyPlayedListener
+import org.oppia.android.app.home.RouteToTopicListener
+import org.oppia.android.app.home.RouteToTopicPlayStoryListener
+import org.oppia.android.app.model.DestinationScreen
+import org.oppia.android.app.model.ExitProfileDialogArguments
+import org.oppia.android.app.model.HighlightItem
+import org.oppia.android.app.model.ProfileId
+import org.oppia.android.app.model.RecentlyPlayedActivityParams
+import org.oppia.android.app.model.RecentlyPlayedActivityTitle
+import org.oppia.android.app.model.ScreenName.CLASSROOM_LIST_ACTIVITY
+import org.oppia.android.app.topic.TopicActivity
+import org.oppia.android.app.translation.AppLanguageResourceHandler
+import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
+import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.decorateWithUserProfileId
+import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
+import javax.inject.Inject
+
+/** The activity for displaying [ClassroomListFragment]. */
+class ClassroomListActivity :
+ InjectableAutoLocalizedAppCompatActivity(),
+ RouteToTopicListener,
+ RouteToTopicPlayStoryListener,
+ RouteToRecentlyPlayedListener {
+ @Inject
+ lateinit var classroomListActivityPresenter: ClassroomListActivityPresenter
+
+ @Inject
+ lateinit var resourceHandler: AppLanguageResourceHandler
+
+ @Inject
+ lateinit var activityRouter: ActivityRouter
+
+ private var internalProfileId: Int = -1
+
+ companion object {
+ /** Returns a new [Intent] to route to [ClassroomListActivity] for a specified [profileId]. */
+ fun createClassroomListActivity(context: Context, profileId: ProfileId?): Intent {
+ return Intent(context, ClassroomListActivity::class.java).apply {
+ decorateWithScreenName(CLASSROOM_LIST_ACTIVITY)
+ profileId?.let { decorateWithUserProfileId(profileId) }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ (activityComponent as ActivityComponentImpl).inject(this)
+
+ internalProfileId = intent.extractCurrentUserProfileId().internalId
+ classroomListActivityPresenter.handleOnCreate()
+ title = resourceHandler.getStringInLocale(R.string.classroom_list_activity_title)
+ }
+
+ override fun onRestart() {
+ super.onRestart()
+ classroomListActivityPresenter.handleOnRestart()
+ }
+
+ override fun onBackPressed() {
+ val previousFragment =
+ supportFragmentManager.findFragmentByTag(TAG_SWITCH_PROFILE_DIALOG)
+ if (previousFragment != null) {
+ supportFragmentManager.beginTransaction().remove(previousFragment).commitNow()
+ }
+ val exitProfileDialogArguments =
+ ExitProfileDialogArguments
+ .newBuilder()
+ .setHighlightItem(HighlightItem.NONE)
+ .build()
+ val dialogFragment = ExitProfileDialogFragment
+ .newInstance(exitProfileDialogArguments = exitProfileDialogArguments)
+ dialogFragment.showNow(supportFragmentManager, TAG_SWITCH_PROFILE_DIALOG)
+ }
+
+ override fun routeToRecentlyPlayed(recentlyPlayedActivityTitle: RecentlyPlayedActivityTitle) {
+ val recentlyPlayedActivityParams =
+ RecentlyPlayedActivityParams
+ .newBuilder()
+ .setProfileId(ProfileId.newBuilder().setInternalId(internalProfileId).build())
+ .setActivityTitle(recentlyPlayedActivityTitle).build()
+
+ activityRouter.routeToScreen(
+ DestinationScreen
+ .newBuilder()
+ .setRecentlyPlayedActivityParams(recentlyPlayedActivityParams)
+ .build()
+ )
+ }
+
+ override fun routeToTopic(internalProfileId: Int, topicId: String) {
+ startActivity(TopicActivity.createTopicActivityIntent(this, internalProfileId, topicId))
+ }
+
+ override fun routeToTopicPlayStory(internalProfileId: Int, topicId: String, storyId: String) {
+ startActivity(
+ TopicActivity.createTopicPlayStoryActivityIntent(
+ this,
+ internalProfileId,
+ topicId,
+ storyId
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt
new file mode 100644
index 00000000000..ea6a0d8df3f
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt
@@ -0,0 +1,59 @@
+package org.oppia.android.app.classroom
+
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.drawerlayout.widget.DrawerLayout
+import org.oppia.android.R
+import org.oppia.android.app.drawer.NavigationDrawerFragment
+import javax.inject.Inject
+
+/** Tag for identifying the [ClassroomListFragment] in transactions. */
+private const val TAG_CLASSROOM_LIST_FRAGMENT = "CLASSROOM_LIST_FRAGMENT"
+
+/** The presenter for [ClassroomListActivity]. */
+class ClassroomListActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
+ private var navigationDrawerFragment: NavigationDrawerFragment? = null
+
+ /**
+ * Handles the creation of the activity. Sets the content view, sets up the navigation drawer,
+ * and adds the [ClassroomListFragment] if it's not already added.
+ */
+ fun handleOnCreate() {
+ activity.setContentView(R.layout.classroom_list_activity)
+ setUpNavigationDrawer()
+ if (getClassroomListFragment() == null) {
+ activity.supportFragmentManager.beginTransaction().add(
+ R.id.classroom_list_fragment_placeholder,
+ ClassroomListFragment(),
+ TAG_CLASSROOM_LIST_FRAGMENT
+ ).commitNow()
+ }
+ }
+
+ /** Handles the activity restart. Re-initializes the navigation drawer. */
+ fun handleOnRestart() {
+ setUpNavigationDrawer()
+ }
+
+ private fun setUpNavigationDrawer() {
+ val toolbar = activity.findViewById(R.id.classroom_list_activity_toolbar) as Toolbar
+ activity.setSupportActionBar(toolbar)
+ activity.supportActionBar!!.setDisplayShowHomeEnabled(true)
+ navigationDrawerFragment = activity
+ .supportFragmentManager
+ .findFragmentById(
+ R.id.classroom_list_activity_fragment_navigation_drawer
+ ) as NavigationDrawerFragment
+ navigationDrawerFragment!!.setUpDrawer(
+ activity.findViewById(R.id.classroom_list_activity_drawer_layout) as DrawerLayout,
+ toolbar, R.id.nav_home
+ )
+ }
+
+ private fun getClassroomListFragment(): ClassroomListFragment? {
+ return activity.supportFragmentManager.findFragmentById(
+ R.id.classroom_list_fragment_placeholder
+ ) as ClassroomListFragment?
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragment.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragment.kt
new file mode 100644
index 00000000000..172665fd74f
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragment.kt
@@ -0,0 +1,44 @@
+package org.oppia.android.app.classroom
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import org.oppia.android.app.fragment.FragmentComponentImpl
+import org.oppia.android.app.fragment.InjectableFragment
+import org.oppia.android.app.home.classroomlist.ClassroomSummaryClickListener
+import org.oppia.android.app.home.topiclist.TopicSummaryClickListener
+import org.oppia.android.app.model.ClassroomSummary
+import org.oppia.android.app.model.TopicSummary
+import javax.inject.Inject
+
+/** Fragment that displays the classroom list screen. */
+class ClassroomListFragment :
+ InjectableFragment(),
+ TopicSummaryClickListener,
+ ClassroomSummaryClickListener {
+ @Inject
+ lateinit var classroomListFragmentPresenter: ClassroomListFragmentPresenter
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ (fragmentComponent as FragmentComponentImpl).inject(this)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return classroomListFragmentPresenter.handleCreateView(inflater, container)
+ }
+
+ override fun onTopicSummaryClicked(topicSummary: TopicSummary) {
+ classroomListFragmentPresenter.onTopicSummaryClicked(topicSummary)
+ }
+
+ override fun onClassroomSummaryClicked(classroomSummary: ClassroomSummary) {
+ classroomListFragmentPresenter.onClassroomSummaryClicked(classroomSummary)
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragmentPresenter.kt
new file mode 100644
index 00000000000..7bc81fd5bdf
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragmentPresenter.kt
@@ -0,0 +1,336 @@
+package org.oppia.android.app.classroom
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.dp
+import androidx.databinding.ObservableList
+import androidx.fragment.app.Fragment
+import org.oppia.android.R
+import org.oppia.android.app.classroom.classroomlist.ClassroomList
+import org.oppia.android.app.classroom.promotedlist.PromotedStoryList
+import org.oppia.android.app.classroom.topiclist.AllTopicsHeaderText
+import org.oppia.android.app.classroom.topiclist.TopicCard
+import org.oppia.android.app.classroom.welcome.WelcomeText
+import org.oppia.android.app.home.HomeItemViewModel
+import org.oppia.android.app.home.RouteToTopicPlayStoryListener
+import org.oppia.android.app.home.WelcomeViewModel
+import org.oppia.android.app.home.classroomlist.ClassroomSummaryViewModel
+import org.oppia.android.app.home.promotedlist.PromotedStoryListViewModel
+import org.oppia.android.app.home.topiclist.AllTopicsViewModel
+import org.oppia.android.app.home.topiclist.TopicSummaryViewModel
+import org.oppia.android.app.model.ClassroomSummary
+import org.oppia.android.app.model.LessonThumbnail
+import org.oppia.android.app.model.LessonThumbnailGraphic
+import org.oppia.android.app.model.TopicSummary
+import org.oppia.android.app.translation.AppLanguageResourceHandler
+import org.oppia.android.app.utility.datetime.DateTimeUtil
+import org.oppia.android.databinding.ClassroomListFragmentBinding
+import org.oppia.android.domain.classroom.ClassroomController
+import org.oppia.android.domain.oppialogger.OppiaLogger
+import org.oppia.android.domain.profile.ProfileManagementController
+import org.oppia.android.domain.topic.TopicListController
+import org.oppia.android.domain.translation.TranslationController
+import org.oppia.android.util.locale.OppiaLocale
+import org.oppia.android.util.parser.html.StoryHtmlParserEntityType
+import org.oppia.android.util.parser.html.TopicHtmlParserEntityType
+import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
+import javax.inject.Inject
+
+/** Test tag for the classroom list screen. */
+const val CLASSROOM_LIST_SCREEN_TEST_TAG = "TEST_TAG.classroom_list_screen"
+
+/** The presenter for [ClassroomListFragment]. */
+class ClassroomListFragmentPresenter @Inject constructor(
+ private val activity: AppCompatActivity,
+ private val fragment: Fragment,
+ private val profileManagementController: ProfileManagementController,
+ private val topicListController: TopicListController,
+ private val classroomController: ClassroomController,
+ private val oppiaLogger: OppiaLogger,
+ @TopicHtmlParserEntityType private val topicEntityType: String,
+ @StoryHtmlParserEntityType private val storyEntityType: String,
+ private val resourceHandler: AppLanguageResourceHandler,
+ private val dateTimeUtil: DateTimeUtil,
+ private val translationController: TranslationController,
+ private val machineLocale: OppiaLocale.MachineLocale,
+) {
+ private val routeToTopicPlayStoryListener = activity as RouteToTopicPlayStoryListener
+ private lateinit var binding: ClassroomListFragmentBinding
+ private lateinit var classroomListViewModel: ClassroomListViewModel
+ private var internalProfileId: Int = -1
+ private val profileId = activity.intent.extractCurrentUserProfileId()
+
+ /** Creates and returns the view for the [ClassroomListFragment]. */
+ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {
+ binding = ClassroomListFragmentBinding.inflate(
+ inflater,
+ container,
+ /* attachToRoot= */ false
+ )
+
+ internalProfileId = profileId.internalId
+
+ classroomListViewModel = ClassroomListViewModel(
+ activity,
+ fragment,
+ oppiaLogger,
+ internalProfileId,
+ profileManagementController,
+ topicListController,
+ classroomController,
+ topicEntityType,
+ storyEntityType,
+ resourceHandler,
+ dateTimeUtil,
+ translationController
+ )
+
+ classroomListViewModel.homeItemViewModelListLiveData.observe(activity) {
+ refreshComposeView()
+ }
+
+ classroomListViewModel.topicList.addOnListChangedCallback(
+ object : ObservableList.OnListChangedCallback>() {
+ override fun onChanged(sender: ObservableList) {}
+
+ override fun onItemRangeChanged(
+ sender: ObservableList,
+ positionStart: Int,
+ itemCount: Int
+ ) {}
+
+ override fun onItemRangeInserted(
+ sender: ObservableList,
+ positionStart: Int,
+ itemCount: Int
+ ) {
+ refreshComposeView()
+ }
+
+ override fun onItemRangeMoved(
+ sender: ObservableList,
+ fromPosition: Int,
+ toPosition: Int,
+ itemCount: Int
+ ) {}
+
+ override fun onItemRangeRemoved(
+ sender: ObservableList,
+ positionStart: Int,
+ itemCount: Int
+ ) {}
+ }
+ )
+
+ return binding.root
+ }
+
+ /** Routes to the play story view for the first story in the given topic summary. */
+ fun onTopicSummaryClicked(topicSummary: TopicSummary) {
+ routeToTopicPlayStoryListener.routeToTopicPlayStory(
+ internalProfileId,
+ topicSummary.topicId,
+ topicSummary.firstStoryId
+ )
+ }
+
+ /** Triggers the view model to update the topic list. */
+ fun onClassroomSummaryClicked(classroomSummary: ClassroomSummary) {
+ val classroomId = classroomSummary.classroomId
+ profileManagementController.updateLastSelectedClassroomId(profileId, classroomId)
+ classroomListViewModel.fetchAndUpdateTopicList(classroomId)
+ }
+
+ private fun refreshComposeView() {
+ binding.composeView.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ MaterialTheme {
+ ClassroomListScreen()
+ }
+ }
+ }
+ }
+
+ /** Display a list of classroom-related items grouped by their types. */
+ @OptIn(ExperimentalFoundationApi::class)
+ @Composable
+ fun ClassroomListScreen() {
+ val groupedItems = classroomListViewModel.homeItemViewModelListLiveData.value
+ ?.plus(classroomListViewModel.topicList)
+ ?.groupBy { it::class }
+ val topicListSpanCount = integerResource(id = R.integer.home_span_count)
+ LazyColumn(
+ modifier = Modifier.testTag(CLASSROOM_LIST_SCREEN_TEST_TAG)
+ ) {
+ groupedItems?.forEach { (type, items) ->
+ when (type) {
+ WelcomeViewModel::class -> items.forEach { item ->
+ item {
+ WelcomeText(welcomeViewModel = item as WelcomeViewModel)
+ }
+ }
+ PromotedStoryListViewModel::class -> items.forEach { item ->
+ item {
+ PromotedStoryList(
+ promotedStoryListViewModel = item as PromotedStoryListViewModel,
+ machineLocale = machineLocale
+ )
+ }
+ }
+ ClassroomSummaryViewModel::class -> stickyHeader {
+ ClassroomList(
+ classroomSummaryList = items.map { it as ClassroomSummaryViewModel },
+ classroomListViewModel.selectedClassroomId.get() ?: ""
+ )
+ }
+ AllTopicsViewModel::class -> items.forEach { _ ->
+ item {
+ AllTopicsHeaderText()
+ }
+ }
+ TopicSummaryViewModel::class -> {
+ gridItems(
+ data = items.map { it as TopicSummaryViewModel },
+ columnCount = topicListSpanCount,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ ) { itemData ->
+ TopicCard(topicSummaryViewModel = itemData)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/** Adds a grid of items to a LazyListScope with specified arrangement and item content. */
+fun LazyListScope.gridItems(
+ data: List,
+ columnCount: Int,
+ modifier: Modifier,
+ horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+ itemContent: @Composable BoxScope.(T) -> Unit,
+) {
+ val size = data.count()
+ // Calculate the number of rows needed.
+ val rows = if (size == 0) 0 else (size + columnCount - 1) / columnCount
+
+ // Generate items in the LazyList.
+ items(rows, key = { it }) { rowIndex ->
+ // Create a row with the specified horizontal arrangement and padding.
+ Row(
+ horizontalArrangement = horizontalArrangement,
+ modifier = modifier
+ .background(
+ colorResource(id = R.color.component_color_classroom_topic_list_background_color)
+ )
+ .padding(
+ horizontal = dimensionResource(id = R.dimen.classrooms_text_margin_start),
+ vertical = 10.dp
+ )
+ ) {
+ // Populate the row with columns.
+ for (columnIndex in 0 until columnCount) {
+ val itemIndex = rowIndex * columnCount + columnIndex
+ if (itemIndex < size) {
+ Box(
+ modifier = Modifier.weight(1F, fill = true),
+ propagateMinConstraints = true
+ ) {
+ itemContent(data[itemIndex]) // Provide content for each item.
+ }
+ } else {
+ Spacer(Modifier.weight(1F, fill = true)) // Add spacer if no more items.
+ }
+ }
+ }
+
+ // Add bottom padding if it's the last row.
+ if (rowIndex == rows - 1) {
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(dimensionResource(id = R.dimen.home_fragment_padding_bottom))
+ .background(
+ colorResource(id = R.color.component_color_classroom_topic_list_background_color)
+ )
+ )
+ }
+ }
+}
+
+/** Retrieves the drawable resource ID for the lesson thumbnail based on its graphic type. */
+fun LessonThumbnail.getDrawableResource(): Int {
+ return when (thumbnailGraphic) {
+ LessonThumbnailGraphic.BAKER ->
+ R.drawable.lesson_thumbnail_graphic_baker
+ LessonThumbnailGraphic.CHILD_WITH_BOOK ->
+ R.drawable.lesson_thumbnail_graphic_child_with_book
+ LessonThumbnailGraphic.CHILD_WITH_CUPCAKES ->
+ R.drawable.lesson_thumbnail_graphic_child_with_cupcakes
+ LessonThumbnailGraphic.CHILD_WITH_FRACTIONS_HOMEWORK ->
+ R.drawable.lesson_thumbnail_graphic_child_with_fractions_homework
+ LessonThumbnailGraphic.DUCK_AND_CHICKEN ->
+ R.drawable.lesson_thumbnail_graphic_duck_and_chicken
+ LessonThumbnailGraphic.PERSON_WITH_PIE_CHART ->
+ R.drawable.lesson_thumbnail_graphic_person_with_pie_chart
+ LessonThumbnailGraphic.IDENTIFYING_THE_PARTS_OF_A_FRACTION ->
+ R.drawable.topic_fractions_01
+ LessonThumbnailGraphic.WRITING_FRACTIONS ->
+ R.drawable.topic_fractions_02
+ LessonThumbnailGraphic.EQUIVALENT_FRACTIONS ->
+ R.drawable.topic_fractions_03
+ LessonThumbnailGraphic.MIXED_NUMBERS_AND_IMPROPER_FRACTIONS ->
+ R.drawable.topic_fractions_04
+ LessonThumbnailGraphic.COMPARING_FRACTIONS ->
+ R.drawable.topic_fractions_05
+ LessonThumbnailGraphic.ADDING_AND_SUBTRACTING_FRACTIONS ->
+ R.drawable.topic_fractions_06
+ LessonThumbnailGraphic.MULTIPLYING_FRACTIONS ->
+ R.drawable.topic_fractions_07
+ LessonThumbnailGraphic.DIVIDING_FRACTIONS ->
+ R.drawable.topic_fractions_08
+ LessonThumbnailGraphic.DERIVE_A_RATIO ->
+ R.drawable.topic_ratios_01
+ LessonThumbnailGraphic.WHAT_IS_A_FRACTION ->
+ R.drawable.topic_fractions_01
+ LessonThumbnailGraphic.FRACTION_OF_A_GROUP ->
+ R.drawable.topic_fractions_02
+ LessonThumbnailGraphic.ADDING_FRACTIONS ->
+ R.drawable.topic_fractions_03
+ LessonThumbnailGraphic.MIXED_NUMBERS ->
+ R.drawable.topic_fractions_04
+ LessonThumbnailGraphic.SCIENCE_CLASSROOM ->
+ R.drawable.ic_science
+ LessonThumbnailGraphic.MATHS_CLASSROOM ->
+ R.drawable.ic_maths
+ LessonThumbnailGraphic.ENGLISH_CLASSROOM ->
+ R.drawable.ic_english
+ else ->
+ R.drawable.topic_fractions_01
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListViewModel.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListViewModel.kt
new file mode 100644
index 00000000000..df1b5cd8f13
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListViewModel.kt
@@ -0,0 +1,359 @@
+package org.oppia.android.app.classroom
+
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.ObservableArrayList
+import androidx.databinding.ObservableField
+import androidx.databinding.ObservableList
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
+import org.oppia.android.R
+import org.oppia.android.app.home.HomeItemViewModel
+import org.oppia.android.app.home.WelcomeViewModel
+import org.oppia.android.app.home.classroomlist.ClassroomSummaryClickListener
+import org.oppia.android.app.home.classroomlist.ClassroomSummaryViewModel
+import org.oppia.android.app.home.promotedlist.ComingSoonTopicListViewModel
+import org.oppia.android.app.home.promotedlist.ComingSoonTopicsViewModel
+import org.oppia.android.app.home.promotedlist.PromotedStoryListViewModel
+import org.oppia.android.app.home.promotedlist.PromotedStoryViewModel
+import org.oppia.android.app.home.topiclist.AllTopicsViewModel
+import org.oppia.android.app.home.topiclist.TopicSummaryClickListener
+import org.oppia.android.app.home.topiclist.TopicSummaryViewModel
+import org.oppia.android.app.model.ClassroomList
+import org.oppia.android.app.model.ComingSoonTopicList
+import org.oppia.android.app.model.Profile
+import org.oppia.android.app.model.ProfileId
+import org.oppia.android.app.model.PromotedActivityList
+import org.oppia.android.app.model.PromotedStoryList
+import org.oppia.android.app.model.TopicList
+import org.oppia.android.app.translation.AppLanguageResourceHandler
+import org.oppia.android.app.utility.datetime.DateTimeUtil
+import org.oppia.android.app.viewmodel.ObservableViewModel
+import org.oppia.android.domain.classroom.ClassroomController
+import org.oppia.android.domain.classroom.TEST_CLASSROOM_ID_0
+import org.oppia.android.domain.oppialogger.OppiaLogger
+import org.oppia.android.domain.profile.ProfileManagementController
+import org.oppia.android.domain.topic.TopicListController
+import org.oppia.android.domain.translation.TranslationController
+import org.oppia.android.util.data.AsyncResult
+import org.oppia.android.util.data.DataProvider
+import org.oppia.android.util.data.DataProviders.Companion.combineWith
+import org.oppia.android.util.data.DataProviders.Companion.toLiveData
+import org.oppia.android.util.parser.html.StoryHtmlParserEntityType
+import org.oppia.android.util.parser.html.TopicHtmlParserEntityType
+
+private const val PROFILE_AND_PROMOTED_ACTIVITY_COMBINED_PROVIDER_ID =
+ "profile+promotedActivityList"
+private const val CLASSROOM_LIST_FRAGMENT_COMBINED_PROVIDER_ID =
+ "profile+promotedActivityList+classroomListProvider"
+
+/** [ViewModel] for layouts in classroom list fragment. */
+class ClassroomListViewModel(
+ private val activity: AppCompatActivity,
+ private val fragment: Fragment,
+ private val oppiaLogger: OppiaLogger,
+ private val internalProfileId: Int,
+ private val profileManagementController: ProfileManagementController,
+ private val topicListController: TopicListController,
+ private val classroomController: ClassroomController,
+ @TopicHtmlParserEntityType private val topicEntityType: String,
+ @StoryHtmlParserEntityType private val storyEntityType: String,
+ private val resourceHandler: AppLanguageResourceHandler,
+ private val dateTimeUtil: DateTimeUtil,
+ private val translationController: TranslationController
+) : ObservableViewModel() {
+ private val profileId: ProfileId = ProfileId.newBuilder().setInternalId(internalProfileId).build()
+ private val promotedStoryListLimit = activity.resources.getInteger(
+ R.integer.promoted_story_list_limit
+ )
+
+ /** An observable boolean property indicating the visibility state of the progress bar. */
+ val isProgressBarVisible = ObservableField(true)
+
+ /** An observable field to store the currently selected classroom ID. */
+ val selectedClassroomId = ObservableField("")
+
+ private val profileDataProvider: DataProvider by lazy {
+ profileManagementController.getProfile(profileId)
+ }
+
+ private val promotedActivityListSummaryDataProvider: DataProvider by lazy {
+ topicListController.getPromotedActivityList(profileId)
+ }
+
+ private val classroomSummaryListDataProvider: DataProvider by lazy {
+ classroomController.getClassroomList(profileId)
+ }
+
+ /**
+ * An observable list containing the topic list of the selected classroom. The topic list updates
+ * when a new classroom is selected by the user.
+ */
+ val topicList: ObservableList by lazy {
+ fetchAndUpdateTopicList() // Fetch the topic list of the last selected classroom.
+ ObservableArrayList() // Initialize the topic list.
+ }
+
+ private val homeItemViewModelListDataProvider: DataProvider> by lazy {
+ // This will block until all data providers return initial results (which may be default
+ // instances). If any of the data providers are pending or failed, the combined result will also
+ // be pending or failed.
+ profileDataProvider.combineWith(
+ promotedActivityListSummaryDataProvider,
+ PROFILE_AND_PROMOTED_ACTIVITY_COMBINED_PROVIDER_ID
+ ) { profile, promotedActivityList ->
+ if (profile.numberOfLogins > 1) {
+ listOfNotNull(
+ computeWelcomeViewModel(profile),
+ computePromotedActivityListViewModel(promotedActivityList)
+ )
+ } else {
+ listOfNotNull(computeWelcomeViewModel(profile))
+ }
+ }.combineWith(
+ classroomSummaryListDataProvider,
+ CLASSROOM_LIST_FRAGMENT_COMBINED_PROVIDER_ID
+ ) { homeItemViewModelList, classroomSummaryList ->
+ homeItemViewModelList + computeClassroomItemViewModelList(classroomSummaryList)
+ }
+ }
+
+ /**
+ * [LiveData] of the list of items displayed in the [ClassroomListFragment] RecyclerView.
+ * The list backing this live data will automatically update if constituent parts of the UI
+ * change (e.g. if the promoted story list changes). If an error occurs or data providers are
+ * still pending, the list is empty and so the view shown will be empty.
+ */
+ val homeItemViewModelListLiveData: LiveData> by lazy {
+ Transformations.map(homeItemViewModelListDataProvider.toLiveData()) { itemListResult ->
+ return@map when (itemListResult) {
+ is AsyncResult.Failure -> {
+ oppiaLogger.e(
+ "ClassroomListFragment",
+ "No classroom list fragment available -- failed to retrieve fragment data.",
+ itemListResult.error
+ )
+ listOf()
+ }
+ is AsyncResult.Pending -> listOf()
+ is AsyncResult.Success -> {
+ isProgressBarVisible.set(false)
+ itemListResult.value
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a [HomeItemViewModel] corresponding to the welcome message (see [WelcomeViewModel]), or null if
+ * the specified profile has insufficient information to show the welcome message.
+ */
+ private fun computeWelcomeViewModel(profile: Profile): HomeItemViewModel? {
+ return if (profile.name.isNotEmpty()) {
+ WelcomeViewModel(profile.name, resourceHandler, dateTimeUtil)
+ } else null
+ }
+
+ /**
+ * Returns a [HomeItemViewModel] corresponding to the promoted stories(Recommended, Recently-played and
+ * Last-played stories)[PromotedStoryListViewModel] and Upcoming topics [ComingSoonTopicListViewModel]
+ * to be displayed for this learner or null if this profile does not have any promoted stories.
+ * Promoted stories are determined by any recent stories last-played stories or suggested stories started by this profile.
+ */
+ private fun computePromotedActivityListViewModel(
+ promotedActivityList: PromotedActivityList
+ ): HomeItemViewModel? {
+ when (promotedActivityList.recommendationTypeCase) {
+ PromotedActivityList.RecommendationTypeCase.PROMOTED_STORY_LIST -> {
+ val storyViewModelList = computePromotedStoryViewModelList(
+ promotedActivityList.promotedStoryList
+ )
+ return if (storyViewModelList.isNotEmpty()) {
+ return PromotedStoryListViewModel(
+ activity,
+ storyViewModelList,
+ promotedActivityList,
+ resourceHandler
+ )
+ } else null
+ }
+ PromotedActivityList.RecommendationTypeCase.COMING_SOON_TOPIC_LIST -> {
+ val comingSoonTopicsList = computeComingSoonTopicViewModelList(
+ promotedActivityList.comingSoonTopicList
+ )
+ return if (comingSoonTopicsList.isNotEmpty()) {
+ return ComingSoonTopicListViewModel(
+ comingSoonTopicsList
+ )
+ } else null
+ }
+ else -> return null
+ }
+ }
+
+ /**
+ * Returns a list of [HomeItemViewModel]s corresponding to the the [PromotedStoryListViewModel] displayed
+ * for this profile (see [PromotedStoryViewModel]), or an empty list if the profile does not have any
+ * ongoing stories at all.
+ */
+ private fun computePromotedStoryViewModelList(
+ promotedStoryList: PromotedStoryList
+ ): List {
+ with(promotedStoryList) {
+ val storyList = when {
+ suggestedStoryList.isNotEmpty() -> {
+ if (recentlyPlayedStoryList.isNotEmpty() || olderPlayedStoryList.isNotEmpty()) {
+ recentlyPlayedStoryList +
+ olderPlayedStoryList +
+ suggestedStoryList
+ } else {
+ suggestedStoryList
+ }
+ }
+ recentlyPlayedStoryList.isNotEmpty() -> {
+ recentlyPlayedStoryList
+ }
+ else -> {
+ olderPlayedStoryList
+ }
+ }
+
+ // Check if at least one story in topic is completed. Prioritize recommended story over
+ // completed story topic.
+ val sortedStoryList = storyList.sortedByDescending { !it.isTopicLearned }
+ return sortedStoryList.take(promotedStoryListLimit)
+ .mapIndexed { index, promotedStory ->
+ PromotedStoryViewModel(
+ activity,
+ internalProfileId,
+ sortedStoryList.size,
+ storyEntityType,
+ promotedStory,
+ translationController,
+ index
+ )
+ }
+ }
+ }
+
+ /**
+ * Returns a list of [HomeItemViewModel]s corresponding to [ComingSoonTopicListViewModel] all the upcoming topics available in future and to be
+ * displayed for this profile (see [ComingSoonTopicsViewModel]), or an empty list if the profile does not have any
+ * ongoing stories at all.
+ */
+ private fun computeComingSoonTopicViewModelList(
+ comingSoonTopicList: ComingSoonTopicList
+ ): List {
+ return comingSoonTopicList.upcomingTopicList.map { topicSummary ->
+ ComingSoonTopicsViewModel(
+ activity,
+ topicSummary,
+ topicEntityType,
+ comingSoonTopicList,
+ translationController
+ )
+ }
+ }
+
+ /**
+ * Returns a list of [HomeItemViewModel]s corresponding to all the classroom summaries available
+ * and to be displayed on the [ClassroomListActivity] (see [ClassroomSummaryViewModel]). Returns
+ * an empty list if there are no classrooms to display to the learner (caused by either
+ * error or pending data providers).
+ */
+ private fun computeClassroomItemViewModelList(
+ classroomList: ClassroomList
+ ): List {
+ return classroomList.classroomSummaryList.map { ephemeralClassroomSummary ->
+ ClassroomSummaryViewModel(
+ fragment as ClassroomSummaryClickListener,
+ ephemeralClassroomSummary,
+ translationController
+ )
+ }
+ }
+
+ /**
+ * Returns a list of [HomeItemViewModel]s corresponding to all the lesson topics available and to
+ * be displayed on the [ClassroomListActivity] (see [TopicSummaryViewModel]) along with
+ * associated topics list header (see [AllTopicsViewModel]). Returns an empty list if there are
+ * no topics to display to the learner (caused by either error or pending data providers).
+ */
+ private fun computeAllTopicsItemsViewModelList(
+ topicList: TopicList
+ ): List {
+ val allTopicsList = topicList.topicSummaryList.mapIndexed { topicIndex, ephemeralSummary ->
+ TopicSummaryViewModel(
+ activity,
+ ephemeralSummary,
+ topicEntityType,
+ fragment as TopicSummaryClickListener,
+ position = topicIndex,
+ resourceHandler,
+ translationController
+ )
+ }
+ return if (allTopicsList.isNotEmpty()) {
+ listOf(AllTopicsViewModel) + allTopicsList
+ } else emptyList()
+ }
+
+ /** Fetches and updates the topic list based on the provided or last selected classroom ID. */
+ fun fetchAndUpdateTopicList(classroomId: String = "") {
+ if (classroomId.isBlank()) {
+ // Retrieve the last selected classroom ID if no specific classroom ID is provided.
+ profileManagementController.retrieveLastSelectedClassroomId(profileId)
+ .toLiveData().observe(fragment) { lastSelectedClassroomIdResult ->
+ when (lastSelectedClassroomIdResult) {
+ is AsyncResult.Success -> {
+ val lastSelectedClassroomId = lastSelectedClassroomIdResult.value
+ updateTopicList(
+ if (lastSelectedClassroomId.isNullOrBlank())
+ TEST_CLASSROOM_ID_0
+ else
+ lastSelectedClassroomId
+ )
+ }
+ is AsyncResult.Failure -> {
+ oppiaLogger.e(
+ "ClassroomListFragment",
+ "Failed to retrieve last selected classroom ID",
+ lastSelectedClassroomIdResult.error
+ )
+ // Use a default classroom ID in case of failure.
+ updateTopicList(TEST_CLASSROOM_ID_0)
+ }
+ is AsyncResult.Pending -> {}
+ }
+ }
+ } else {
+ // Fetch the topic list using the provided classroom ID.
+ updateTopicList(classroomId)
+ }
+ }
+
+ private fun updateTopicList(classroomId: String) {
+ selectedClassroomId.set(classroomId)
+ classroomController.getTopicList(
+ profileId,
+ classroomId
+ ).toLiveData().observe(fragment) { topicListResult ->
+ when (topicListResult) {
+ is AsyncResult.Success -> {
+ topicList.clear()
+ computeAllTopicsItemsViewModelList(topicListResult.value).map { itemViewModel ->
+ topicList.add(itemViewModel)
+ }
+ }
+ is AsyncResult.Failure -> {
+ oppiaLogger.e(
+ "ClassroomListFragment",
+ "Failed to retrieve topic list.",
+ topicListResult.error
+ )
+ }
+ is AsyncResult.Pending -> {}
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/classroomlist/ClassroomList.kt b/app/src/main/java/org/oppia/android/app/classroom/classroomlist/ClassroomList.kt
new file mode 100644
index 00000000000..5c35b3383b1
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/classroomlist/ClassroomList.kt
@@ -0,0 +1,142 @@
+package org.oppia.android.app.classroom.classroomlist
+
+import android.content.res.Configuration
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Card
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.oppia.android.R
+import org.oppia.android.app.classroom.getDrawableResource
+import org.oppia.android.app.home.classroomlist.ClassroomSummaryViewModel
+
+/** Test tag for the header of the classroom section. */
+const val CLASSROOM_HEADER_TEST_TAG = "TEST_TAG.classroom_header"
+
+/** Test tag for the classroom list. */
+const val CLASSROOM_LIST_TEST_TAG = "TEST_TAG.classroom_list"
+
+/** Displays a list of classroom summaries with a header. */
+@Composable
+fun ClassroomList(
+ classroomSummaryList: List,
+ selectedClassroomId: String
+) {
+ Column(
+ modifier = Modifier
+ .background(
+ color = colorResource(id = R.color.component_color_shared_screen_primary_background_color)
+ )
+ .fillMaxWidth(),
+ ) {
+ Text(
+ text = stringResource(id = R.string.classrooms_list_activity_section_header),
+ color = colorResource(id = R.color.component_color_shared_primary_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(id = R.dimen.classrooms_list_header_text_size).value.sp,
+ modifier = Modifier
+ .testTag(CLASSROOM_HEADER_TEST_TAG)
+ .padding(
+ start = dimensionResource(id = R.dimen.classrooms_text_margin_start),
+ top = dimensionResource(id = R.dimen.classrooms_text_margin_top),
+ end = dimensionResource(id = R.dimen.classrooms_text_margin_end),
+ bottom = dimensionResource(id = R.dimen.classrooms_text_margin_bottom),
+ ),
+ )
+ LazyRow(
+ modifier = Modifier.testTag(CLASSROOM_LIST_TEST_TAG),
+ contentPadding = PaddingValues(
+ start = dimensionResource(id = R.dimen.classrooms_text_margin_start),
+ end = dimensionResource(id = R.dimen.classrooms_text_margin_end),
+ ),
+ ) {
+ items(classroomSummaryList) {
+ ClassroomCard(classroomSummaryViewModel = it, selectedClassroomId)
+ }
+ }
+ }
+}
+
+/** Displays a single classroom card with an image and text, handling click events. */
+@Composable
+fun ClassroomCard(
+ classroomSummaryViewModel: ClassroomSummaryViewModel,
+ selectedClassroomId: String
+) {
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+ val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
+ val isTablet = if (isPortrait) screenWidth > 600 else screenWidth > 840
+ val isCardSelected = classroomSummaryViewModel.classroomSummary.classroomId == selectedClassroomId
+ Card(
+ modifier = Modifier
+ .height(dimensionResource(id = R.dimen.classrooms_card_height))
+ .width(dimensionResource(id = R.dimen.classrooms_card_width))
+ .padding(
+ start = dimensionResource(R.dimen.promoted_story_card_layout_margin_start),
+ end = dimensionResource(R.dimen.promoted_story_card_layout_margin_end),
+ )
+ .clickable {
+ classroomSummaryViewModel.handleClassroomClick()
+ },
+ backgroundColor = if (isCardSelected) {
+ colorResource(id = R.color.component_color_classroom_card_color)
+ } else {
+ colorResource(id = R.color.component_color_shared_screen_primary_background_color)
+ },
+ border = BorderStroke(2.dp, color = colorResource(id = R.color.color_def_oppia_green)),
+ elevation = dimensionResource(id = R.dimen.classrooms_card_elevation),
+ ) {
+ Column(
+ modifier = Modifier.padding(all = dimensionResource(id = R.dimen.classrooms_card_padding)),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ if (isPortrait || isTablet) { // Hides the classroom icon for landscape phone layouts.
+ Image(
+ painter = painterResource(
+ id = classroomSummaryViewModel
+ .classroomSummary
+ .classroomThumbnail
+ .getDrawableResource()
+ ),
+ contentDescription = classroomSummaryViewModel.title,
+ modifier = Modifier
+ .padding(bottom = dimensionResource(id = R.dimen.classrooms_card_icon_padding_bottom))
+ .size(size = dimensionResource(id = R.dimen.classrooms_card_icon_size)),
+ )
+ }
+ Text(
+ text = classroomSummaryViewModel.title,
+ color = colorResource(id = R.color.component_color_classroom_card_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(id = R.dimen.classrooms_card_label_text_size).value.sp,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/promotedlist/PromotedList.kt b/app/src/main/java/org/oppia/android/app/classroom/promotedlist/PromotedList.kt
new file mode 100644
index 00000000000..3ac2bdebf1d
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/promotedlist/PromotedList.kt
@@ -0,0 +1,233 @@
+package org.oppia.android.app.classroom.promotedlist
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.oppia.android.R
+import org.oppia.android.app.classroom.getDrawableResource
+import org.oppia.android.app.home.promotedlist.PromotedStoryListViewModel
+import org.oppia.android.app.home.promotedlist.PromotedStoryViewModel
+import org.oppia.android.util.locale.OppiaLocale
+
+/** Test tag for the header of the promoted story list. */
+const val PROMOTED_STORY_LIST_HEADER_TEST_TAG = "TEST_TAG.promoted_story_list_header"
+
+/** Test tag for the promoted story list. */
+const val PROMOTED_STORY_LIST_TEST_TAG = "TEST_TAG.promoted_story_list"
+
+/** Displays a list of promoted stories. */
+@Composable
+fun PromotedStoryList(
+ promotedStoryListViewModel: PromotedStoryListViewModel,
+ machineLocale: OppiaLocale.MachineLocale,
+) {
+ Row(
+ modifier = Modifier
+ .testTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG)
+ .fillMaxWidth()
+ .padding(
+ start = dimensionResource(id = R.dimen.promoted_story_list_layout_margin_start),
+ top = dimensionResource(id = R.dimen.promoted_story_list_layout_margin_top),
+ end = dimensionResource(id = R.dimen.promoted_story_list_layout_margin_end),
+ ),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ text = promotedStoryListViewModel.getHeader(),
+ color = colorResource(id = R.color.component_color_shared_primary_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(id = R.dimen.promoted_story_list_header_text_size).value.sp,
+ modifier = Modifier
+ .weight(weight = 1f, fill = false),
+ )
+ if (promotedStoryListViewModel.getViewAllButtonVisibility() == View.VISIBLE) {
+ Text(
+ text = machineLocale.run { stringResource(id = R.string.view_all).toMachineUpperCase() },
+ color = colorResource(id = R.color.component_color_home_activity_view_all_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(id = R.dimen.promoted_story_list_view_all_text_size).value.sp,
+ modifier = Modifier
+ .padding(
+ start = dimensionResource(id = R.dimen.promoted_story_list_view_all_padding_start)
+ )
+ .clickable { promotedStoryListViewModel.clickOnViewAll() },
+ )
+ }
+ }
+ LazyRow(
+ modifier = Modifier
+ .testTag(PROMOTED_STORY_LIST_TEST_TAG)
+ .padding(top = dimensionResource(id = R.dimen.promoted_story_list_padding)),
+ contentPadding = PaddingValues(
+ start = dimensionResource(id = R.dimen.promoted_story_list_layout_margin_start),
+ end = promotedStoryListViewModel.endPadding.dp,
+ ),
+ ) {
+ items(promotedStoryListViewModel.promotedStoryList) {
+ PromotedStoryCard(
+ promotedStoryViewModel = it,
+ machineLocale = machineLocale
+ )
+ }
+ }
+}
+
+/** Displays a single promoted story card with an image, title, and handling click events. */
+@Composable
+fun PromotedStoryCard(
+ promotedStoryViewModel: PromotedStoryViewModel,
+ machineLocale: OppiaLocale.MachineLocale,
+) {
+ val cardLayoutWidth = promotedStoryViewModel.computeLayoutWidth()
+ val cardColumnModifier =
+ if (cardLayoutWidth == ViewGroup.LayoutParams.MATCH_PARENT) Modifier.fillMaxWidth()
+ else Modifier.width(promotedStoryViewModel.computeLayoutWidth().dp)
+
+ Card(
+ modifier = Modifier
+ .width(width = dimensionResource(id = R.dimen.promoted_story_card_layout_width))
+ .padding(
+ start = dimensionResource(id = R.dimen.promoted_story_card_layout_margin_start),
+ end = dimensionResource(id = R.dimen.promoted_story_card_layout_margin_end),
+ bottom = dimensionResource(id = R.dimen.promoted_story_card_layout_margin_bottom),
+ )
+ .clickable { promotedStoryViewModel.clickOnStoryTile() },
+ backgroundColor = colorResource(
+ id = R.color.component_color_shared_screen_primary_background_color
+ ),
+ elevation = dimensionResource(id = R.dimen.promoted_story_card_elevation),
+ ) {
+ Column(
+ modifier = cardColumnModifier
+ ) {
+ Image(
+ painter = painterResource(
+ id = promotedStoryViewModel.promotedStory.lessonThumbnail.getDrawableResource()
+ ),
+ contentDescription = promotedStoryViewModel.storyTitle,
+ modifier = Modifier
+ .aspectRatio(16f / 9f)
+ .background(
+ Color(
+ (
+ 0xff000000L or
+ promotedStoryViewModel.promotedStory.lessonThumbnail.backgroundColorRgb.toLong()
+ ).toInt()
+ )
+ )
+ )
+ Text(
+ text = promotedStoryViewModel.nextChapterTitle,
+ modifier = Modifier.padding(
+ start = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_horizontal
+ ),
+ top = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_vertical
+ ),
+ end = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_horizontal
+ ),
+ ),
+ color = colorResource(id = R.color.component_color_shared_primary_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(
+ id = R.dimen.promoted_story_card_chapter_title_text_size
+ ).value.sp,
+ textAlign = TextAlign.Start,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ Text(
+ text = machineLocale.run { promotedStoryViewModel.topicTitle.toMachineUpperCase() },
+ modifier = Modifier.padding(
+ start = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_horizontal
+ ),
+ top = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_vertical
+ ),
+ end = dimensionResource(
+ id = R.dimen.promoted_story_card_padding_horizontal
+ ),
+ ),
+ color = colorResource(
+ id = R.color.component_color_shared_story_card_topic_name_text_color
+ ),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Light,
+ fontSize = dimensionResource(
+ id = R.dimen.promoted_story_card_topic_title_text_size
+ ).value.sp,
+ textAlign = TextAlign.Start,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ Text(
+ text = machineLocale.run { promotedStoryViewModel.classroomTitle.toMachineUpperCase() },
+ modifier = Modifier
+ .padding(
+ horizontal = dimensionResource(id = R.dimen.promoted_story_card_padding_horizontal),
+ vertical = dimensionResource(id = R.dimen.promoted_story_card_padding_vertical),
+ )
+ .border(
+ width = 2.dp,
+ color = colorResource(
+ id = R.color.component_color_classroom_promoted_list_classroom_label_color
+ ),
+ shape = RoundedCornerShape(50)
+ )
+ .padding(
+ horizontal = dimensionResource(id = R.dimen.promoted_story_card_padding_horizontal),
+ vertical = dimensionResource(id = R.dimen.promoted_story_card_padding_vertical),
+ ),
+ color = colorResource(
+ id = R.color.component_color_classroom_promoted_list_classroom_label_color
+ ),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(
+ id = R.dimen.promoted_story_card_classroom_title_text_size
+ ).value.sp,
+ textAlign = TextAlign.Start,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/topiclist/AllTopicsHeaderText.kt b/app/src/main/java/org/oppia/android/app/classroom/topiclist/AllTopicsHeaderText.kt
new file mode 100644
index 00000000000..31dec186c60
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/topiclist/AllTopicsHeaderText.kt
@@ -0,0 +1,41 @@
+package org.oppia.android.app.classroom.topiclist
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import org.oppia.android.R
+
+/** Test tag for the all topics section header. */
+const val ALL_TOPICS_HEADER_TEST_TAG = "TEST_TAG.all_topics_header"
+
+/** Displays the header text for the topic list section. */
+@Composable
+fun AllTopicsHeaderText() {
+ Text(
+ text = stringResource(id = R.string.select_a_topic_to_start),
+ color = colorResource(id = R.color.component_color_classroom_all_topics_header_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Medium,
+ fontSize = dimensionResource(id = R.dimen.all_topics_text_size).value.sp,
+ modifier = Modifier
+ .testTag(ALL_TOPICS_HEADER_TEST_TAG)
+ .fillMaxWidth()
+ .background(colorResource(id = R.color.color_palette_classroom_topic_list_background_color))
+ .padding(
+ start = dimensionResource(id = R.dimen.all_topics_text_margin_start),
+ top = dimensionResource(id = R.dimen.all_topics_text_margin_top),
+ end = dimensionResource(id = R.dimen.all_topics_text_margin_end),
+ bottom = dimensionResource(id = R.dimen.all_topics_text_margin_bottom),
+ ),
+ )
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/topiclist/TopicCard.kt b/app/src/main/java/org/oppia/android/app/classroom/topiclist/TopicCard.kt
new file mode 100644
index 00000000000..6e67f6b5e3d
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/topiclist/TopicCard.kt
@@ -0,0 +1,108 @@
+package org.oppia.android.app.classroom.topiclist
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Card
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.sp
+import org.oppia.android.R
+import org.oppia.android.app.classroom.getDrawableResource
+import org.oppia.android.app.home.topiclist.TopicSummaryViewModel
+
+/** Displays a card with the topic summary information. */
+@Composable
+fun TopicCard(topicSummaryViewModel: TopicSummaryViewModel) {
+ Card(
+ modifier = Modifier
+ .padding(
+ start = dimensionResource(R.dimen.topic_card_margin_start),
+ end = dimensionResource(R.dimen.topic_card_margin_end),
+ )
+ .clickable { topicSummaryViewModel.clickOnSummaryTile() },
+ elevation = dimensionResource(id = R.dimen.topic_card_elevation),
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Image(
+ painter = painterResource(
+ id = topicSummaryViewModel.topicSummary.topicThumbnail.getDrawableResource()
+ ),
+ contentDescription = "Picture of a " +
+ "${topicSummaryViewModel.topicSummary.topicThumbnail.thumbnailGraphic.name}.",
+ modifier = Modifier
+ .aspectRatio(4f / 3f)
+ .background(
+ Color(
+ (
+ 0xff000000L or
+ topicSummaryViewModel.topicSummary.topicThumbnail.backgroundColorRgb.toLong()
+ ).toInt()
+ )
+ )
+ )
+ TopicCardTextSection(topicSummaryViewModel)
+ }
+ }
+}
+
+/** Displays text section of the topic card. */
+@Composable
+fun TopicCardTextSection(topicSummaryViewModel: TopicSummaryViewModel) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ color = colorResource(
+ id = R.color.component_color_shared_topic_card_item_background_color
+ )
+ ),
+ verticalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = topicSummaryViewModel.title,
+ modifier = Modifier
+ .padding(
+ start = dimensionResource(id = R.dimen.topic_list_item_text_padding),
+ top = dimensionResource(id = R.dimen.topic_list_item_text_padding),
+ end = dimensionResource(id = R.dimen.topic_list_item_text_padding)
+ ),
+ color = colorResource(id = R.color.component_color_shared_secondary_4_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontSize = dimensionResource(id = R.dimen.topic_list_item_text_size).value.sp,
+ textAlign = TextAlign.Start,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ Text(
+ text = topicSummaryViewModel.computeLessonCountText(),
+ modifier = Modifier
+ .padding(all = dimensionResource(id = R.dimen.topic_list_item_text_padding)),
+ color = colorResource(id = R.color.component_color_shared_secondary_4_text_color),
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight.Light,
+ fontSize = dimensionResource(id = R.dimen.topic_list_item_text_size).value.sp,
+ fontStyle = FontStyle.Italic,
+ textAlign = TextAlign.Start,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/classroom/welcome/WelcomeText.kt b/app/src/main/java/org/oppia/android/app/classroom/welcome/WelcomeText.kt
new file mode 100644
index 00000000000..f70bcd293b0
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/classroom/welcome/WelcomeText.kt
@@ -0,0 +1,53 @@
+package org.oppia.android.app.classroom.welcome
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import org.oppia.android.R
+import org.oppia.android.app.home.WelcomeViewModel
+
+/** Test tag for the welcome section. */
+const val WELCOME_TEST_TAG = "TEST_TAG.welcome"
+
+/** Displays a welcome text with an underline. */
+@Composable
+fun WelcomeText(welcomeViewModel: WelcomeViewModel) {
+ val outerPadding = dimensionResource(id = R.dimen.home_welcome_outer_padding)
+ val textMarginEnd = dimensionResource(id = R.dimen.home_welcome_text_view_margin_end)
+ val greetingLineColor = colorResource(
+ id = R.color.component_color_home_activity_layout_greeting_text_line_color
+ )
+
+ Text(
+ text = welcomeViewModel.computeWelcomeText(),
+ modifier = Modifier
+ .testTag(WELCOME_TEST_TAG)
+ .padding(
+ start = outerPadding,
+ top = outerPadding,
+ end = outerPadding + textMarginEnd,
+ )
+ .drawBehind {
+ val strokeWidthPx = 6.dp.toPx()
+ val verticalOffset = size.height + 4.dp.toPx()
+ drawLine(
+ color = greetingLineColor,
+ strokeWidth = strokeWidthPx,
+ start = Offset(x = 0f, y = verticalOffset),
+ end = Offset(x = size.width, y = verticalOffset),
+ )
+ },
+ color = colorResource(id = R.color.component_color_shared_primary_text_color),
+ fontSize = dimensionResource(id = R.dimen.home_welcome_text_size).value.sp,
+ fontFamily = FontFamily.SansSerif,
+ )
+}
diff --git a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt
index a8fcd95253f..f606bafca88 100644
--- a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt
@@ -16,6 +16,7 @@ import com.google.android.material.navigation.NavigationView
import com.google.common.base.Optional
import org.oppia.android.R
import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.devoptions.DeveloperOptionsStarter
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.help.HelpActivity
@@ -37,6 +38,8 @@ import org.oppia.android.domain.profile.ProfileManagementController
import org.oppia.android.domain.topic.TopicController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
+import org.oppia.android.util.platformparameter.EnableMultipleClassrooms
+import org.oppia.android.util.platformparameter.PlatformParameterValue
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import org.oppia.android.util.statusbar.StatusBarColor
import javax.inject.Inject
@@ -53,7 +56,8 @@ class NavigationDrawerFragmentPresenter @Inject constructor(
private val oppiaLogger: OppiaLogger,
private val headerViewModel: NavigationDrawerHeaderViewModel,
private val footerViewModel: NavigationDrawerFooterViewModel,
- private val developerOptionsStarter: Optional
+ private val developerOptionsStarter: Optional,
+ @EnableMultipleClassrooms private val enableMultipleClassrooms: PlatformParameterValue,
) : NavigationView.OnNavigationItemSelectedListener {
private lateinit var drawerToggle: ActionBarDrawerToggle
private lateinit var drawerLayout: DrawerLayout
@@ -232,7 +236,10 @@ class NavigationDrawerFragmentPresenter @Inject constructor(
if (previousMenuItemId != menuItemId) {
when (NavigationDrawerItem.valueFromNavId(menuItemId)) {
NavigationDrawerItem.HOME -> {
- val intent = HomeActivity.createHomeActivity(activity, profileId)
+ val intent = if (enableMultipleClassrooms.value)
+ ClassroomListActivity.createClassroomListActivity(activity, profileId)
+ else
+ HomeActivity.createHomeActivity(activity, profileId)
fragment.activity!!.startActivity(intent)
drawerLayout.closeDrawers()
}
diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt
index 029d214a41a..b3b35f5f2ed 100644
--- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt
+++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt
@@ -7,6 +7,7 @@ import org.oppia.android.app.administratorcontrols.AdministratorControlsFragment
import org.oppia.android.app.administratorcontrols.LogoutDialogFragment
import org.oppia.android.app.administratorcontrols.appversion.AppVersionFragment
import org.oppia.android.app.administratorcontrols.learneranalytics.ProfileAndDeviceIdFragment
+import org.oppia.android.app.classroom.ClassroomListFragment
import org.oppia.android.app.completedstorylist.CompletedStoryListFragment
import org.oppia.android.app.devoptions.DeveloperOptionsFragment
import org.oppia.android.app.devoptions.forcenetworktype.ForceNetworkTypeFragment
@@ -194,4 +195,5 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto
fun inject(exitSurveyConfirmationDialogFragment: ExitSurveyConfirmationDialogFragment)
fun inject(surveyWelcomeDialogFragment: SurveyWelcomeDialogFragment)
fun inject(surveyOutroDialogFragment: SurveyOutroDialogFragment)
+ fun inject(classroomListFragment: ClassroomListFragment)
}
diff --git a/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryClickListener.kt b/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryClickListener.kt
new file mode 100644
index 00000000000..4ce96bb5eec
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryClickListener.kt
@@ -0,0 +1,9 @@
+package org.oppia.android.app.home.classroomlist
+
+import org.oppia.android.app.model.ClassroomSummary
+
+/** Listener interface for when a classroom card is clicked. */
+interface ClassroomSummaryClickListener {
+ /** Called when a classroom card is clicked by the user. */
+ fun onClassroomSummaryClicked(classroomSummary: ClassroomSummary)
+}
diff --git a/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryViewModel.kt b/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryViewModel.kt
new file mode 100644
index 00000000000..3ffb03b1b80
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryViewModel.kt
@@ -0,0 +1,39 @@
+package org.oppia.android.app.home.classroomlist
+
+import org.oppia.android.app.home.HomeItemViewModel
+import org.oppia.android.app.model.ClassroomSummary
+import org.oppia.android.app.model.EphemeralClassroomSummary
+import org.oppia.android.domain.translation.TranslationController
+import java.util.Objects
+
+/** The view model corresponding to individual classroom summaries in the classroom summary RecyclerView. */
+class ClassroomSummaryViewModel(
+ private val classroomSummaryClickListener: ClassroomSummaryClickListener,
+ ephemeralClassroomSummary: EphemeralClassroomSummary,
+ translationController: TranslationController,
+) : HomeItemViewModel() {
+ /** The [ClassroomSummary] retrieved from the [EphemeralClassroomSummary]. */
+ val classroomSummary: ClassroomSummary = ephemeralClassroomSummary.classroomSummary
+
+ /** Lazy-loaded title extracted using the [TranslationController]. */
+ val title: String by lazy {
+ translationController.extractString(
+ ephemeralClassroomSummary.classroomSummary.classroomTitle,
+ ephemeralClassroomSummary.writtenTranslationContext
+ )
+ }
+
+ /** Handles the click event for a [ClassroomSummary] by invoking the click listener. */
+ fun handleClassroomClick() {
+ classroomSummaryClickListener.onClassroomSummaryClicked(classroomSummary)
+ }
+
+ // Overriding equals is needed so that DataProvider combine functions used in the
+ // ClassroomListViewModel will only rebind when the actual data in the data list changes,
+ // rather than when the ViewModel object changes.
+ override fun equals(other: Any?): Boolean {
+ return other is ClassroomSummaryViewModel
+ }
+
+ override fun hashCode() = Objects.hash()
+}
diff --git a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModel.kt b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModel.kt
index 2adceb6abea..6b300f6355d 100755
--- a/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModel.kt
+++ b/app/src/main/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModel.kt
@@ -39,6 +39,11 @@ class PromotedStoryViewModel(
promotedStory.nextChapterTitle, promotedStory.nextChapterWrittenTranslationContext
)
}
+ val classroomTitle by lazy {
+ translationController.extractString(
+ promotedStory.classroomTitle, promotedStory.classroomWrittenTranslationContext
+ )
+ }
private val routeToTopicPlayStoryListener = activity as RouteToTopicPlayStoryListener
diff --git a/app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsActivity.kt b/app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsActivity.kt
index 18b623ab7ec..7d6be2cf97d 100644
--- a/app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsActivity.kt
@@ -5,10 +5,13 @@ import android.content.Intent
import android.os.Bundle
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.ScreenName.MY_DOWNLOADS_ACTIVITY
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
+import org.oppia.android.util.platformparameter.EnableMultipleClassrooms
+import org.oppia.android.util.platformparameter.PlatformParameterValue
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.decorateWithUserProfileId
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import javax.inject.Inject
@@ -17,6 +20,11 @@ import javax.inject.Inject
class MyDownloadsActivity : InjectableAutoLocalizedAppCompatActivity() {
@Inject
lateinit var myDownloadsActivityPresenter: MyDownloadsActivityPresenter
+
+ @Inject
+ @EnableMultipleClassrooms
+ lateinit var enableMultipleClassrooms: PlatformParameterValue
+
private var internalProfileId: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
@@ -39,8 +47,11 @@ class MyDownloadsActivity : InjectableAutoLocalizedAppCompatActivity() {
}
override fun onBackPressed() {
- val profileid = ProfileId.newBuilder().setInternalId(internalProfileId).build()
- val intent = HomeActivity.createHomeActivity(this, profileid)
+ val profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build()
+ val intent = if (enableMultipleClassrooms.value)
+ ClassroomListActivity.createClassroomListActivity(this, profileId)
+ else
+ HomeActivity.createHomeActivity(this, profileId)
startActivity(intent)
finish()
}
diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageActivityPresenter.kt
index 0a842397a4b..cb33ecf7c0e 100644
--- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageActivityPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageActivityPresenter.kt
@@ -1,5 +1,6 @@
package org.oppia.android.app.options
+import android.app.Activity.RESULT_OK
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
@@ -53,7 +54,7 @@ class AudioLanguageActivityPresenter @Inject constructor(private val activity: A
putProtoExtra(MESSAGE_AUDIO_LANGUAGE_RESULTS_KEY, result)
}
- activity.setResult(REQUEST_CODE_AUDIO_LANGUAGE, intent)
+ activity.setResult(RESULT_OK, intent)
activity.finish()
}
diff --git a/app/src/main/java/org/oppia/android/app/options/OptionsActivity.kt b/app/src/main/java/org/oppia/android/app/options/OptionsActivity.kt
index ff8d49a7ede..52a993a52f6 100644
--- a/app/src/main/java/org/oppia/android/app/options/OptionsActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/options/OptionsActivity.kt
@@ -4,6 +4,8 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.TextView
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import org.oppia.android.R
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
@@ -54,6 +56,8 @@ class OptionsActivity :
private var isFirstOpen = true
private lateinit var selectedFragment: String
private var profileId: Int? = -1
+ private lateinit var readingTextSizeLauncher: ActivityResultLauncher
+ private lateinit var audioLanguageLauncher: ActivityResultLauncher
companion object {
// TODO(#1655): Re-restrict access to fields in tests post-Gradle.
@@ -115,26 +119,31 @@ class OptionsActivity :
profileId!!
)
title = resourceHandler.getStringInLocale(R.string.menu_options)
- }
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- checkNotNull(data) {
- "Expected data to be passed as an activity result for request: $requestCode."
- }
- when (requestCode) {
- REQUEST_CODE_TEXT_SIZE -> {
- val textSizeResults = data.getProtoExtra(
+ readingTextSizeLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == RESULT_OK && result.data != null) {
+ val textSizeResults = result.data?.getProtoExtra(
MESSAGE_READING_TEXT_SIZE_RESULTS_KEY,
ReadingTextSizeActivityResultBundle.getDefaultInstance()
)
- optionActivityPresenter.updateReadingTextSize(textSizeResults.selectedReadingTextSize)
+ if (textSizeResults != null) {
+ optionActivityPresenter.updateReadingTextSize(textSizeResults.selectedReadingTextSize)
+ }
}
- REQUEST_CODE_AUDIO_LANGUAGE -> {
- val audioLanguage = data.getProtoExtra(
+ }
+
+ audioLanguageLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == RESULT_OK && result.data != null) {
+ val audioLanguage = result.data?.getProtoExtra(
MESSAGE_AUDIO_LANGUAGE_RESULTS_KEY, AudioLanguageActivityResultBundle.getDefaultInstance()
- ).audioLanguage
- optionActivityPresenter.updateAudioLanguage(audioLanguage)
+ )?.audioLanguage
+ if (audioLanguage != null) {
+ optionActivityPresenter.updateAudioLanguage(audioLanguage)
+ }
}
}
}
@@ -150,16 +159,14 @@ class OptionsActivity :
}
override fun routeAudioLanguageList(audioLanguage: AudioLanguage) {
- startActivityForResult(
- AudioLanguageActivity.createAudioLanguageActivityIntent(this, audioLanguage),
- REQUEST_CODE_AUDIO_LANGUAGE
+ audioLanguageLauncher.launch(
+ AudioLanguageActivity.createAudioLanguageActivityIntent(this, audioLanguage)
)
}
override fun routeReadingTextSize(readingTextSize: ReadingTextSize) {
- startActivityForResult(
- ReadingTextSizeActivity.createReadingTextSizeActivityIntent(this, readingTextSize),
- REQUEST_CODE_TEXT_SIZE
+ readingTextSizeLauncher.launch(
+ ReadingTextSizeActivity.createReadingTextSizeActivityIntent(this, readingTextSize)
)
}
diff --git a/app/src/main/java/org/oppia/android/app/options/OptionsFragment.kt b/app/src/main/java/org/oppia/android/app/options/OptionsFragment.kt
index 43a91064be6..2312e1247c2 100644
--- a/app/src/main/java/org/oppia/android/app/options/OptionsFragment.kt
+++ b/app/src/main/java/org/oppia/android/app/options/OptionsFragment.kt
@@ -21,12 +21,6 @@ const val MESSAGE_READING_TEXT_SIZE_RESULTS_KEY = "OptionsFragment.message_readi
/** OnActivity result key to access [AudioLanguage] result. */
const val MESSAGE_AUDIO_LANGUAGE_RESULTS_KEY = "OptionsFragment.message_audio_language"
-/** Request code for [ReadingTextSize]. */
-const val REQUEST_CODE_TEXT_SIZE = 1
-
-/** Request code for [AudioLanguage]. */
-const val REQUEST_CODE_AUDIO_LANGUAGE = 3
-
/** Arguments key for OptionsFragment. */
const val OPTIONS_FRAGMENT_ARGUMENTS_KEY = "OptionsFragment.arguments"
diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt
index 6c8c14d87ae..e88525841b5 100644
--- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt
@@ -67,7 +67,7 @@ class ReadingTextSizeActivity : InjectableAutoLocalizedAppCompatActivity() {
val intent = Intent().apply {
putProtoExtra(MESSAGE_READING_TEXT_SIZE_RESULTS_KEY, resultBundle)
}
- setResult(REQUEST_CODE_TEXT_SIZE, intent)
+ setResult(RESULT_OK, intent)
finish()
}
diff --git a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt
index e90eda75749..1acff5fd581 100644
--- a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt
@@ -3,6 +3,7 @@ package org.oppia.android.app.profile
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
import org.oppia.android.app.model.AddProfileActivityParams
@@ -32,6 +33,14 @@ class AddProfileActivity : InjectableAutoLocalizedAppCompatActivity() {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)
addProfileFragmentPresenter.handleOnCreate()
+
+ addProfileFragmentPresenter.resultLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == RESULT_OK) {
+ addProfileFragmentPresenter.updateProfileAvatar(result.data)
+ }
+ }
}
override fun onSupportNavigateUp(): Boolean {
@@ -42,11 +51,6 @@ class AddProfileActivity : InjectableAutoLocalizedAppCompatActivity() {
return false
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- addProfileFragmentPresenter.handleOnActivityResult(requestCode, resultCode, data)
- }
-
override fun onDestroy() {
super.onDestroy()
addProfileFragmentPresenter.dismissAlertDialog()
diff --git a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivityPresenter.kt
index eff1305e68e..ef00c6d337d 100644
--- a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivityPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivityPresenter.kt
@@ -1,6 +1,5 @@
package org.oppia.android.app.profile
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff
@@ -10,6 +9,7 @@ import android.provider.MediaStore
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
+import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
@@ -37,8 +37,6 @@ import org.oppia.android.util.platformparameter.EnableDownloadsSupport
import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject
-const val GALLERY_INTENT_RESULT_CODE = 1
-
/** The presenter for [AddProfileActivity]. */
@ActivityScope
class AddProfileActivityPresenter @Inject constructor(
@@ -55,6 +53,11 @@ class AddProfileActivityPresenter @Inject constructor(
private var checkboxStateClicked = false
private var inputtedConfirmPin = false
private lateinit var alertDialog: AlertDialog
+ private val galleryIntent = Intent(
+ Intent.ACTION_PICK,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ )
+ lateinit var resultLauncher: ActivityResultLauncher
fun handleOnCreate() {
val binding = DataBindingUtil.setContentView(
@@ -184,25 +187,23 @@ class AddProfileActivityPresenter @Inject constructor(
}
}
- fun handleOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == GALLERY_INTENT_RESULT_CODE && resultCode == Activity.RESULT_OK) {
- data?.let {
- selectedImage = data.data
- Glide.with(activity)
- .load(selectedImage)
- .centerCrop()
- .apply(RequestOptions.circleCropTransform())
- .into(uploadImageView)
- }
+ fun updateProfileAvatar(data: Intent?) {
+ data?.let {
+ selectedImage = data.data
+ Glide.with(activity)
+ .load(selectedImage)
+ .centerCrop()
+ .apply(RequestOptions.circleCropTransform())
+ .into(uploadImageView)
}
}
private fun addButtonListeners(binding: AddProfileActivityBinding) {
uploadImageView.setOnClickListener {
- openGalleryIntent()
+ resultLauncher.launch(galleryIntent)
}
binding.addProfileActivityEditUserImageView.setOnClickListener {
- openGalleryIntent()
+ resultLauncher.launch(galleryIntent)
}
binding.addProfileActivityCreateButton.setOnClickListener {
@@ -249,11 +250,6 @@ class AddProfileActivityPresenter @Inject constructor(
}
}
- private fun openGalleryIntent() {
- val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
- activity.startActivityForResult(galleryIntent, GALLERY_INTENT_RESULT_CODE)
- }
-
private fun checkInputsAreValid(name: String, pin: String, confirmPin: String): Boolean {
var failed = false
if (name.isEmpty()) {
diff --git a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt
index be7e104eaf4..266a88636fa 100644
--- a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt
@@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import org.oppia.android.R
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.model.PinPasswordActivityParams
import org.oppia.android.app.model.ProfileId
@@ -20,6 +21,8 @@ import org.oppia.android.util.accessibility.AccessibilityService
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import org.oppia.android.util.extensions.getProtoExtra
+import org.oppia.android.util.platformparameter.EnableMultipleClassrooms
+import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject
import kotlin.system.exitProcess
@@ -33,9 +36,11 @@ class PinPasswordActivityPresenter @Inject constructor(
private val lifecycleSafeTimerFactory: LifecycleSafeTimerFactory,
private val pinViewModel: PinPasswordViewModel,
private val resourceHandler: AppLanguageResourceHandler,
- private val accessibilityService: AccessibilityService
+ private val accessibilityService: AccessibilityService,
+ @EnableMultipleClassrooms private val enableMultipleClassrooms: PlatformParameterValue,
) {
- private var profileId = -1
+ private var internalProfileId = -1
+ private var profileId = ProfileId.getDefaultInstance()
private lateinit var alertDialog: AlertDialog
private var confirmedDeletion = false
@@ -46,13 +51,14 @@ class PinPasswordActivityPresenter @Inject constructor(
)
val adminPin = args?.adminPin
- profileId = args?.internalProfileId ?: -1
+ internalProfileId = args?.internalProfileId ?: -1
+ profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build()
val binding = DataBindingUtil.setContentView(
activity,
R.layout.pin_password_activity
)
- pinViewModel.setProfileId(profileId)
+ pinViewModel.setProfileId(internalProfileId)
binding.apply {
lifecycleOwner = activity
viewModel = pinViewModel
@@ -95,15 +101,16 @@ class PinPasswordActivityPresenter @Inject constructor(
) {
if (inputtedPin == pinViewModel.correctPin.get()) {
profileManagementController
- .loginToProfile(
- ProfileId.newBuilder().setInternalId(profileId).build()
- ).toLiveData()
- .observe(
+ .loginToProfile(profileId).toLiveData().observe(
activity,
{
if (it is AsyncResult.Success) {
- val profileid = ProfileId.newBuilder().setInternalId(profileId).build()
- activity.startActivity((HomeActivity.createHomeActivity(activity, profileid)))
+ activity.startActivity(
+ if (enableMultipleClassrooms.value)
+ ClassroomListActivity.createClassroomListActivity(activity, profileId)
+ else
+ HomeActivity.createHomeActivity(activity, profileId)
+ )
}
}
)
@@ -157,7 +164,7 @@ class PinPasswordActivityPresenter @Inject constructor(
) as DialogFragment
).dismiss()
val dialogFragment = ResetPinDialogFragment.newInstance(
- profileId,
+ internalProfileId,
pinViewModel.name.get()!!
)
dialogFragment.showNow(activity.supportFragmentManager, TAG_RESET_PIN_DIALOG)
diff --git a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt
index a138df9b575..371bdfc9037 100644
--- a/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/profile/ProfileChooserFragmentPresenter.kt
@@ -14,6 +14,7 @@ import androidx.lifecycle.Transformations
import androidx.recyclerview.widget.GridLayoutManager
import org.oppia.android.R
import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.model.Profile
@@ -28,6 +29,8 @@ import org.oppia.android.domain.oppialogger.analytics.AnalyticsController
import org.oppia.android.domain.profile.ProfileManagementController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
+import org.oppia.android.util.platformparameter.EnableMultipleClassrooms
+import org.oppia.android.util.platformparameter.PlatformParameterValue
import org.oppia.android.util.statusbar.StatusBarColor
import javax.inject.Inject
@@ -68,7 +71,8 @@ class ProfileChooserFragmentPresenter @Inject constructor(
private val profileManagementController: ProfileManagementController,
private val oppiaLogger: OppiaLogger,
private val analyticsController: AnalyticsController,
- private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory
+ private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory,
+ @EnableMultipleClassrooms private val enableMultipleClassrooms: PlatformParameterValue,
) {
private lateinit var binding: ProfileChooserFragmentBinding
val hasProfileEverBeenAddedValue = ObservableField(true)
@@ -175,14 +179,15 @@ class ProfileChooserFragmentPresenter @Inject constructor(
fragment,
Observer {
if (it is AsyncResult.Success) {
- activity.startActivity(
- (
- HomeActivity.createHomeActivity(
- activity,
- model.profile.id
- )
- )
- )
+ if (enableMultipleClassrooms.value) {
+ activity.startActivity(
+ ClassroomListActivity.createClassroomListActivity(activity, model.profile.id)
+ )
+ } else {
+ activity.startActivity(
+ HomeActivity.createHomeActivity(activity, model.profile.id)
+ )
+ }
}
}
)
diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivity.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivity.kt
index 0dcb5c6e39f..b0066ad36f5 100644
--- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivity.kt
@@ -3,6 +3,8 @@ package org.oppia.android.app.profileprogress
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
import org.oppia.android.app.activity.route.ActivityRouter
@@ -38,11 +40,21 @@ class ProfileProgressActivity :
@Inject
lateinit var resourceHandler: AppLanguageResourceHandler
+ private lateinit var resultLauncher: ActivityResultLauncher
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)
internalProfileId = intent?.extractCurrentUserProfileId()?.internalId ?: -1
profileProgressActivityPresenter.handleOnCreate(internalProfileId)
+
+ resultLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == RESULT_OK) {
+ profileProgressActivityPresenter.updateProfileAvatar(result.data)
+ }
+ }
}
override fun routeToRecentlyPlayed(recentlyPlayedActivityTitle: RecentlyPlayedActivityTitle) {
@@ -101,11 +113,7 @@ class ProfileProgressActivity :
}
override fun showGalleryForProfilePicture() {
- profileProgressActivityPresenter.openGalleryIntent()
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- profileProgressActivityPresenter.handleOnActivityResult(data)
+ val galleryIntent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "image/*" }
+ resultLauncher.launch(galleryIntent)
}
}
diff --git a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivityPresenter.kt
index 562e38f3d9f..836f5cfc0d5 100644
--- a/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivityPresenter.kt
+++ b/app/src/main/java/org/oppia/android/app/profileprogress/ProfileProgressActivityPresenter.kt
@@ -7,7 +7,6 @@ import androidx.appcompat.widget.Toolbar
import org.oppia.android.R
import org.oppia.android.app.activity.ActivityScope
import org.oppia.android.app.model.ProfileId
-import org.oppia.android.app.profile.GALLERY_INTENT_RESULT_CODE
import org.oppia.android.domain.profile.ProfileManagementController
import javax.inject.Inject
@@ -50,18 +49,11 @@ class ProfileProgressActivityPresenter @Inject constructor(
) as ProfileProgressFragment?
}
- fun openGalleryIntent() {
- val galleryIntent = Intent(Intent.ACTION_GET_CONTENT).apply { type = "image/*" }
- activity.startActivityForResult(galleryIntent, GALLERY_INTENT_RESULT_CODE)
- }
-
- fun handleOnActivityResult(intent: Intent?) {
- intent?.let {
- profileManagementController.updateProfileAvatar(
- profileId,
- intent.data,
- /* colorRgb= */ 10710042
- )
- }
+ fun updateProfileAvatar(intent: Intent?) {
+ profileManagementController.updateProfileAvatar(
+ profileId,
+ intent?.data,
+ /* colorRgb= */ 10710042
+ )
}
}
diff --git a/app/src/main/res/drawable/ic_english.xml b/app/src/main/res/drawable/ic_english.xml
new file mode 100644
index 00000000000..ebd5f248582
--- /dev/null
+++ b/app/src/main/res/drawable/ic_english.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_maths.xml b/app/src/main/res/drawable/ic_maths.xml
new file mode 100644
index 00000000000..0abcf106ad1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_maths.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_science.xml b/app/src/main/res/drawable/ic_science.xml
new file mode 100644
index 00000000000..fc7b811a7ef
--- /dev/null
+++ b/app/src/main/res/drawable/ic_science.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/classroom_list_activity.xml b/app/src/main/res/layout/classroom_list_activity.xml
new file mode 100644
index 00000000000..9143cfc29e1
--- /dev/null
+++ b/app/src/main/res/layout/classroom_list_activity.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/classroom_list_fragment.xml b/app/src/main/res/layout/classroom_list_fragment.xml
new file mode 100644
index 00000000000..7a69ab537b6
--- /dev/null
+++ b/app/src/main/res/layout/classroom_list_fragment.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
index 578d3179178..49afd330bdf 100644
--- a/app/src/main/res/values-land/dimens.xml
+++ b/app/src/main/res/values-land/dimens.xml
@@ -348,6 +348,10 @@
14sp
8dp
+
+ 8dp
+ 8dp
+
104dp
96dp
@@ -542,4 +546,12 @@
108dp
+
+
+ 72dp
+ 20dp
+ 72dp
+ 12dp
+ 150dp
+ 60dp
diff --git a/app/src/main/res/values-night/color_palette.xml b/app/src/main/res/values-night/color_palette.xml
index fe0d284624e..7c9347d590a 100644
--- a/app/src/main/res/values-night/color_palette.xml
+++ b/app/src/main/res/values-night/color_palette.xml
@@ -231,4 +231,8 @@
@color/color_def_dark_green
@color/color_def_accessible_grey
+
+ @color/color_def_greenish_black
+ @color/color_def_white
+ @color/color_def_greenish_black
diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml
index d24a8c3c4e8..47caee25f05 100644
--- a/app/src/main/res/values-sw600dp-land/dimens.xml
+++ b/app/src/main/res/values-sw600dp-land/dimens.xml
@@ -362,6 +362,10 @@
16sp
4dp
+
+ 0dp
+ 32dp
+
8dp
0dp
@@ -507,4 +511,12 @@
192dp
192dp
4dp
+
+
+ 72dp
+ 20dp
+ 72dp
+ 12dp
+ 150dp
+ 182dp
diff --git a/app/src/main/res/values-sw600dp-port/dimens.xml b/app/src/main/res/values-sw600dp-port/dimens.xml
index 325532ede04..d2c15feccbc 100644
--- a/app/src/main/res/values-sw600dp-port/dimens.xml
+++ b/app/src/main/res/values-sw600dp-port/dimens.xml
@@ -367,6 +367,10 @@
16sp
4dp
+
+ 8dp
+ 24dp
+
8dp
0dp
@@ -524,4 +528,12 @@
72dp
32dp
8dp
+
+
+ 60dp
+ 20dp
+ 60dp
+ 12dp
+ 150dp
+ 182dp
diff --git a/app/src/main/res/values/color_defs.xml b/app/src/main/res/values/color_defs.xml
index 9fdaccaaf51..d6144b1d5ed 100644
--- a/app/src/main/res/values/color_defs.xml
+++ b/app/src/main/res/values/color_defs.xml
@@ -144,4 +144,6 @@
#E8E8E8
#E2F5F4
#25000000
+ #EDF6F5
+ #172B28
diff --git a/app/src/main/res/values/color_palette.xml b/app/src/main/res/values/color_palette.xml
index 36cf4fa64ef..649cc14c2d9 100644
--- a/app/src/main/res/values/color_palette.xml
+++ b/app/src/main/res/values/color_palette.xml
@@ -271,4 +271,9 @@
@color/color_def_oppia_green
@color/color_def_accessible_grey
+
+ @color/color_def_greenish_white
+ @color/color_def_green
+ @color/color_def_greenish_white
+ @color/color_def_persian_blue
diff --git a/app/src/main/res/values/component_colors.xml b/app/src/main/res/values/component_colors.xml
index 3de87773381..fd03df0ed14 100644
--- a/app/src/main/res/values/component_colors.xml
+++ b/app/src/main/res/values/component_colors.xml
@@ -307,4 +307,11 @@
@color/color_palette_white_text_color
@color/color_palette_onboarding_primary_color
@color/color_palette_onboarding_primary_text_color
+
+ @color/color_palette_classroom_card_color
+ @color/color_palette_classroom_shared_text_color
+ @color/color_palette_classroom_shared_text_color
+ @color/color_palette_classroom_shared_text_color
+ @color/color_palette_classroom_topic_list_background_color
+ @color/color_palette_classroom_promoted_list_classroom_label_color
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index fe44bb45857..2d53f641df9 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -387,6 +387,8 @@
12dp
+ 8dp
+ 14sp
88dp
@@ -453,6 +455,7 @@
56dp
32dp
12dp
+ 18dp
16dp
@@ -509,6 +512,7 @@
28dp
28dp
4dp
+ 24sp
@@ -527,11 +531,23 @@
8dp
8dp
+ 8dp
+ 8dp
+ 16dp
+ 8dp
+ 4dp
280sp
280sp
16sp
- 8dp
+ 18sp
+ 14sp
+ 14sp
+
+
+ 8dp
+ 8dp
+ 4dp
40dp
@@ -633,6 +649,11 @@
28dp
28dp
+ 24dp
+ 18sp
+ 14sp
+ 8sp
+ 12dp
132dp
@@ -802,4 +823,18 @@
16dp
28dp
36dp
+
+
+ 28dp
+ 20dp
+ 28dp
+ 12dp
+ 18sp
+ 150dp
+ 182dp
+ 18sp
+ 20dp
+ 20dp
+ 80dp
+ 4dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a4978ce466f..409c502e500 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -326,6 +326,9 @@
Home
From now, you can see lessons recommended for you here.
Select a Topic to Start
+
+ Home
+ Classrooms
Profiles
diff --git a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt
new file mode 100644
index 00000000000..845096edd39
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListActivityTest.kt
@@ -0,0 +1,218 @@
+package org.oppia.android.app.classroom
+
+import android.app.Application
+import android.content.Context
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.intent.Intents
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.model.ProfileId
+import org.oppia.android.app.model.ScreenName
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.firebase.TestAuthenticationModule
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.GlideImageLoaderModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [ClassroomListActivity]. */
+@RunWith(AndroidJUnit4::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+@Config(
+ application = ClassroomListActivityTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+class ClassroomListActivityTest {
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @Inject
+ lateinit var context: Context
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @Before
+ fun setUp() {
+ Intents.init()
+ setUpTestApplicationComponent()
+ }
+
+ @After
+ fun tearDown() {
+ Intents.release()
+ }
+
+ @Test
+ fun testActivity_createIntent_verifyScreenNameInIntent() {
+ val screenName = ClassroomListActivity
+ .createClassroomListActivity(
+ context,
+ ProfileId.newBuilder().setInternalId(0).build()
+ )
+ .extractCurrentAppScreenName()
+ assertThat(screenName).isEqualTo(ScreenName.CLASSROOM_LIST_ACTIVITY)
+ }
+
+ @Test
+ fun testClassroomListActivity_hasCorrectActivityLabel() {
+ launchClassroomListActivity().use { scenario ->
+ lateinit var title: CharSequence
+ scenario?.onActivity { activity -> title = activity.title }
+
+ assertThat(title).isEqualTo(context.getString(R.string.classroom_list_activity_title))
+ }
+ }
+
+ private fun launchClassroomListActivity():
+ ActivityScenario? {
+ val scenario = ActivityScenario.launch(
+ ClassroomListActivity.createClassroomListActivity(
+ context,
+ ProfileId.newBuilder().setInternalId(0).build()
+ )
+ )
+ testCoroutineDispatchers.runCurrent()
+ return scenario
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them.
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class,
+ PlatformParameterModule::class, PlatformParameterSingletonModule::class,
+ TestDispatcherModule::class, ApplicationModule::class,
+ LoggerModule::class, ContinueModule::class, FractionInputModule::class,
+ ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
+ NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
+ DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class,
+ GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class,
+ HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class,
+ AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class,
+ ExpirationMetaDataRetrieverModule::class,
+ ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class,
+ ApplicationStartupListenerModule::class, LogReportWorkerModule::class,
+ HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
+ MathEquationInputModule::class, SplitScreenInteractionModule::class,
+ LoggingIdentifierModule::class, ApplicationLifecycleModule::class,
+ SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class,
+ TestAuthenticationModule::class
+ ]
+ )
+
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder : ApplicationComponent.Builder
+
+ fun inject(classroomListActivityTest: ClassroomListActivityTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerClassroomListActivityTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build() as TestApplicationComponent
+ }
+
+ fun inject(classroomListActivityTest: ClassroomListActivityTest) {
+ component.inject(classroomListActivityTest)
+ }
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt
new file mode 100644
index 00000000000..87a5c58fd0a
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/classroom/ClassroomListFragmentTest.kt
@@ -0,0 +1,848 @@
+package org.oppia.android.app.classroom
+
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertTextContains
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onChildAt
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
+import androidx.compose.ui.test.performScrollToNode
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.pressBack
+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.matcher.ViewMatchers.isRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.protobuf.MessageLite
+import dagger.Component
+import org.hamcrest.Description
+import org.hamcrest.Matcher
+import org.hamcrest.TypeSafeMatcher
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.classroom.classroomlist.CLASSROOM_HEADER_TEST_TAG
+import org.oppia.android.app.classroom.classroomlist.CLASSROOM_LIST_TEST_TAG
+import org.oppia.android.app.classroom.promotedlist.PROMOTED_STORY_LIST_HEADER_TEST_TAG
+import org.oppia.android.app.classroom.promotedlist.PROMOTED_STORY_LIST_TEST_TAG
+import org.oppia.android.app.classroom.topiclist.ALL_TOPICS_HEADER_TEST_TAG
+import org.oppia.android.app.classroom.welcome.WELCOME_TEST_TAG
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.home.recentlyplayed.RecentlyPlayedActivity
+import org.oppia.android.app.model.ProfileId
+import org.oppia.android.app.model.TopicActivityParams
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.topic.TopicActivity
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.app.utility.OrientationChangeAction.Companion.orientationLandscape
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.topic.FRACTIONS_STORY_ID_0
+import org.oppia.android.domain.topic.FRACTIONS_TOPIC_ID
+import org.oppia.android.domain.topic.TEST_STORY_ID_0
+import org.oppia.android.domain.topic.TEST_TOPIC_ID_0
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestImageLoaderModule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.data.DataProviderTestMonitor
+import org.oppia.android.testing.firebase.TestAuthenticationModule
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.platformparameter.TestPlatformParameterModule
+import org.oppia.android.testing.profile.ProfileTestHelper
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.story.StoryProgressTestHelper
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClock
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.extensions.getProtoExtra
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.locale.OppiaLocale
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+// Time: Tue Apr 23 2019 23:22:00
+private const val EVENING_TIMESTAMP = 1556061720000
+
+// Time: Wed Apr 24 2019 08:22:00
+private const val MORNING_TIMESTAMP = 1556094120000
+
+// Time: Tue Apr 23 2019 14:22:00
+private const val AFTERNOON_TIMESTAMP = 1556029320000
+
+/** Tests for [ClassroomListFragment]. */
+// FunctionName: test names are conventionally named with underscores.
+@Suppress("FunctionName")
+@RunWith(AndroidJUnit4::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+@Config(
+ application = ClassroomListFragmentTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+class ClassroomListFragmentTest {
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @get:Rule
+ val composeRule = createAndroidComposeRule()
+
+ @Inject
+ lateinit var context: Context
+
+ @Inject
+ lateinit var fakeOppiaClock: FakeOppiaClock
+
+ @Inject
+ lateinit var profileTestHelper: ProfileTestHelper
+
+ @Inject
+ lateinit var machineLocale: OppiaLocale.MachineLocale
+
+ @Inject
+ lateinit var storyProgressTestHelper: StoryProgressTestHelper
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @Inject
+ lateinit var dataProviderTestMonitor: DataProviderTestMonitor.Factory
+
+ private val internalProfileId: Int = 0
+ private lateinit var profileId: ProfileId
+
+ @Before
+ fun setUp() {
+ Intents.init()
+ setUpTestApplicationComponent()
+ profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build()
+ testCoroutineDispatchers.registerIdlingResource()
+ profileTestHelper.initializeProfiles()
+ }
+
+ @After
+ fun tearDown() {
+ testCoroutineDispatchers.unregisterIdlingResource()
+ Intents.release()
+ }
+
+ @Test
+ fun testFragment_allComponentsAreDisplayed() {
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_HEADER_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(ALL_TOPICS_HEADER_TEST_TAG).assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_loginTwice_allComponentsAreDisplayed() {
+ logIntoAdminTwice()
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_HEADER_TEST_TAG).assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).assertIsDisplayed()
+
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).performScrollToNode(
+ hasTestTag(ALL_TOPICS_HEADER_TEST_TAG)
+ )
+ composeRule.onNodeWithTag(ALL_TOPICS_HEADER_TEST_TAG).assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_withAdminProfile_configChange_profileNameIsDisplayed() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME)
+ fakeOppiaClock.setCurrentTimeToSameDateTime(EVENING_TIMESTAMP)
+
+ composeRule.activity.recreate()
+ testCoroutineDispatchers.runCurrent()
+
+ onView(isRoot()).perform(orientationLandscape())
+
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG)
+ .assertTextContains("Good evening, Admin!")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_morningTimestamp_goodMorningMessageIsDisplayed_withAdminProfileName() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME)
+ fakeOppiaClock.setCurrentTimeToSameDateTime(MORNING_TIMESTAMP)
+
+ composeRule.activity.recreate()
+ testCoroutineDispatchers.runCurrent()
+
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG)
+ .assertTextContains("Good morning, Admin!")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_afternoonTimestamp_goodAfternoonMessageIsDisplayed_withAdminProfileName() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME)
+ fakeOppiaClock.setCurrentTimeToSameDateTime(AFTERNOON_TIMESTAMP)
+
+ composeRule.activity.recreate()
+ testCoroutineDispatchers.runCurrent()
+
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG)
+ .assertTextContains("Good afternoon, Admin!")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_eveningTimestamp_goodEveningMessageIsDisplayed_withAdminProfileName() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_FIXED_FAKE_TIME)
+ fakeOppiaClock.setCurrentTimeToSameDateTime(EVENING_TIMESTAMP)
+
+ composeRule.activity.recreate()
+ testCoroutineDispatchers.runCurrent()
+
+ composeRule.onNodeWithTag(WELCOME_TEST_TAG)
+ .assertTextContains("Good evening, Admin!")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_logUserInFirstTime_checkPromotedStoriesIsNotDisplayed() {
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).assertDoesNotExist()
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).assertDoesNotExist()
+ }
+
+ @Test
+ fun testFragment_recentlyPlayedStoriesTextIsDisplayed() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedRatiosStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recently_played_stories))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_viewAllTextIsDisplayed() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedRatiosStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedTestTopic0Story0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(1)
+ .assertTextContains(
+ machineLocale.run { context.getString(R.string.view_all).toMachineUpperCase() }
+ )
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_storiesPlayedOneWeekAgo_displaysLastPlayedStoriesText() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = true
+ )
+ testCoroutineDispatchers.runCurrent()
+ storyProgressTestHelper.markInProgressSavedRatiosStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = true
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.last_played_stories))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_markStory0DoneForFraction_displaysRecommendedStories() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedFractionsTopic(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).apply {
+ onChildAt(0)
+ .assertTextContains("Prototype Exploration")
+ .assertTextContains("FIRST TEST TOPIC")
+ .assertTextContains("SCIENCE")
+ .assertIsDisplayed()
+
+ onChildAt(1)
+ .assertTextContains("What is a Ratio?")
+ .assertTextContains("RATIOS AND PROPORTIONAL REASONING")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+ }
+ }
+
+ @Test
+ fun testFragment_markCompletedRatiosStory0_recommendsFractions() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedRatiosStory0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).onChildAt(0)
+ .assertTextContains("What is a Fraction?")
+ .assertTextContains("FRACTIONS")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_noTopicProgress_initialRecommendationFractionsAndRatiosIsCorrect() {
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).apply {
+ onChildAt(0)
+ .assertTextContains("What is a Fraction?")
+ .assertTextContains("FRACTIONS")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+
+ onChildAt(1)
+ .assertTextContains("What is a Ratio?")
+ .assertTextContains("RATIOS AND PROPORTIONAL REASONING")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+ }
+ }
+
+ @Test
+ fun testFragment_forPromotedActivityList_hideViewAll() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(1)
+ .assertDoesNotExist()
+ }
+
+ @Test
+ @Ignore("Temporarily ignored as the test is failing.")
+ // TODO(#5344): Update the logic or fix the test.
+ fun testFragment_markStory0DoneForRatiosAndFirstTestTopic_displaysRecommendedStories() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedTestTopic0Story0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markCompletedRatiosStory0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).onChildAt(0)
+ .assertTextContains("Fifth Exploration")
+ .assertTextContains("SECOND TEST TOPIC")
+ .assertTextContains("SCIENCE")
+ .assertIsDisplayed()
+ }
+
+ /*
+ * # Dependency graph:
+ *
+ * Fractions
+ * |
+ * |
+ * v
+ * Test topic 0 Ratios
+ * \ /
+ * \ /
+ * -----> Test topic 1 <----
+ *
+ * # Logic for recommendation system
+ *
+ * We always recommend the next topic that all dependencies are completed for. If a topic with
+ * prerequisites is completed out-of-order (e.g. test topic 1 above) then we assume fractions is
+ * already done. In the same way, finishing test topic 2 means there's nothing else to recommend.
+ */
+ @Test
+ fun testFragment_markStory0DonePlayStory1FirstTestTopic_playFractionsTopic_orderIsCorrect() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedTestTopic0Story0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedTestTopic1Story0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.stories_for_you))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).apply {
+ onChildAt(0)
+ .assertTextContains("Fifth Exploration")
+ .assertTextContains("SECOND TEST TOPIC")
+ .assertTextContains("SCIENCE")
+ .assertIsDisplayed()
+
+ onChildAt(1)
+ .assertTextContains("What is a Fraction?")
+ .assertTextContains("FRACTIONS")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+
+ // TODO(#5344): 'What is a Ratio?' story should be promoted.
+ onChildAt(2)
+ .assertDoesNotExist()
+ }
+ }
+
+ @Test
+ @Ignore
+ // TODO(#5344): Update logic or fix the test.
+ fun testFragment_markStory0DoneFirstTestTopic_recommendedStoriesIsCorrect() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedTestTopic0Story0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).onChildAt(1)
+ .assertTextContains("What is a Ratio?")
+ .assertTextContains("RATIOS AND PROPORTIONAL REASONING")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_markStory0DoneForFractions_recommendedStoriesIsCorrect() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markCompletedFractionsStory0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.activity.recreate()
+ testCoroutineDispatchers.runCurrent()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.recommended_stories))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).apply {
+ onChildAt(0)
+ .assertTextContains("Prototype Exploration")
+ .assertTextContains("FIRST TEST TOPIC")
+ .assertTextContains("SCIENCE")
+ .assertIsDisplayed()
+
+ onChildAt(1)
+ .assertTextContains("What is a Ratio?")
+ .assertTextContains("RATIOS AND PROPORTIONAL REASONING")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+ }
+ }
+
+ @Test
+ fun testFragment_clickViewAll_opensRecentlyPlayedActivity() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedRatiosStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markInProgressSavedTestTopic1(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(1)
+ .assertIsDisplayed()
+ .performClick()
+
+ intended(hasComponent(RecentlyPlayedActivity::class.java.name))
+ }
+
+ @Test
+ fun testFragment_markFullProgressForFractions_playRatios_displaysRecommendedStories() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedRatiosStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ storyProgressTestHelper.markCompletedFractionsStory0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_HEADER_TEST_TAG).onChildAt(0)
+ .assertTextContains(context.getString(R.string.stories_for_you))
+ .assertIsDisplayed()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).apply {
+ onChildAt(0)
+ .assertTextContains("What is a Ratio?")
+ .assertTextContains("RATIOS AND PROPORTIONAL REASONING")
+ .assertTextContains("MATHS")
+ .assertIsDisplayed()
+
+ onChildAt(1)
+ .assertTextContains("Prototype Exploration")
+ .assertTextContains("FIRST TEST TOPIC")
+ .assertTextContains("SCIENCE")
+ .assertIsDisplayed()
+ }
+ }
+
+ @Test
+ fun testFragment_clickPromotedStory_opensTopicActivity() {
+ fakeOppiaClock.setFakeTimeMode(FakeOppiaClock.FakeTimeMode.MODE_UPTIME_MILLIS)
+ storyProgressTestHelper.markInProgressSavedFractionsStory0Exp0(
+ profileId = profileId,
+ timestampOlderThanOneWeek = false
+ )
+ logIntoAdminTwice()
+
+ composeRule.onNodeWithTag(PROMOTED_STORY_LIST_TEST_TAG).onChildAt(0)
+ .assertIsDisplayed()
+ .performClick()
+
+ testCoroutineDispatchers.runCurrent()
+
+ val args = TopicActivityParams.newBuilder().apply {
+ this.topicId = FRACTIONS_TOPIC_ID
+ this.storyId = FRACTIONS_STORY_ID_0
+ }.build()
+ intended(hasComponent(TopicActivity::class.java.name))
+ intended(hasProtoExtra(TopicActivity.TOPIC_ACTIVITY_PARAMS_KEY, args))
+ }
+
+ @Test
+ fun testFragment_clickTopicSummary_opensTopicActivityThroughPlayIntent() {
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(0).performClick()
+ testCoroutineDispatchers.runCurrent()
+
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("First Test Topic")
+ .assertTextContains("3 Lessons")
+ .assertIsDisplayed()
+ .performClick()
+
+ testCoroutineDispatchers.runCurrent()
+
+ val args = TopicActivityParams.newBuilder().apply {
+ this.topicId = TEST_TOPIC_ID_0
+ this.storyId = TEST_STORY_ID_0
+ }.build()
+ intended(hasComponent(TopicActivity::class.java.name))
+ intended(hasProtoExtra(TopicActivity.TOPIC_ACTIVITY_PARAMS_KEY, args))
+ }
+
+ @Test
+ fun testFragment_scrollToBottom_classroomListSticks_classroomListIsVisible() {
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).performScrollToIndex(3)
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_switchClassroom_topicListUpdatesCorrectly() {
+ // Click on Science classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(0).performClick()
+ testCoroutineDispatchers.runCurrent()
+
+ // Check that Science classroom's topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("First Test Topic")
+ .assertTextContains("3 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Second Test Topic")
+ .assertTextContains("1 Lesson")
+ .assertIsDisplayed()
+
+ // Click on Maths classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(1).performClick()
+ testCoroutineDispatchers.runCurrent()
+
+ // Check that Maths classroom's topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("Fractions")
+ .assertTextContains("2 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Ratios and Proportional Reasoning")
+ .assertTextContains("4 Lessons")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_clickOnTopicCard_returnBack_classroomSelectionIsRetained() {
+ // Click on Maths classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(1).performClick()
+ testCoroutineDispatchers.runCurrent()
+
+ // Check that Fractions topic is displayed and perform click.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("Fractions")
+ .assertTextContains("2 Lessons")
+ .assertIsDisplayed()
+ .performClick()
+
+ pressBack()
+
+ // Check that Maths classroom is selected & its topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("Fractions")
+ .assertTextContains("2 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Ratios and Proportional Reasoning")
+ .assertTextContains("4 Lessons")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun testFragment_switchClassrooms_topicListUpdatesCorrectly() {
+ profileTestHelper.logIntoAdmin()
+ testCoroutineDispatchers.runCurrent()
+
+ // Click on Science classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(0).performClick()
+ testCoroutineDispatchers.runCurrent()
+ // Check that Science classroom's topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("First Test Topic")
+ .assertTextContains("3 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Second Test Topic")
+ .assertTextContains("1 Lesson")
+ .assertIsDisplayed()
+
+ // Click on Maths classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(1).performClick()
+ testCoroutineDispatchers.runCurrent()
+ // Check that Maths classroom's topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("Fractions")
+ .assertTextContains("2 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Ratios and Proportional Reasoning")
+ .assertTextContains("4 Lessons")
+ .assertIsDisplayed()
+
+ // Click on Science classroom card.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_TEST_TAG).onChildAt(0).performClick()
+ testCoroutineDispatchers.runCurrent()
+ // Check that Science classroom's topics are displayed.
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(4)
+ .assertTextContains("First Test Topic")
+ .assertTextContains("3 Lessons")
+ .assertIsDisplayed()
+ composeRule.onNodeWithTag(CLASSROOM_LIST_SCREEN_TEST_TAG).onChildAt(5)
+ .assertTextContains("Second Test Topic")
+ .assertTextContains("1 Lesson")
+ .assertIsDisplayed()
+ }
+
+ private fun logIntoAdminTwice() {
+ dataProviderTestMonitor.waitForNextSuccessfulResult(profileTestHelper.logIntoAdmin())
+ dataProviderTestMonitor.waitForNextSuccessfulResult(profileTestHelper.logIntoAdmin())
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ private fun hasProtoExtra(keyName: String, expectedProto: T): Matcher {
+ val defaultProto = expectedProto.newBuilderForType().build()
+ return object : TypeSafeMatcher() {
+ override fun describeTo(description: Description) {
+ description.appendText("Intent with extra: $keyName and proto value: $expectedProto")
+ }
+
+ override fun matchesSafely(intent: Intent): Boolean {
+ return intent.hasExtra(keyName) &&
+ intent.getProtoExtra(keyName, defaultProto) == expectedProto
+ }
+ }
+ }
+
+ // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them.
+ @Singleton
+ @Component(
+ modules = [
+ TestPlatformParameterModule::class, RobolectricModule::class,
+ TestDispatcherModule::class, ApplicationModule::class,
+ LoggerModule::class, ContinueModule::class, FractionInputModule::class,
+ ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
+ NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
+ DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class,
+ GcsResourceModule::class, ImageParsingModule::class,
+ HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class,
+ AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class,
+ ExpirationMetaDataRetrieverModule::class,
+ ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class,
+ ApplicationStartupListenerModule::class, LogReportWorkerModule::class,
+ HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ PlatformParameterSingletonModule::class,
+ NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
+ MathEquationInputModule::class, SplitScreenInteractionModule::class,
+ LoggingIdentifierModule::class, ApplicationLifecycleModule::class,
+ SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class,
+ TestAuthenticationModule::class, TestImageLoaderModule::class,
+ ]
+ )
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder : ApplicationComponent.Builder
+
+ fun inject(classroomListFragmentTest: ClassroomListFragmentTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerClassroomListFragmentTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build() as TestApplicationComponent
+ }
+
+ fun inject(classroomListFragmentTest: ClassroomListFragmentTest) {
+ component.inject(classroomListFragmentTest)
+ }
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt
index ce7efa5e687..98eee4d1a68 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt
@@ -5,9 +5,15 @@ import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.pressBack
+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.hasExtraWithKey
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import dagger.Component
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -22,8 +28,10 @@ import org.oppia.android.app.application.ApplicationInjectorProvider
import org.oppia.android.app.application.ApplicationModule
import org.oppia.android.app.application.ApplicationStartupListenerModule
import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.model.ScreenName
import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.shim.ViewBindingShimModule
@@ -55,14 +63,16 @@ import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
-import org.oppia.android.domain.platformparameter.PlatformParameterModule
import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
import org.oppia.android.domain.question.QuestionModule
import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.RunOn
import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.TestPlatform
import org.oppia.android.testing.firebase.TestAuthenticationModule
import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.platformparameter.TestPlatformParameterModule
import org.oppia.android.testing.robolectric.RobolectricModule
import org.oppia.android.testing.threading.TestDispatcherModule
import org.oppia.android.testing.time.FakeOppiaClockModule
@@ -81,6 +91,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
import org.oppia.android.util.parser.image.GlideImageLoaderModule
import org.oppia.android.util.parser.image.ImageParsingModule
+import org.oppia.android.util.profile.PROFILE_ID_INTENT_DECORATOR
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import javax.inject.Inject
@@ -102,9 +113,15 @@ class MyDownloadsActivityTest {
@Before
fun setUp() {
+ Intents.init()
setUpTestApplicationComponent()
}
+ @After
+ fun tearDown() {
+ Intents.release()
+ }
+
private fun setUpTestApplicationComponent() {
ApplicationProvider.getApplicationContext().inject(this)
}
@@ -133,11 +150,32 @@ class MyDownloadsActivityTest {
assertThat(screenName).isEqualTo(ScreenName.MY_DOWNLOADS_ACTIVITY)
}
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testMyDownloadsActivity_classroomsFlagDisabled_pressBack_opensHomeActivity() {
+ ActivityScenario.launch(MyDownloadsActivity::class.java).use {
+ pressBack()
+ intended(hasComponent(HomeActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testMyDownloadsActivity_classroomsFlagEnabled_pressBack_opensClassroomListActivity() {
+ TestPlatformParameterModule.forceEnableMultipleClassrooms(true)
+ ActivityScenario.launch(MyDownloadsActivity::class.java).use {
+ pressBack()
+ intended(hasComponent(ClassroomListActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
@Singleton
@Component(
modules = [
RobolectricModule::class,
- PlatformParameterModule::class, PlatformParameterSingletonModule::class,
+ TestPlatformParameterModule::class, PlatformParameterSingletonModule::class,
TestDispatcherModule::class, ApplicationModule::class,
LoggerModule::class, ContinueModule::class, FractionInputModule::class,
ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt
index 54751fd2ac0..5da1ccdf4e5 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt
@@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
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.hasExtraWithKey
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.hasFocus
import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
@@ -45,6 +46,7 @@ import org.oppia.android.app.application.ApplicationInjectorProvider
import org.oppia.android.app.application.ApplicationModule
import org.oppia.android.app.application.ApplicationStartupListenerModule
import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
import org.oppia.android.app.home.HomeActivity
@@ -81,17 +83,19 @@ import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
-import org.oppia.android.domain.platformparameter.PlatformParameterModule
import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
import org.oppia.android.domain.question.QuestionModule
import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.RunOn
import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.TestPlatform
import org.oppia.android.testing.espresso.EditTextInputAction
import org.oppia.android.testing.espresso.TextInputAction.Companion.hasErrorText
import org.oppia.android.testing.espresso.TextInputAction.Companion.hasNoErrorText
import org.oppia.android.testing.firebase.TestAuthenticationModule
import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.platformparameter.TestPlatformParameterModule
import org.oppia.android.testing.profile.ProfileTestHelper
import org.oppia.android.testing.robolectric.RobolectricModule
import org.oppia.android.testing.threading.TestCoroutineDispatchers
@@ -113,6 +117,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
import org.oppia.android.util.parser.image.GlideImageLoaderModule
import org.oppia.android.util.parser.image.ImageParsingModule
+import org.oppia.android.util.profile.PROFILE_ID_INTENT_DECORATOR
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import javax.inject.Inject
@@ -229,6 +234,26 @@ class PinPasswordActivityTest {
}
}
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testPinPassword_enableClassrooms_withAdmin_inputCorrectPin_opensClassroomListActivity() {
+ TestPlatformParameterModule.forceEnableMultipleClassrooms(true)
+ ActivityScenario.launch(
+ PinPasswordActivity.createPinPasswordActivityIntent(
+ context = context,
+ adminPin = adminPin,
+ profileId = adminId
+ )
+ ).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.pin_password_input_pin_edit_text))
+ .perform(editTextInputAction.appendText("12345"))
+ testCoroutineDispatchers.runCurrent()
+ intended(hasComponent(ClassroomListActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
@Test
fun testPinPassword_withUser_inputCorrectPin_opensHomeActivity() {
ActivityScenario.launch(
@@ -246,6 +271,26 @@ class PinPasswordActivityTest {
}
}
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testPinPassword_enableClassrooms_withUser_inputCorrectPin_opensClassroomListActivity() {
+ TestPlatformParameterModule.forceEnableMultipleClassrooms(true)
+ ActivityScenario.launch(
+ PinPasswordActivity.createPinPasswordActivityIntent(
+ context = context,
+ adminPin = adminPin,
+ profileId = userId
+ )
+ ).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.pin_password_input_pin_edit_text))
+ .perform(editTextInputAction.appendText("123"))
+ testCoroutineDispatchers.runCurrent()
+ intended(hasComponent(ClassroomListActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
@Test
fun testPinPassword_withAdmin_inputWrongPin_incorrectPinShows() {
ActivityScenario.launch(
@@ -1194,7 +1239,7 @@ class PinPasswordActivityTest {
@Singleton
@Component(
modules = [
- RobolectricModule::class, PlatformParameterModule::class, TestDispatcherModule::class,
+ RobolectricModule::class, TestPlatformParameterModule::class, TestDispatcherModule::class,
ApplicationModule::class, LoggerModule::class, ContinueModule::class,
FractionInputModule::class, ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt
index a3ec9ef99e5..b24ca39d366 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt
@@ -15,7 +15,6 @@ import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
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.intent.matcher.IntentMatchers.hasExtraWithKey
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
@@ -46,8 +45,10 @@ import org.oppia.android.app.application.ApplicationInjectorProvider
import org.oppia.android.app.application.ApplicationModule
import org.oppia.android.app.application.ApplicationStartupListenerModule
import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.classroom.ClassroomListActivity
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
import org.oppia.android.app.profile.AdminAuthActivity.Companion.ADMIN_AUTH_ACTIVITY_PARAMS_KEY
import org.oppia.android.app.profile.AdminPinActivity.Companion.ADMIN_PIN_ACTIVITY_PARAMS_KEY
@@ -83,15 +84,17 @@ import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
-import org.oppia.android.domain.platformparameter.PlatformParameterModule
import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
import org.oppia.android.domain.profile.ProfileManagementController
import org.oppia.android.domain.question.QuestionModule
import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.RunOn
import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.TestPlatform
import org.oppia.android.testing.firebase.TestAuthenticationModule
import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.platformparameter.TestPlatformParameterModule
import org.oppia.android.testing.profile.ProfileTestHelper
import org.oppia.android.testing.robolectric.RobolectricModule
import org.oppia.android.testing.threading.TestCoroutineDispatchers
@@ -113,6 +116,7 @@ import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
import org.oppia.android.util.parser.image.GlideImageLoaderModule
import org.oppia.android.util.parser.image.ImageParsingModule
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
+import org.oppia.android.util.profile.PROFILE_ID_INTENT_DECORATOR
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import javax.inject.Inject
@@ -486,6 +490,57 @@ class ProfileChooserFragmentTest {
}
}
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testProfileChooserFragment_clickProfile_opensHomeActivity() {
+ profileManagementController.addProfile(
+ name = "Admin",
+ pin = "",
+ avatarImagePath = null,
+ allowDownloadAccess = true,
+ colorRgb = -10710042,
+ isAdmin = true
+ )
+ launch(createProfileChooserActivityIntent()).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(
+ atPositionOnView(
+ recyclerViewId = R.id.profile_recycler_view,
+ position = 0,
+ targetViewId = R.id.profile_chooser_item
+ )
+ ).perform(click())
+ intended(hasComponent(HomeActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
+ @Test
+ @RunOn(TestPlatform.ESPRESSO)
+ fun testProfileChooserFragment_enableClassrooms_clickProfile_opensClassroomListActivity() {
+ TestPlatformParameterModule.forceEnableMultipleClassrooms(true)
+ profileManagementController.addProfile(
+ name = "Admin",
+ pin = "",
+ avatarImagePath = null,
+ allowDownloadAccess = true,
+ colorRgb = -10710042,
+ isAdmin = true
+ )
+ launch(createProfileChooserActivityIntent()).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(
+ atPositionOnView(
+ recyclerViewId = R.id.profile_recycler_view,
+ position = 0,
+ targetViewId = R.id.profile_chooser_item
+ )
+ ).perform(click())
+ intended(hasComponent(ClassroomListActivity::class.java.name))
+ hasExtraWithKey(PROFILE_ID_INTENT_DECORATOR)
+ }
+ }
+
private fun createProfileChooserActivityIntent(): Intent {
return ProfileChooserActivity
.createProfileChooserActivity(ApplicationProvider.getApplicationContext())
@@ -532,7 +587,7 @@ class ProfileChooserFragmentTest {
@Component(
modules = [
RobolectricModule::class,
- PlatformParameterModule::class, PlatformParameterSingletonModule::class,
+ TestPlatformParameterModule::class, PlatformParameterSingletonModule::class,
TestDispatcherModule::class, ApplicationModule::class,
LoggerModule::class, ContinueModule::class, FractionInputModule::class,
ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
diff --git a/app/src/test/AndroidManifest.xml b/app/src/test/AndroidManifest.xml
index 7e21885881d..3d2119fd3f3 100644
--- a/app/src/test/AndroidManifest.xml
+++ b/app/src/test/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
diff --git a/build.gradle b/build.gradle
index 97172637258..11bc8b058d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,12 +3,13 @@
buildscript {
ext.kotlin_version = '1.6.10'
ext.fragment_version = '1.2.0-rc01'
+ ext.compose_version = '1.1.1'
repositories {
google()
gradlePluginPortal()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.1'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17'
classpath 'com.google.gms:google-services:4.3.3'
diff --git a/build_flavors.bzl b/build_flavors.bzl
index 0440b171a55..68c4b6f469a 100644
--- a/build_flavors.bzl
+++ b/build_flavors.bzl
@@ -59,7 +59,7 @@ _FLAVOR_METADATA = {
},
"dev_kitkat": {
"manifest": "//app:src/main/AndroidManifest.xml",
- "min_sdk_version": 19,
+ "min_sdk_version": 21,
"target_sdk_version": 33,
"multidex": "manual_main_dex",
"main_dex_list": _MAIN_DEX_LIST_TARGET_KITKAT,
@@ -88,7 +88,7 @@ _FLAVOR_METADATA = {
},
"alpha_kitkat": {
"manifest": "//app:src/main/AndroidManifest.xml",
- "min_sdk_version": 19,
+ "min_sdk_version": 21,
"target_sdk_version": 33,
"multidex": "manual_main_dex",
"main_dex_list": _MAIN_DEX_LIST_TARGET_KITKAT,
diff --git a/data/build.gradle b/data/build.gradle
index 103cea56f3b..daadba86bb8 100644
--- a/data/build.gradle
+++ b/data/build.gradle
@@ -4,10 +4,10 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 33
- buildToolsVersion "29.0.2"
+ buildToolsVersion "30.0.2"
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -77,6 +77,7 @@ dependencies {
)
testImplementation(
'androidx.test.ext:junit:1.1.1',
+ 'androidx.test.ext:truth:1.4.0',
'com.google.dagger:dagger:2.41',
'com.google.truth:truth:1.1.3',
'com.google.truth.extensions:truth-liteproto-extension:1.1.3',
diff --git a/data/src/test/java/org/oppia/android/data/backends/gae/NetworkModuleTest.kt b/data/src/test/java/org/oppia/android/data/backends/gae/NetworkModuleTest.kt
index 4a842cbe7aa..e30664000e8 100644
--- a/data/src/test/java/org/oppia/android/data/backends/gae/NetworkModuleTest.kt
+++ b/data/src/test/java/org/oppia/android/data/backends/gae/NetworkModuleTest.kt
@@ -43,36 +43,18 @@ class NetworkModuleTest {
assertThat(getTestApplication().getRetrofit()).isPresent()
}
- @Test
- @Config(sdk = [Build.VERSION_CODES.KITKAT])
- fun testRetrofitInstance_kitkat_isEmpty() {
- assertThat(getTestApplication().getRetrofit()).isAbsent()
- }
-
@Test
@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
fun testFeedbackReportingService_lollipop_isProvided() {
assertThat(getTestApplication().getFeedbackReportingService()).isPresent()
}
- @Test
- @Config(sdk = [Build.VERSION_CODES.KITKAT])
- fun testFeedbackReportingService_kitkat_isEmpty() {
- assertThat(getTestApplication().getFeedbackReportingService()).isAbsent()
- }
-
@Test
@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
fun testPlatformParameterService_lollipop_isProvided() {
assertThat(getTestApplication().getPlatformParameterService()).isPresent()
}
- @Test
- @Config(sdk = [Build.VERSION_CODES.KITKAT])
- fun testPlatformParameterService_kitkat_isEmpty() {
- assertThat(getTestApplication().getPlatformParameterService()).isAbsent()
- }
-
@Test
fun testNetworkApiKey_isEmpty() {
// The network API key is empty by default on developer builds.
diff --git a/domain/build.gradle b/domain/build.gradle
index 0d35db61ce2..5634edfa80f 100644
--- a/domain/build.gradle
+++ b/domain/build.gradle
@@ -4,10 +4,10 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 33
- buildToolsVersion "29.0.2"
+ buildToolsVersion "30.0.2"
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -107,6 +107,7 @@ dependencies {
testImplementation(
'androidx.test.espresso:espresso-core:3.2.0',
'androidx.test.ext:junit:1.1.1',
+ 'androidx.test.ext:truth:1.4.0',
'androidx.work:work-testing:2.4.0',
'com.google.dagger:dagger:2.41',
'com.google.truth.extensions:truth-liteproto-extension:1.1.3',
diff --git a/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt b/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt
index 69c2acffa78..80175c999e5 100644
--- a/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt
+++ b/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt
@@ -382,4 +382,4 @@ internal fun createClassroomThumbnail2(): LessonThumbnail {
.build()
}
-private fun String?.isNotNullOrEmpty(): Boolean = !this.isNullOrBlank() || this != "null"
+private fun String?.isNotNullOrEmpty(): Boolean = !this.isNullOrBlank() && this != "null"
diff --git a/domain/src/test/AndroidManifest.xml b/domain/src/test/AndroidManifest.xml
index 852db61ae7b..30e725f93fa 100644
--- a/domain/src/test/AndroidManifest.xml
+++ b/domain/src/test/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
diff --git a/instrumentation/BUILD.bazel b/instrumentation/BUILD.bazel
index f6ffffcdc8a..d252c301da9 100644
--- a/instrumentation/BUILD.bazel
+++ b/instrumentation/BUILD.bazel
@@ -19,7 +19,7 @@ android_binary(
manifest = "//instrumentation:src/java/AndroidManifest.xml",
manifest_values = {
"applicationId": "org.oppia.android",
- "minSdkVersion": "19",
+ "minSdkVersion": "21",
"targetSdkVersion": "33",
"versionCode": "0",
"versionName": "0.1-test",
diff --git a/model/src/main/proto/screens.proto b/model/src/main/proto/screens.proto
index e0ee3599d6d..84518cbea6d 100644
--- a/model/src/main/proto/screens.proto
+++ b/model/src/main/proto/screens.proto
@@ -158,6 +158,9 @@ enum ScreenName {
// Screen name value for the scenario when the survey activity is visible to the user.
SURVEY_ACTIVITY = 49;
+
+ // Screen name value for the scenario when the classroom list activity is visible to the user.
+ CLASSROOM_LIST_ACTIVITY = 50;
}
// Defines the current visible UI screen of the application.
diff --git a/scripts/assets/maven_dependencies.textproto b/scripts/assets/maven_dependencies.textproto
index 4f046941d1e..b5a03249897 100644
--- a/scripts/assets/maven_dependencies.textproto
+++ b/scripts/assets/maven_dependencies.textproto
@@ -1,6 +1,6 @@
maven_dependency {
- artifact_name: "androidx.activity:activity:1.1.0"
- artifact_version: "1.1.0"
+ artifact_name: "androidx.activity:activity-compose:1.4.0"
+ artifact_version: "1.4.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -10,8 +10,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.annotation:annotation:1.1.0"
- artifact_version: "1.1.0"
+ artifact_name: "androidx.activity:activity-ktx:1.4.0"
+ artifact_version: "1.4.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -21,8 +21,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.annotation:annotation-experimental:1.0.0"
- artifact_version: "1.0.0"
+ artifact_name: "androidx.activity:activity:1.4.0"
+ artifact_version: "1.4.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -32,7 +32,7 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.appcompat:appcompat-resources:1.2.0"
+ artifact_name: "androidx.annotation:annotation:1.2.0"
artifact_version: "1.2.0"
license {
license_name: "The Apache Software License, Version 2.0"
@@ -43,8 +43,30 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.appcompat:appcompat:1.2.0"
- artifact_version: "1.2.0"
+ artifact_name: "androidx.annotation:annotation-experimental:1.1.0"
+ artifact_version: "1.1.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.appcompat:appcompat-resources:1.3.1"
+ artifact_version: "1.3.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.appcompat:appcompat:1.3.1"
+ artifact_version: "1.3.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -75,6 +97,17 @@ maven_dependency {
}
}
}
+maven_dependency {
+ artifact_name: "androidx.autofill:autofill:1.0.0"
+ artifact_version: "1.0.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
maven_dependency {
artifact_name: "androidx.cardview:cardview:1.0.0"
artifact_version: "1.0.0"
@@ -97,6 +130,193 @@ maven_dependency {
}
}
}
+maven_dependency {
+ artifact_name: "androidx.compose.animation:animation-core:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.animation:animation:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.compiler:compiler:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.foundation:foundation-layout:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.foundation:foundation:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.material:material-icons-core:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.material:material-ripple:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.material:material:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.runtime:runtime-saveable:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.runtime:runtime:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui-geometry:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui-graphics:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui-text:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui-unit:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui-util:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.compose.ui:ui:1.1.1"
+ artifact_version: "1.1.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.concurrent:concurrent-futures:1.0.0"
+ artifact_version: "1.0.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
maven_dependency {
artifact_name: "androidx.constraintlayout:constraintlayout-solver:2.0.1"
artifact_version: "2.0.1"
@@ -131,8 +351,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.core:core-ktx:1.0.1"
- artifact_version: "1.0.1"
+ artifact_name: "androidx.core:core-ktx:1.1.0"
+ artifact_version: "1.1.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -142,8 +362,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.core:core:1.3.1"
- artifact_version: "1.3.1"
+ artifact_name: "androidx.core:core:1.7.0"
+ artifact_version: "1.7.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -252,8 +472,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.fragment:fragment:1.2.0"
- artifact_version: "1.2.0"
+ artifact_name: "androidx.fragment:fragment:1.3.6"
+ artifact_version: "1.3.6"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -285,8 +505,19 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.lifecycle:lifecycle-common:2.2.0"
- artifact_version: "2.2.0"
+ artifact_name: "androidx.lifecycle:lifecycle-common:2.3.1"
+ artifact_version: "2.3.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.lifecycle:lifecycle-common-java8:2.3.0"
+ artifact_version: "2.3.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -318,8 +549,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.lifecycle:lifecycle-livedata-core:2.2.0"
- artifact_version: "2.2.0"
+ artifact_name: "androidx.lifecycle:lifecycle-livedata-core:2.3.1"
+ artifact_version: "2.3.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -362,8 +593,19 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.lifecycle:lifecycle-runtime:2.2.0"
- artifact_version: "2.2.0"
+ artifact_name: "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
+ artifact_version: "2.3.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.lifecycle:lifecycle-runtime:2.3.1"
+ artifact_version: "2.3.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -384,8 +626,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0"
- artifact_version: "1.0.0"
+ artifact_name: "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
+ artifact_version: "2.3.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -395,8 +637,19 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
- artifact_version: "2.2.0"
+ artifact_name: "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1"
+ artifact_version: "2.3.1"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.lifecycle:lifecycle-viewmodel:2.3.1"
+ artifact_version: "2.3.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -449,6 +702,17 @@ maven_dependency {
}
}
}
+maven_dependency {
+ artifact_name: "androidx.profileinstaller:profileinstaller:1.1.0"
+ artifact_version: "1.1.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
maven_dependency {
artifact_name: "androidx.recyclerview:recyclerview:1.1.0"
artifact_version: "1.1.0"
@@ -483,8 +747,19 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.savedstate:savedstate:1.0.0"
- artifact_version: "1.0.0"
+ artifact_name: "androidx.savedstate:savedstate-ktx:1.1.0"
+ artifact_version: "1.1.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.savedstate:savedstate:1.1.0"
+ artifact_version: "1.1.0"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
@@ -515,6 +790,28 @@ maven_dependency {
}
}
}
+maven_dependency {
+ artifact_name: "androidx.startup:startup-runtime:1.0.0"
+ artifact_version: "1.0.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
+maven_dependency {
+ artifact_name: "androidx.tracing:tracing:1.0.0"
+ artifact_version: "1.0.0"
+ license {
+ license_name: "The Apache Software License, Version 2.0"
+ original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ scrapable_link {
+ url: "https://www.apache.org/licenses/LICENSE-2.0.txt"
+ }
+ }
+}
maven_dependency {
artifact_name: "androidx.transition:transition:1.2.0"
artifact_version: "1.2.0"
@@ -549,8 +846,8 @@ maven_dependency {
}
}
maven_dependency {
- artifact_name: "androidx.versionedparcelable:versionedparcelable:1.1.0"
- artifact_version: "1.1.0"
+ artifact_name: "androidx.versionedparcelable:versionedparcelable:1.1.1"
+ artifact_version: "1.1.1"
license {
license_name: "The Apache Software License, Version 2.0"
original_link: "https://www.apache.org/licenses/LICENSE-2.0.txt"
diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto
index a84bd17397f..6721dcb339e 100644
--- a/scripts/assets/test_file_exemptions.textproto
+++ b/scripts/assets/test_file_exemptions.textproto
@@ -258,6 +258,38 @@ test_file_exemption {
exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt"
test_file_not_required: true
}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/ClassroomListFragmentPresenter.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/ClassroomListViewModel.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/classroomlist/ClassroomList.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/promotedlist/PromotedList.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/topiclist/AllTopicsHeaderText.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/topiclist/TopicCard.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/classroom/welcome/WelcomeText.kt"
+ test_file_not_required: true
+}
test_file_exemption {
exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryItemViewModel.kt"
test_file_not_required: true
@@ -854,6 +886,14 @@ test_file_exemption {
exempted_file_path: "app/src/main/java/org/oppia/android/app/home/UserAppHistoryViewModel.kt"
test_file_not_required: true
}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryClickListener.kt"
+ test_file_not_required: true
+}
+test_file_exemption {
+ exempted_file_path: "app/src/main/java/org/oppia/android/app/home/classroomlist/ClassroomSummaryViewModel.kt"
+ test_file_not_required: true
+}
test_file_exemption {
exempted_file_path: "app/src/main/java/org/oppia/android/app/home/promotedlist/ComingSoonTopicListViewModel.kt"
test_file_not_required: true
diff --git a/testing/build.gradle b/testing/build.gradle
index 26f050e48ef..94f2e559dff 100644
--- a/testing/build.gradle
+++ b/testing/build.gradle
@@ -4,10 +4,10 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 33
- buildToolsVersion "29.0.2"
+ buildToolsVersion "30.0.2"
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
@@ -71,6 +71,7 @@ dependencies {
"androidx.test:core:1.0.0",
'androidx.test.espresso:espresso-accessibility:3.1.0',
'androidx.test.espresso:espresso-core:3.2.0',
+ 'androidx.test.ext:truth:1.4.0',
'androidx.test:runner:1.2.0',
'com.google.android.material:material:1.3.0',
'com.google.dagger:dagger:2.41',
diff --git a/testing/src/test/AndroidManifest.xml b/testing/src/test/AndroidManifest.xml
index 4682f548b88..7131a53dd2f 100644
--- a/testing/src/test/AndroidManifest.xml
+++ b/testing/src/test/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
diff --git a/third_party/maven_install.json b/third_party/maven_install.json
index c9a27f18450..e05d3b9c565 100644
--- a/third_party/maven_install.json
+++ b/third_party/maven_install.json
@@ -1,12 +1,19 @@
{
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
- "__INPUT_ARTIFACTS_HASH": 1041130706,
- "__RESOLVED_ARTIFACTS_HASH": 1257218600,
+ "__INPUT_ARTIFACTS_HASH": 1471454102,
+ "__RESOLVED_ARTIFACTS_HASH": 1994124852,
"conflict_resolution": {
+ "androidx.annotation:annotation:1.1.0": "androidx.annotation:annotation:1.2.0",
"androidx.constraintlayout:constraintlayout:1.1.3": "androidx.constraintlayout:constraintlayout:2.0.1",
- "androidx.core:core:1.0.1": "androidx.core:core:1.3.1",
+ "androidx.core:core-ktx:1.0.1": "androidx.core:core-ktx:1.1.0",
+ "androidx.core:core:1.0.1": "androidx.core:core:1.7.0",
+ "androidx.lifecycle:lifecycle-livedata-core:2.2.0": "androidx.lifecycle:lifecycle-livedata-core:2.3.1",
+ "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0": "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1",
"androidx.recyclerview:recyclerview:1.0.0": "androidx.recyclerview:recyclerview:1.1.0",
+ "androidx.test.espresso:espresso-core:3.2.0": "androidx.test.espresso:espresso-core:3.3.0",
+ "androidx.test.ext:junit:1.1.1": "androidx.test.ext:junit:1.1.2",
"androidx.test:core:1.0.0": "androidx.test:core:1.4.0",
+ "androidx.test:runner:1.2.0": "androidx.test:runner:1.3.0",
"com.google.firebase:firebase-common:19.3.0": "com.google.firebase:firebase-common:20.1.1",
"com.google.protobuf:protobuf-javalite:3.17.3": "com.google.protobuf:protobuf-javalite:3.19.2",
"com.google.truth:truth:0.43": "com.google.truth:truth:1.1.3",
@@ -16,35 +23,47 @@
"org.mockito:mockito-core:2.19.0": "org.mockito:mockito-core:3.9.0"
},
"artifacts": {
+ "androidx.activity:activity-compose:aar": {
+ "shasums": {
+ "jar": "82f97c1c4b96d15ee721b8204bd1273f80bcc654e50d0b16ca5399c77f6c3531"
+ },
+ "version": "1.4.0"
+ },
+ "androidx.activity:activity-ktx:aar": {
+ "shasums": {
+ "jar": "3f301941f37a90b4bc553dbbe84e7464a97c0d21df6cf2d6c0cb1b2c07349f33"
+ },
+ "version": "1.4.0"
+ },
"androidx.activity:activity:aar": {
"shasums": {
- "jar": "4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef"
+ "jar": "89dc38e0cdbd11f328c7d0b3b021ddb387ca9da0d49f14b18c91e300c45ed79c"
},
- "version": "1.1.0"
+ "version": "1.4.0"
},
"androidx.annotation:annotation": {
"shasums": {
- "jar": "d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692"
+ "jar": "9029262bddce116e6d02be499e4afdba21f24c239087b76b3b57d7e98b490a36"
},
- "version": "1.1.0"
+ "version": "1.2.0"
},
"androidx.annotation:annotation-experimental:aar": {
"shasums": {
- "jar": "b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11"
+ "jar": "0157de61a2064047896a058080f3fd67ba57ad9a94857b3f7a363660243e3f90"
},
- "version": "1.0.0"
+ "version": "1.1.0"
},
"androidx.appcompat:appcompat-resources:aar": {
"shasums": {
- "jar": "c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5"
+ "jar": "e3306cd3e9a19a28a5de5ec3b379580f237c4d81c15c3d795404be9291890a75"
},
- "version": "1.2.0"
+ "version": "1.3.1"
},
"androidx.appcompat:appcompat:aar": {
"shasums": {
- "jar": "3d2131a55a61a777322e2126e0018011efa6339e53b44153eb651b16020cca70"
+ "jar": "959b1daefe40d5e7eed1022f97730b22bc5fd52e6a6083eba284fa86c2971303"
},
- "version": "1.2.0"
+ "version": "1.3.1"
},
"androidx.arch.core:core-common": {
"shasums": {
@@ -64,6 +83,12 @@
},
"version": "2.1.0"
},
+ "androidx.autofill:autofill:aar": {
+ "shasums": {
+ "jar": "c9468f56e05006ea151a426c54957cd0799b8b83a579d2846dd22061f33e5ecd"
+ },
+ "version": "1.0.0"
+ },
"androidx.cardview:cardview:aar": {
"shasums": {
"jar": "1193c04c22a3d6b5946dae9f4e8c59d6adde6a71b6bd5d87fb99d82dda1afec7"
@@ -76,6 +101,120 @@
},
"version": "1.1.0"
},
+ "androidx.compose.animation:animation-core:aar": {
+ "shasums": {
+ "jar": "797048e2d03a9b1b98443fbe118d70f0955988e3fca56a71985ee3550be46f4e"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.animation:animation:aar": {
+ "shasums": {
+ "jar": "95fca9d5bbb8da8c4f351331558e7b2f4ec04db0cf290b021852423461d76a9c"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.compiler:compiler": {
+ "shasums": {
+ "jar": "925acb226edfade11905827a8387bca83f83d6bd1a13f48708d1efab2129520a"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.foundation:foundation-layout:aar": {
+ "shasums": {
+ "jar": "8337856c1babb54bdadfdb97eea47eb94640fc5557f91de47d35e9158258a971"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.foundation:foundation:aar": {
+ "shasums": {
+ "jar": "7f5fff6b1d462c7e411533b75ec0a9a73027f9a38f1e85ae8309295811ab64a0"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.material:material-icons-core:aar": {
+ "shasums": {
+ "jar": "8c9c1a9688a9f4dee57d31de8784d0970919fa939a48a466380815b9ebd84cfa"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.material:material-ripple:aar": {
+ "shasums": {
+ "jar": "c84f77c84e0c9ac17d513d38611894558bea6592c5318976fc8b0a51bc3cf8f2"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.material:material:aar": {
+ "shasums": {
+ "jar": "538ab37092d8e837e0d7e58bd854296e54033f449435db3baf913cc5ba4f97f3"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.runtime:runtime-saveable:aar": {
+ "shasums": {
+ "jar": "15b542e07bea14065336da0b255650433da46e250a160feaf39b90f2edc8b230"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.runtime:runtime:aar": {
+ "shasums": {
+ "jar": "e075b3e3952cb3775e7acc7ccdb8f2de777da76bd6d2ea2cc1f96d6f57b37763"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-geometry:aar": {
+ "shasums": {
+ "jar": "4f1e34ae515af8c04e783275dded50bc8d156135fc35397fb1c81a9e01f173b3"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-graphics:aar": {
+ "shasums": {
+ "jar": "e1719d8db7545e7f3be3baf42ac1865fb1cca36525d9b889601e2c2f4ea3aafc"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-test-junit4:aar": {
+ "shasums": {
+ "jar": "db527d645365f76ab135a7b3a5225ea3a3a3171ded8d6092c732c2af033c9e4e"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-test:aar": {
+ "shasums": {
+ "jar": "b710a1fe38c8acf9dbc7d678b45bdda61867013f2cad031b82547d0dc0b18e26"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-text:aar": {
+ "shasums": {
+ "jar": "e4af67f79b658862e4d7437f243033ed64334fbbff3247d4df39785a1c2c75d4"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-unit:aar": {
+ "shasums": {
+ "jar": "35b1d8f7f460874dde09fd7e630cd44052913695298076fc62de91a4fe1345a7"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui-util:aar": {
+ "shasums": {
+ "jar": "56420281f21e2888145ec9438ca4a632012792013b667da9aa03ea733e482e96"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.compose.ui:ui:aar": {
+ "shasums": {
+ "jar": "f0243d6a2176c9bcb6f077e1212cbc890943391ec9a6e32a4f249ab7c2c807a6"
+ },
+ "version": "1.1.1"
+ },
+ "androidx.concurrent:concurrent-futures": {
+ "shasums": {
+ "jar": "5595a40e278a7b39fa78a09490e3d7f3faa95c7b01447148bd38b5ade0605c35"
+ },
+ "version": "1.0.0"
+ },
"androidx.constraintlayout:constraintlayout-solver": {
"shasums": {
"jar": "b23732edbb3511d937fea1ffef047b0e6c001b50c1921f0d959fc384d706ec6a"
@@ -96,15 +235,15 @@
},
"androidx.core:core-ktx:aar": {
"shasums": {
- "jar": "a151b7e21acc3d272e1d397a2084e76ccce88e8542adcc4e0cf1e0655063255f"
+ "jar": "070cc5f8864f449128a2f4b25ca5b67aa3adca3ee1bd611e2eaf1a18fad83178"
},
- "version": "1.0.1"
+ "version": "1.1.0"
},
"androidx.core:core:aar": {
"shasums": {
- "jar": "e92ea65a37d589943d405a6a54d1be9d12a225948f26c4e41e511dd55e81efb6"
+ "jar": "aaf6734226fff923784f92f65d78a2984dbf17534138855c5ce2038f18656e0b"
},
- "version": "1.3.1"
+ "version": "1.7.0"
},
"androidx.cursoradapter:cursoradapter:aar": {
"shasums": {
@@ -174,9 +313,9 @@
},
"androidx.fragment:fragment:aar": {
"shasums": {
- "jar": "fdd0eac80c6b26c79093a63fc699303f928cc1fa73ca7196d5590a77eb6d6873"
+ "jar": "12f0831b4f08092d5dda272c1923c11a022ff20ceffed3e801751e21bb8d1c1e"
},
- "version": "1.2.0"
+ "version": "1.3.6"
},
"androidx.interpolator:interpolator:aar": {
"shasums": {
@@ -192,9 +331,15 @@
},
"androidx.lifecycle:lifecycle-common": {
"shasums": {
- "jar": "63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b"
+ "jar": "15848fb56db32f4c7cdc72b324003183d52a4884d6bf09be708ac7f587d139b5"
},
- "version": "2.2.0"
+ "version": "2.3.1"
+ },
+ "androidx.lifecycle:lifecycle-common-java8": {
+ "shasums": {
+ "jar": "a1ec63c1bb973443cb731d78ec336c5e20e7ee35c89cbb32d36f92c55bb02542"
+ },
+ "version": "2.3.0"
},
"androidx.lifecycle:lifecycle-extensions:aar": {
"shasums": {
@@ -210,9 +355,9 @@
},
"androidx.lifecycle:lifecycle-livedata-core:aar": {
"shasums": {
- "jar": "556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614"
+ "jar": "e55d38c372460f0a03997ddc950c67227511340fd74f8634d99d29653cd81ab1"
},
- "version": "2.2.0"
+ "version": "2.3.1"
},
"androidx.lifecycle:lifecycle-livedata-ktx:aar": {
"shasums": {
@@ -232,11 +377,17 @@
},
"version": "2.2.0"
},
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar": {
+ "shasums": {
+ "jar": "7ad2987dd7f4075c0871a72cf07e9649d9cd790fc23dfab1972eca4710373873"
+ },
+ "version": "2.3.1"
+ },
"androidx.lifecycle:lifecycle-runtime:aar": {
"shasums": {
- "jar": "2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc"
+ "jar": "dd294f4a689c71ff877fd41f3b67a3a62f7760d44ce420e6130f1fc3569d8f00"
},
- "version": "2.2.0"
+ "version": "2.3.1"
},
"androidx.lifecycle:lifecycle-service:aar": {
"shasums": {
@@ -246,21 +397,21 @@
},
"androidx.lifecycle:lifecycle-viewmodel-ktx:aar": {
"shasums": {
- "jar": "f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702"
+ "jar": "5fb3591b6a54eeb3e204be0125d48eb987b8ea45a5048140036865482ccf9de9"
},
- "version": "2.2.0"
+ "version": "2.3.1"
},
"androidx.lifecycle:lifecycle-viewmodel-savedstate:aar": {
"shasums": {
- "jar": "f4cceafbf86acfc7f3ba6a61d6dc6842a6738c1274610767d3ab8f8a114cba97"
+ "jar": "97137a8af6a31776a14e4866ab808c7c0a791b484bdbc788bbd83e66407564c0"
},
- "version": "1.0.0"
+ "version": "2.3.1"
},
"androidx.lifecycle:lifecycle-viewmodel:aar": {
"shasums": {
- "jar": "967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27"
+ "jar": "b6db4c274a12ff85a4747e1e6669c7e98aefa2571ace9d1f1a6fa6be417ce838"
},
- "version": "2.2.0"
+ "version": "2.3.1"
},
"androidx.loader:loader:aar": {
"shasums": {
@@ -340,6 +491,12 @@
},
"version": "1.0.0"
},
+ "androidx.profileinstaller:profileinstaller:aar": {
+ "shasums": {
+ "jar": "d85f562bc70f33595b46a893c22d64f5b4f856c19a02b1eb09aad00c3a2124ee"
+ },
+ "version": "1.1.0"
+ },
"androidx.recyclerview:recyclerview:aar": {
"shasums": {
"jar": "f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f"
@@ -358,11 +515,17 @@
},
"version": "2.2.5"
},
+ "androidx.savedstate:savedstate-ktx:aar": {
+ "shasums": {
+ "jar": "e44d61347463b0fafeeb649cbcc3d7109b2eb5e11d1522e986105170cdebbf68"
+ },
+ "version": "1.1.0"
+ },
"androidx.savedstate:savedstate:aar": {
"shasums": {
- "jar": "2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83"
+ "jar": "d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7"
},
- "version": "1.0.0"
+ "version": "1.1.0"
},
"androidx.sqlite:sqlite-framework:aar": {
"shasums": {
@@ -376,6 +539,12 @@
},
"version": "2.1.0"
},
+ "androidx.startup:startup-runtime:aar": {
+ "shasums": {
+ "jar": "ff081d2db7dd28aec59f74934c514fbaf4ae5aac5258495fe10d612a3622f876"
+ },
+ "version": "1.0.0"
+ },
"androidx.test.espresso:espresso-accessibility:aar": {
"shasums": {
"jar": "e2ee8b50081c0b578521d112808321f737f3bf1acce035fe7be0e26ef00b491f"
@@ -390,15 +559,15 @@
},
"androidx.test.espresso:espresso-core:aar": {
"shasums": {
- "jar": "beb4712c2520c1da30ac1f25506871f16ea5b83ee686ece5a258769df1a01e15"
+ "jar": "23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee"
},
- "version": "3.2.0"
+ "version": "3.3.0"
},
"androidx.test.espresso:espresso-idling-resource:aar": {
"shasums": {
- "jar": "c1a0454fe95788122ba652c3ecff7ec538c7e27de206aed970f2809fb8090d09"
+ "jar": "29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6"
},
- "version": "3.2.0"
+ "version": "3.3.0"
},
"androidx.test.espresso:espresso-intents:aar": {
"shasums": {
@@ -408,9 +577,9 @@
},
"androidx.test.ext:junit:aar": {
"shasums": {
- "jar": "449df418d2916a0f86fe7dafb1edb09480fafb6e995d5c751c7d0d1970d4ae72"
+ "jar": "6c6ab120c640bf16fcaae69cb83c144d0ed6b6298562be0ac35e37ed969c0409"
},
- "version": "1.1.1"
+ "version": "1.1.2"
},
"androidx.test.ext:truth:aar": {
"shasums": {
@@ -424,6 +593,12 @@
},
"version": "2.2.0"
},
+ "androidx.test:annotation:aar": {
+ "shasums": {
+ "jar": "c0754928effe1968c3a9a7b55d1dfc7ceb1e1e7c9f3f09f98afd42431f712492"
+ },
+ "version": "1.0.0"
+ },
"androidx.test:core:aar": {
"shasums": {
"jar": "671284e62e393f16ceae1a99a3a9a07bf1aacda29f8fe7b6b884355ef34c09cf"
@@ -432,9 +607,9 @@
},
"androidx.test:monitor:aar": {
"shasums": {
- "jar": "46a912a1e175f27a97521af3f50e5af87c22c49275dd2c57c043740012806325"
+ "jar": "10b1723c436beecb5884c69f8473504bc59611f9463ae549c48b3cf8e73b09c0"
},
- "version": "1.4.0"
+ "version": "1.5.0"
},
"androidx.test:rules:aar": {
"shasums": {
@@ -444,9 +619,15 @@
},
"androidx.test:runner:aar": {
"shasums": {
- "jar": "5387e011167a3c8da08d99b5d59248c0e2da839317b48ebf202e31dc1f791ec1"
+ "jar": "61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b"
},
- "version": "1.2.0"
+ "version": "1.3.0"
+ },
+ "androidx.tracing:tracing:aar": {
+ "shasums": {
+ "jar": "07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612"
+ },
+ "version": "1.0.0"
},
"androidx.transition:transition:aar": {
"shasums": {
@@ -468,9 +649,9 @@
},
"androidx.versionedparcelable:versionedparcelable:aar": {
"shasums": {
- "jar": "9a1d77140ac222b7866b5054ee7d159bc1800987ed2d46dd6afdd145abb710c1"
+ "jar": "57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4"
},
- "version": "1.1.0"
+ "version": "1.1.1"
},
"androidx.viewpager2:viewpager2:aar": {
"shasums": {
@@ -1758,13 +1939,31 @@
}
},
"dependencies": {
+ "androidx.activity:activity-compose:aar": [
+ "androidx.activity:activity-ktx:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.activity:activity-ktx:aar": [
+ "androidx.activity:activity:aar",
+ "androidx.core:core-ktx:aar",
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar",
+ "androidx.lifecycle:lifecycle-viewmodel-ktx:aar",
+ "androidx.savedstate:savedstate-ktx:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
"androidx.activity:activity:aar": [
"androidx.annotation:annotation",
+ "androidx.collection:collection",
"androidx.core:core:aar",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.lifecycle:lifecycle-viewmodel-savedstate:aar",
"androidx.lifecycle:lifecycle-viewmodel:aar",
- "androidx.savedstate:savedstate:aar"
+ "androidx.savedstate:savedstate:aar",
+ "androidx.tracing:tracing:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
],
"androidx.appcompat:appcompat-resources:aar": [
"androidx.annotation:annotation",
@@ -1774,13 +1973,17 @@
"androidx.vectordrawable:vectordrawable:aar"
],
"androidx.appcompat:appcompat:aar": [
+ "androidx.activity:activity:aar",
"androidx.annotation:annotation",
"androidx.appcompat:appcompat-resources:aar",
"androidx.collection:collection",
"androidx.core:core:aar",
"androidx.cursoradapter:cursoradapter:aar",
"androidx.drawerlayout:drawerlayout:aar",
- "androidx.fragment:fragment:aar"
+ "androidx.fragment:fragment:aar",
+ "androidx.lifecycle:lifecycle-runtime:aar",
+ "androidx.lifecycle:lifecycle-viewmodel:aar",
+ "androidx.savedstate:savedstate:aar"
],
"androidx.arch.core:core-common": [
"androidx.annotation:annotation"
@@ -1795,12 +1998,184 @@
"junit:junit",
"org.mockito:mockito-core"
],
+ "androidx.autofill:autofill:aar": [
+ "androidx.core:core:aar"
+ ],
"androidx.cardview:cardview:aar": [
"androidx.annotation:annotation"
],
"androidx.collection:collection": [
"androidx.annotation:annotation"
],
+ "androidx.compose.animation:animation-core:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core"
+ ],
+ "androidx.compose.animation:animation:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.foundation:foundation-layout:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.foundation:foundation:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.material:material-icons-core:aar": [
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.compose.material:material-ripple:aar": [
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.material:material:aar": [
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.material:material-icons-core:aar",
+ "androidx.compose.material:material-ripple:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.lifecycle:lifecycle-runtime:aar",
+ "androidx.lifecycle:lifecycle-viewmodel:aar",
+ "androidx.savedstate:savedstate:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.runtime:runtime-saveable:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.compose.runtime:runtime:aar": [
+ "androidx.annotation:annotation",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android"
+ ],
+ "androidx.compose.ui:ui-geometry:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.compose.ui:ui-graphics:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.ui:ui-test-junit4:aar": [
+ "androidx.activity:activity-compose:aar",
+ "androidx.activity:activity:aar",
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.ui:ui-test:aar",
+ "androidx.lifecycle:lifecycle-common",
+ "androidx.lifecycle:lifecycle-runtime:aar",
+ "androidx.test.espresso:espresso-core:aar",
+ "androidx.test.espresso:espresso-idling-resource:aar",
+ "androidx.test.ext:junit:aar",
+ "androidx.test:core:aar",
+ "androidx.test:monitor:aar",
+ "junit:junit",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib-common",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-test"
+ ],
+ "androidx.compose.ui:ui-test:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.test.espresso:espresso-core:aar",
+ "androidx.test:monitor:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib-common",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-test"
+ ],
+ "androidx.compose.ui:ui-text:aar": [
+ "androidx.annotation:annotation",
+ "androidx.collection:collection",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.core:core:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib-common"
+ ],
+ "androidx.compose.ui:ui-unit:aar": [
+ "androidx.annotation:annotation",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.compose.ui:ui-util:aar": [
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
+ "androidx.compose.ui:ui:aar": [
+ "androidx.annotation:annotation",
+ "androidx.autofill:autofill:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.lifecycle:lifecycle-common-java8",
+ "androidx.lifecycle:lifecycle-runtime:aar",
+ "androidx.lifecycle:lifecycle-viewmodel:aar",
+ "androidx.profileinstaller:profileinstaller:aar",
+ "androidx.savedstate:savedstate:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlin:kotlin-stdlib-common",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core"
+ ],
+ "androidx.concurrent:concurrent-futures": [
+ "androidx.annotation:annotation",
+ "com.google.guava:listenablefuture"
+ ],
"androidx.constraintlayout:constraintlayout:aar": [
"androidx.appcompat:appcompat:aar",
"androidx.constraintlayout:constraintlayout-solver",
@@ -1819,7 +2194,9 @@
],
"androidx.core:core:aar": [
"androidx.annotation:annotation",
+ "androidx.annotation:annotation-experimental:aar",
"androidx.collection:collection",
+ "androidx.concurrent:concurrent-futures",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.versionedparcelable:versionedparcelable:aar"
],
@@ -1881,12 +2258,14 @@
"androidx.fragment:fragment:aar": [
"androidx.activity:activity:aar",
"androidx.annotation:annotation",
+ "androidx.annotation:annotation-experimental:aar",
"androidx.collection:collection",
"androidx.core:core:aar",
"androidx.lifecycle:lifecycle-livedata-core:aar",
"androidx.lifecycle:lifecycle-viewmodel-savedstate:aar",
"androidx.lifecycle:lifecycle-viewmodel:aar",
"androidx.loader:loader:aar",
+ "androidx.savedstate:savedstate:aar",
"androidx.viewpager:viewpager:aar"
],
"androidx.interpolator:interpolator:aar": [
@@ -1903,6 +2282,10 @@
"androidx.lifecycle:lifecycle-common": [
"androidx.annotation:annotation"
],
+ "androidx.lifecycle:lifecycle-common-java8": [
+ "androidx.annotation:annotation",
+ "androidx.lifecycle:lifecycle-common"
+ ],
"androidx.lifecycle:lifecycle-extensions:aar": [
"androidx.arch.core:core-common",
"androidx.arch.core:core-runtime:aar",
@@ -1937,9 +2320,16 @@
"androidx.lifecycle:lifecycle-process:aar": [
"androidx.lifecycle:lifecycle-runtime:aar"
],
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar": [
+ "androidx.annotation:annotation",
+ "androidx.lifecycle:lifecycle-runtime:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib",
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android"
+ ],
"androidx.lifecycle:lifecycle-runtime:aar": [
"androidx.annotation:annotation",
"androidx.arch.core:core-common",
+ "androidx.arch.core:core-runtime:aar",
"androidx.lifecycle:lifecycle-common"
],
"androidx.lifecycle:lifecycle-service:aar": [
@@ -2010,6 +2400,10 @@
"androidx.print:print:aar": [
"androidx.annotation:annotation"
],
+ "androidx.profileinstaller:profileinstaller:aar": [
+ "androidx.annotation:annotation",
+ "androidx.startup:startup-runtime:aar"
+ ],
"androidx.recyclerview:recyclerview:aar": [
"androidx.annotation:annotation",
"androidx.collection:collection",
@@ -2025,6 +2419,10 @@
"androidx.sqlite:sqlite-framework:aar",
"androidx.sqlite:sqlite:aar"
],
+ "androidx.savedstate:savedstate-ktx:aar": [
+ "androidx.savedstate:savedstate:aar",
+ "org.jetbrains.kotlin:kotlin-stdlib"
+ ],
"androidx.savedstate:savedstate:aar": [
"androidx.annotation:annotation",
"androidx.arch.core:core-common",
@@ -2037,6 +2435,10 @@
"androidx.sqlite:sqlite:aar": [
"androidx.annotation:annotation"
],
+ "androidx.startup:startup-runtime:aar": [
+ "androidx.annotation:annotation",
+ "androidx.tracing:tracing:aar"
+ ],
"androidx.test.espresso:espresso-accessibility:aar": [
"androidx.test.espresso:espresso-core:aar",
"com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework"
@@ -2075,13 +2477,18 @@
"com.google.guava:guava",
"com.google.truth:truth"
],
+ "androidx.test:annotation:aar": [
+ "androidx.annotation:annotation",
+ "androidx.annotation:annotation-experimental:aar"
+ ],
"androidx.test:core:aar": [
"androidx.annotation:annotation",
"androidx.lifecycle:lifecycle-common",
"androidx.test:monitor:aar"
],
"androidx.test:monitor:aar": [
- "androidx.annotation:annotation"
+ "androidx.annotation:annotation",
+ "androidx.test:annotation:aar"
],
"androidx.test:rules:aar": [
"androidx.test:runner:aar"
@@ -2089,8 +2496,10 @@
"androidx.test:runner:aar": [
"androidx.annotation:annotation",
"androidx.test:monitor:aar",
- "junit:junit",
- "net.sf.kxml:kxml2"
+ "junit:junit"
+ ],
+ "androidx.tracing:tracing:aar": [
+ "androidx.annotation:annotation"
],
"androidx.transition:transition:aar": [
"androidx.annotation:annotation",
@@ -2996,6 +3405,15 @@
"androidx.collection:collection": [
"androidx.collection"
],
+ "androidx.compose.compiler:compiler": [
+ "androidx.compose.compiler.plugins.kotlin",
+ "androidx.compose.compiler.plugins.kotlin.analysis",
+ "androidx.compose.compiler.plugins.kotlin.lower",
+ "androidx.compose.compiler.plugins.kotlin.lower.decoys"
+ ],
+ "androidx.concurrent:concurrent-futures": [
+ "androidx.concurrent.futures"
+ ],
"androidx.constraintlayout:constraintlayout-solver": [
"androidx.constraintlayout.solver",
"androidx.constraintlayout.solver.state",
@@ -3031,6 +3449,9 @@
"androidx.lifecycle:lifecycle-common": [
"androidx.lifecycle"
],
+ "androidx.lifecycle:lifecycle-common-java8": [
+ "androidx.lifecycle"
+ ],
"androidx.room:room-common": [
"androidx.room"
],
@@ -6372,6 +6793,8 @@
},
"repositories": {
"https://maven.google.com/": [
+ "androidx.activity:activity-compose:aar",
+ "androidx.activity:activity-ktx:aar",
"androidx.activity:activity:aar",
"androidx.annotation:annotation",
"androidx.annotation:annotation-experimental:aar",
@@ -6380,8 +6803,28 @@
"androidx.arch.core:core-common",
"androidx.arch.core:core-runtime:aar",
"androidx.arch.core:core-testing:aar",
+ "androidx.autofill:autofill:aar",
"androidx.cardview:cardview:aar",
"androidx.collection:collection",
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.compiler:compiler",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.material:material-icons-core:aar",
+ "androidx.compose.material:material-ripple:aar",
+ "androidx.compose.material:material:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-test-junit4:aar",
+ "androidx.compose.ui:ui-test:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.concurrent:concurrent-futures",
"androidx.constraintlayout:constraintlayout-solver",
"androidx.constraintlayout:constraintlayout:aar",
"androidx.coordinatorlayout:coordinatorlayout:aar",
@@ -6402,12 +6845,14 @@
"androidx.interpolator:interpolator:aar",
"androidx.legacy:legacy-support-core-utils:aar",
"androidx.lifecycle:lifecycle-common",
+ "androidx.lifecycle:lifecycle-common-java8",
"androidx.lifecycle:lifecycle-extensions:aar",
"androidx.lifecycle:lifecycle-livedata-core-ktx:aar",
"androidx.lifecycle:lifecycle-livedata-core:aar",
"androidx.lifecycle:lifecycle-livedata-ktx:aar",
"androidx.lifecycle:lifecycle-livedata:aar",
"androidx.lifecycle:lifecycle-process:aar",
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.lifecycle:lifecycle-service:aar",
"androidx.lifecycle:lifecycle-viewmodel-ktx:aar",
@@ -6426,12 +6871,15 @@
"androidx.navigation:navigation-ui-ktx:aar",
"androidx.navigation:navigation-ui:aar",
"androidx.print:print:aar",
+ "androidx.profileinstaller:profileinstaller:aar",
"androidx.recyclerview:recyclerview:aar",
"androidx.room:room-common",
"androidx.room:room-runtime:aar",
+ "androidx.savedstate:savedstate-ktx:aar",
"androidx.savedstate:savedstate:aar",
"androidx.sqlite:sqlite-framework:aar",
"androidx.sqlite:sqlite:aar",
+ "androidx.startup:startup-runtime:aar",
"androidx.test.espresso:espresso-accessibility:aar",
"androidx.test.espresso:espresso-contrib:aar",
"androidx.test.espresso:espresso-core:aar",
@@ -6440,10 +6888,12 @@
"androidx.test.ext:junit:aar",
"androidx.test.ext:truth:aar",
"androidx.test.uiautomator:uiautomator:aar",
+ "androidx.test:annotation:aar",
"androidx.test:core:aar",
"androidx.test:monitor:aar",
"androidx.test:rules:aar",
"androidx.test:runner:aar",
+ "androidx.tracing:tracing:aar",
"androidx.transition:transition:aar",
"androidx.vectordrawable:vectordrawable-animated:aar",
"androidx.vectordrawable:vectordrawable:aar",
@@ -6664,6 +7114,8 @@
"xml-apis:xml-apis"
],
"https://repo1.maven.org/maven2/": [
+ "androidx.activity:activity-compose:aar",
+ "androidx.activity:activity-ktx:aar",
"androidx.activity:activity:aar",
"androidx.annotation:annotation",
"androidx.annotation:annotation-experimental:aar",
@@ -6672,8 +7124,28 @@
"androidx.arch.core:core-common",
"androidx.arch.core:core-runtime:aar",
"androidx.arch.core:core-testing:aar",
+ "androidx.autofill:autofill:aar",
"androidx.cardview:cardview:aar",
"androidx.collection:collection",
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.compiler:compiler",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.material:material-icons-core:aar",
+ "androidx.compose.material:material-ripple:aar",
+ "androidx.compose.material:material:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-test-junit4:aar",
+ "androidx.compose.ui:ui-test:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.concurrent:concurrent-futures",
"androidx.constraintlayout:constraintlayout-solver",
"androidx.constraintlayout:constraintlayout:aar",
"androidx.coordinatorlayout:coordinatorlayout:aar",
@@ -6694,12 +7166,14 @@
"androidx.interpolator:interpolator:aar",
"androidx.legacy:legacy-support-core-utils:aar",
"androidx.lifecycle:lifecycle-common",
+ "androidx.lifecycle:lifecycle-common-java8",
"androidx.lifecycle:lifecycle-extensions:aar",
"androidx.lifecycle:lifecycle-livedata-core-ktx:aar",
"androidx.lifecycle:lifecycle-livedata-core:aar",
"androidx.lifecycle:lifecycle-livedata-ktx:aar",
"androidx.lifecycle:lifecycle-livedata:aar",
"androidx.lifecycle:lifecycle-process:aar",
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.lifecycle:lifecycle-service:aar",
"androidx.lifecycle:lifecycle-viewmodel-ktx:aar",
@@ -6718,12 +7192,15 @@
"androidx.navigation:navigation-ui-ktx:aar",
"androidx.navigation:navigation-ui:aar",
"androidx.print:print:aar",
+ "androidx.profileinstaller:profileinstaller:aar",
"androidx.recyclerview:recyclerview:aar",
"androidx.room:room-common",
"androidx.room:room-runtime:aar",
+ "androidx.savedstate:savedstate-ktx:aar",
"androidx.savedstate:savedstate:aar",
"androidx.sqlite:sqlite-framework:aar",
"androidx.sqlite:sqlite:aar",
+ "androidx.startup:startup-runtime:aar",
"androidx.test.espresso:espresso-accessibility:aar",
"androidx.test.espresso:espresso-contrib:aar",
"androidx.test.espresso:espresso-core:aar",
@@ -6732,10 +7209,12 @@
"androidx.test.ext:junit:aar",
"androidx.test.ext:truth:aar",
"androidx.test.uiautomator:uiautomator:aar",
+ "androidx.test:annotation:aar",
"androidx.test:core:aar",
"androidx.test:monitor:aar",
"androidx.test:rules:aar",
"androidx.test:runner:aar",
+ "androidx.tracing:tracing:aar",
"androidx.transition:transition:aar",
"androidx.vectordrawable:vectordrawable-animated:aar",
"androidx.vectordrawable:vectordrawable:aar",
@@ -6956,6 +7435,8 @@
"xml-apis:xml-apis"
],
"https://oss.sonatype.org/content/repositories/snapshots/": [
+ "androidx.activity:activity-compose:aar",
+ "androidx.activity:activity-ktx:aar",
"androidx.activity:activity:aar",
"androidx.annotation:annotation",
"androidx.annotation:annotation-experimental:aar",
@@ -6964,8 +7445,28 @@
"androidx.arch.core:core-common",
"androidx.arch.core:core-runtime:aar",
"androidx.arch.core:core-testing:aar",
+ "androidx.autofill:autofill:aar",
"androidx.cardview:cardview:aar",
"androidx.collection:collection",
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.compiler:compiler",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.material:material-icons-core:aar",
+ "androidx.compose.material:material-ripple:aar",
+ "androidx.compose.material:material:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-test-junit4:aar",
+ "androidx.compose.ui:ui-test:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.concurrent:concurrent-futures",
"androidx.constraintlayout:constraintlayout-solver",
"androidx.constraintlayout:constraintlayout:aar",
"androidx.coordinatorlayout:coordinatorlayout:aar",
@@ -6986,12 +7487,14 @@
"androidx.interpolator:interpolator:aar",
"androidx.legacy:legacy-support-core-utils:aar",
"androidx.lifecycle:lifecycle-common",
+ "androidx.lifecycle:lifecycle-common-java8",
"androidx.lifecycle:lifecycle-extensions:aar",
"androidx.lifecycle:lifecycle-livedata-core-ktx:aar",
"androidx.lifecycle:lifecycle-livedata-core:aar",
"androidx.lifecycle:lifecycle-livedata-ktx:aar",
"androidx.lifecycle:lifecycle-livedata:aar",
"androidx.lifecycle:lifecycle-process:aar",
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.lifecycle:lifecycle-service:aar",
"androidx.lifecycle:lifecycle-viewmodel-ktx:aar",
@@ -7010,12 +7513,15 @@
"androidx.navigation:navigation-ui-ktx:aar",
"androidx.navigation:navigation-ui:aar",
"androidx.print:print:aar",
+ "androidx.profileinstaller:profileinstaller:aar",
"androidx.recyclerview:recyclerview:aar",
"androidx.room:room-common",
"androidx.room:room-runtime:aar",
+ "androidx.savedstate:savedstate-ktx:aar",
"androidx.savedstate:savedstate:aar",
"androidx.sqlite:sqlite-framework:aar",
"androidx.sqlite:sqlite:aar",
+ "androidx.startup:startup-runtime:aar",
"androidx.test.espresso:espresso-accessibility:aar",
"androidx.test.espresso:espresso-contrib:aar",
"androidx.test.espresso:espresso-core:aar",
@@ -7024,10 +7530,12 @@
"androidx.test.ext:junit:aar",
"androidx.test.ext:truth:aar",
"androidx.test.uiautomator:uiautomator:aar",
+ "androidx.test:annotation:aar",
"androidx.test:core:aar",
"androidx.test:monitor:aar",
"androidx.test:rules:aar",
"androidx.test:runner:aar",
+ "androidx.tracing:tracing:aar",
"androidx.transition:transition:aar",
"androidx.vectordrawable:vectordrawable-animated:aar",
"androidx.vectordrawable:vectordrawable:aar",
@@ -7248,6 +7756,8 @@
"xml-apis:xml-apis"
],
"https://maven.fabric.io/public/": [
+ "androidx.activity:activity-compose:aar",
+ "androidx.activity:activity-ktx:aar",
"androidx.activity:activity:aar",
"androidx.annotation:annotation",
"androidx.annotation:annotation-experimental:aar",
@@ -7256,8 +7766,28 @@
"androidx.arch.core:core-common",
"androidx.arch.core:core-runtime:aar",
"androidx.arch.core:core-testing:aar",
+ "androidx.autofill:autofill:aar",
"androidx.cardview:cardview:aar",
"androidx.collection:collection",
+ "androidx.compose.animation:animation-core:aar",
+ "androidx.compose.animation:animation:aar",
+ "androidx.compose.compiler:compiler",
+ "androidx.compose.foundation:foundation-layout:aar",
+ "androidx.compose.foundation:foundation:aar",
+ "androidx.compose.material:material-icons-core:aar",
+ "androidx.compose.material:material-ripple:aar",
+ "androidx.compose.material:material:aar",
+ "androidx.compose.runtime:runtime-saveable:aar",
+ "androidx.compose.runtime:runtime:aar",
+ "androidx.compose.ui:ui-geometry:aar",
+ "androidx.compose.ui:ui-graphics:aar",
+ "androidx.compose.ui:ui-test-junit4:aar",
+ "androidx.compose.ui:ui-test:aar",
+ "androidx.compose.ui:ui-text:aar",
+ "androidx.compose.ui:ui-unit:aar",
+ "androidx.compose.ui:ui-util:aar",
+ "androidx.compose.ui:ui:aar",
+ "androidx.concurrent:concurrent-futures",
"androidx.constraintlayout:constraintlayout-solver",
"androidx.constraintlayout:constraintlayout:aar",
"androidx.coordinatorlayout:coordinatorlayout:aar",
@@ -7278,12 +7808,14 @@
"androidx.interpolator:interpolator:aar",
"androidx.legacy:legacy-support-core-utils:aar",
"androidx.lifecycle:lifecycle-common",
+ "androidx.lifecycle:lifecycle-common-java8",
"androidx.lifecycle:lifecycle-extensions:aar",
"androidx.lifecycle:lifecycle-livedata-core-ktx:aar",
"androidx.lifecycle:lifecycle-livedata-core:aar",
"androidx.lifecycle:lifecycle-livedata-ktx:aar",
"androidx.lifecycle:lifecycle-livedata:aar",
"androidx.lifecycle:lifecycle-process:aar",
+ "androidx.lifecycle:lifecycle-runtime-ktx:aar",
"androidx.lifecycle:lifecycle-runtime:aar",
"androidx.lifecycle:lifecycle-service:aar",
"androidx.lifecycle:lifecycle-viewmodel-ktx:aar",
@@ -7302,12 +7834,15 @@
"androidx.navigation:navigation-ui-ktx:aar",
"androidx.navigation:navigation-ui:aar",
"androidx.print:print:aar",
+ "androidx.profileinstaller:profileinstaller:aar",
"androidx.recyclerview:recyclerview:aar",
"androidx.room:room-common",
"androidx.room:room-runtime:aar",
+ "androidx.savedstate:savedstate-ktx:aar",
"androidx.savedstate:savedstate:aar",
"androidx.sqlite:sqlite-framework:aar",
"androidx.sqlite:sqlite:aar",
+ "androidx.startup:startup-runtime:aar",
"androidx.test.espresso:espresso-accessibility:aar",
"androidx.test.espresso:espresso-contrib:aar",
"androidx.test.espresso:espresso-core:aar",
@@ -7316,10 +7851,12 @@
"androidx.test.ext:junit:aar",
"androidx.test.ext:truth:aar",
"androidx.test.uiautomator:uiautomator:aar",
+ "androidx.test:annotation:aar",
"androidx.test:core:aar",
"androidx.test:monitor:aar",
"androidx.test:rules:aar",
"androidx.test:runner:aar",
+ "androidx.tracing:tracing:aar",
"androidx.transition:transition:aar",
"androidx.vectordrawable:vectordrawable-animated:aar",
"androidx.vectordrawable:vectordrawable:aar",
diff --git a/third_party/versions.bzl b/third_party/versions.bzl
index f9e0864672d..2bcb6612f34 100644
--- a/third_party/versions.bzl
+++ b/third_party/versions.bzl
@@ -20,8 +20,15 @@ https://github.com/oppia/oppia-android/wiki/Updating-Maven-Dependencies
# Note to developers: Please keep this dict sorted by key to make it easier to find dependencies.
# This list should contain only production (non-test) dependencies.
MAVEN_PRODUCTION_DEPENDENCY_VERSIONS = {
+ "androidx.activity:activity-compose": "1.4.0",
"androidx.annotation:annotation": "1.1.0",
- "androidx.appcompat:appcompat": "1.2.0",
+ "androidx.appcompat:appcompat": "1.3.1",
+ "androidx.compose.compiler:compiler": "1.1.1",
+ "androidx.compose.foundation:foundation": "1.1.1",
+ "androidx.compose.foundation:foundation-layout": "1.1.1",
+ "androidx.compose.material:material": "1.1.1",
+ "androidx.compose.runtime:runtime": "1.1.1",
+ "androidx.compose.ui:ui": "1.1.1",
"androidx.constraintlayout:constraintlayout": "1.1.3",
"androidx.core:core": "1.0.1",
"androidx.core:core-ktx": "1.0.1",
@@ -91,6 +98,7 @@ MAVEN_PRODUCTION_DEPENDENCY_VERSIONS = {
# cannot be included in production builds of the app.
MAVEN_TEST_DEPENDENCY_VERSIONS = {
"androidx.arch.core:core-testing": "2.1.0",
+ "androidx.compose.ui:ui-test-junit4": "1.1.1",
"androidx.test.espresso:espresso-accessibility": "3.1.0",
"androidx.test.espresso:espresso-contrib": "3.1.0",
"androidx.test.espresso:espresso-core": "3.2.0",
diff --git a/tools/kotlin/BUILD.bazel b/tools/kotlin/BUILD.bazel
index ad4704f6609..5908169ccf9 100644
--- a/tools/kotlin/BUILD.bazel
+++ b/tools/kotlin/BUILD.bazel
@@ -3,7 +3,7 @@ Configurations for a codebase-wide build toolchain for Kotlin.
"""
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
-load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_javac_options", "kt_kotlinc_options")
+load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_compiler_plugin", "kt_javac_options", "kt_kotlinc_options")
# This exposes a patch that fixes an issue with duplicate processor arguments. See:
# github.com/bazelbuild/rules_kotlin/pull/940.
@@ -54,3 +54,11 @@ define_kt_toolchain(
kotlinc_options = ":oppia_kotlinc_options",
language_version = "1.6",
)
+
+kt_compiler_plugin(
+ name = "jetpack_compose_compiler_plugin",
+ id = "androidx.compose.compiler",
+ target_embedded_compiler = True,
+ visibility = ["//app:app_visibility"],
+ deps = ["//third_party:androidx_compose_compiler_compiler"],
+)
diff --git a/utility/build.gradle b/utility/build.gradle
index 487c60f8b71..53e7905913f 100644
--- a/utility/build.gradle
+++ b/utility/build.gradle
@@ -4,10 +4,10 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 33
- buildToolsVersion "29.0.2"
+ buildToolsVersion "30.0.2"
defaultConfig {
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
diff --git a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt
index cb95c888227..ab091a7bba7 100644
--- a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt
+++ b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt
@@ -854,6 +854,7 @@ class EventBundleCreator @Inject constructor(
ScreenName.UNRECOGNIZED -> "unrecognized"
ScreenName.FOREGROUND_SCREEN -> "foreground_screen"
ScreenName.SURVEY_ACTIVITY -> "survey_activity"
+ ScreenName.CLASSROOM_LIST_ACTIVITY -> "classroom_list_activity"
}
private fun AppLanguageSelection.toAnalyticsText(): String {