diff --git a/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayViewModel.kt b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayViewModel.kt new file mode 100755 index 00000000000..408a656c658 --- /dev/null +++ b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayViewModel.kt @@ -0,0 +1,56 @@ +package org.oppia.app.home.continueplaying + +import androidx.fragment.app.Fragment +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import androidx.lifecycle.ViewModel +import org.oppia.app.R +import org.oppia.app.model.OngoingStoryList +import org.oppia.domain.topic.TopicListController +import org.oppia.util.data.AsyncResult +import javax.inject.Inject + +// TODO(#297): Add download status information to promoted-story-card. + +/** [ViewModel] for displaying a promoted story. */ +class ContinuePlayViewModel @Inject constructor( + private val fragment: Fragment, + private val topicListController: TopicListController +) : ContinuePlayingItemViewModel() { + + private val itemList: MutableList = ArrayList() + + private val ongoingStoryListSummaryResultLiveData: LiveData> by lazy { + topicListController.getOngoingStoryList() + } + + val ongoingStoryLiveData: LiveData>by lazy { + Transformations.map(ongoingStoryListSummaryResultLiveData, ::processOngoingStoryList) + } + + private fun processOngoingStoryList(ongoingStoryList: AsyncResult): List { + if (ongoingStoryList.isSuccess()) { + if (ongoingStoryList.getOrThrow().recentStoryList.isNotEmpty()) { + val recentSectionTitleViewModel = + SectionTitleViewModel(fragment.getString(R.string.ongoing_story_last_week), false) + itemList.add(recentSectionTitleViewModel) + for (promotedStory in ongoingStoryList.getOrThrow().recentStoryList) { + val ongoingStoryViewModel = OngoingStoryViewModel(promotedStory, fragment as OngoingStoryClickListener) + itemList.add(ongoingStoryViewModel) + } + } + + if (ongoingStoryList.getOrThrow().olderStoryList.isNotEmpty()) { + val showDivider = itemList.isNotEmpty() + val olderSectionTitleViewModel = + SectionTitleViewModel(fragment.getString(R.string.ongoing_story_last_month), showDivider) + itemList.add(olderSectionTitleViewModel) + for (promotedStory in ongoingStoryList.getOrThrow().olderStoryList) { + val ongoingStoryViewModel = OngoingStoryViewModel(promotedStory, fragment as OngoingStoryClickListener) + itemList.add(ongoingStoryViewModel) + } + } + } + return itemList + } +} diff --git a/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingFragmentPresenter.kt b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingFragmentPresenter.kt index 218db1ea811..44a5475e196 100755 --- a/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingFragmentPresenter.kt @@ -5,17 +5,16 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.lifecycle.LiveData import androidx.lifecycle.Observer -import androidx.lifecycle.Transformations -import org.oppia.app.R import org.oppia.app.databinding.ContinuePlayingFragmentBinding +import org.oppia.app.databinding.OngoingStoryCardBinding +import org.oppia.app.databinding.SectionTitleBinding import org.oppia.app.fragment.FragmentScope import org.oppia.app.home.RouteToExplorationListener -import org.oppia.app.model.OngoingStoryList import org.oppia.app.model.PromotedStory +import org.oppia.app.recyclerview.BindableAdapter +import org.oppia.app.viewmodel.ViewModelProvider import org.oppia.domain.exploration.ExplorationDataController -import org.oppia.domain.topic.TopicListController import org.oppia.util.data.AsyncResult import org.oppia.util.logging.Logger import javax.inject.Inject @@ -27,71 +26,56 @@ class ContinuePlayingFragmentPresenter @Inject constructor( private val fragment: Fragment, private val logger: Logger, private val explorationDataController: ExplorationDataController, - private val topicListController: TopicListController + private val viewModelProvider: ViewModelProvider ) { private val routeToExplorationListener = activity as RouteToExplorationListener private lateinit var binding: ContinuePlayingFragmentBinding - private lateinit var ongoingListAdapter: OngoingListAdapter - - private val itemList: MutableList = ArrayList() - fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? { binding = ContinuePlayingFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false) - + val viewModel = getContinuePlayModel() binding.continuePlayingToolbar.setNavigationOnClickListener { (activity as ContinuePlayingActivity).finish() } - ongoingListAdapter = OngoingListAdapter(itemList) - binding.ongoingStoryRecyclerView.apply { - adapter = ongoingListAdapter + adapter = createRecyclerViewAdapter() } binding.let { it.lifecycleOwner = fragment + it.viewModel = viewModel } - - subscribeToOngoingStoryList() - return binding.root } - private val ongoingStoryListSummaryResultLiveData: LiveData> by lazy { - topicListController.getOngoingStoryList() - } - - private fun subscribeToOngoingStoryList() { - getAssumedSuccessfulOngoingStoryList().observe(fragment, Observer { it -> - if (it.recentStoryCount > 0) { - val recentSectionTitleViewModel = - SectionTitleViewModel(activity.getString(R.string.ongoing_story_last_week), false) - itemList.add(recentSectionTitleViewModel) - for (promotedStory in it.recentStoryList) { - val ongoingStoryViewModel = OngoingStoryViewModel(promotedStory, fragment as OngoingStoryClickListener) - itemList.add(ongoingStoryViewModel) - } - } - - if (it.olderStoryCount > 0) { - val showDivider = itemList.isNotEmpty() - val olderSectionTitleViewModel = - SectionTitleViewModel(activity.getString(R.string.ongoing_story_last_month), showDivider) - itemList.add(olderSectionTitleViewModel) - for (promotedStory in it.olderStoryList) { - val ongoingStoryViewModel = OngoingStoryViewModel(promotedStory, fragment as OngoingStoryClickListener) - itemList.add(ongoingStoryViewModel) + private fun createRecyclerViewAdapter(): BindableAdapter { + return BindableAdapter.MultiTypeBuilder + .newBuilder { viewModel -> + when (viewModel) { + is SectionTitleViewModel -> ContinuePlayingItemViewModel.ViewType.VIEW_TYPE_SECTION_TITLE_TEXT + is OngoingStoryViewModel -> ContinuePlayingItemViewModel.ViewType.VIEW_TYPE_SECTION_STORY_ITEM + else -> throw IllegalArgumentException("Encountered unexpected view model: $viewModel") } } - ongoingListAdapter.notifyDataSetChanged() - }) + .registerViewDataBinder( + viewType = ContinuePlayingItemViewModel.ViewType.VIEW_TYPE_SECTION_TITLE_TEXT, + inflateDataBinding = SectionTitleBinding::inflate, + setViewModel = SectionTitleBinding::setViewModel, + transformViewModel = { it as SectionTitleViewModel } + ) + .registerViewDataBinder( + viewType = ContinuePlayingItemViewModel.ViewType.VIEW_TYPE_SECTION_STORY_ITEM, + inflateDataBinding = OngoingStoryCardBinding::inflate, + setViewModel = OngoingStoryCardBinding::setViewModel, + transformViewModel = { it as OngoingStoryViewModel } + ) + .build() } - private fun getAssumedSuccessfulOngoingStoryList(): LiveData { - // If there's an error loading the data, assume the default. - return Transformations.map(ongoingStoryListSummaryResultLiveData) { it.getOrDefault(OngoingStoryList.getDefaultInstance()) } + private fun getContinuePlayModel(): ContinuePlayViewModel { + return viewModelProvider.getForFragment(fragment, ContinuePlayViewModel::class.java) } fun onOngoingStoryClicked(promotedStory: PromotedStory) { diff --git a/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingItemViewModel.kt b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingItemViewModel.kt index 391222c7c89..a29fafb21d6 100644 --- a/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingItemViewModel.kt +++ b/app/src/main/java/org/oppia/app/home/continueplaying/ContinuePlayingItemViewModel.kt @@ -2,5 +2,10 @@ package org.oppia.app.home.continueplaying import org.oppia.app.viewmodel.ObservableViewModel -/** The root [ViewModel] for all individual items that may be displayed in continue-playing fragment recycler view. */ -abstract class ContinuePlayingItemViewModel: ObservableViewModel() +/** The root [ViewModel] for all individual items that may be displayed in [ContinuePlayingFragment] [RecyclerView]. */ +abstract class ContinuePlayingItemViewModel : ObservableViewModel() { + enum class ViewType { + VIEW_TYPE_SECTION_TITLE_TEXT, + VIEW_TYPE_SECTION_STORY_ITEM + } +} diff --git a/app/src/main/java/org/oppia/app/home/continueplaying/OngoingListAdapter.kt b/app/src/main/java/org/oppia/app/home/continueplaying/OngoingListAdapter.kt deleted file mode 100644 index 3658b172dc8..00000000000 --- a/app/src/main/java/org/oppia/app/home/continueplaying/OngoingListAdapter.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.oppia.app.home.continueplaying - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.oppia.app.databinding.OngoingStoryCardBinding -import org.oppia.app.databinding.SectionTitleBinding - -private const val VIEW_TYPE_SECTION_TITLE_TEXT = 1 -private const val VIEW_TYPE_SECTION_STORY_ITEM = 2 - -/** Adapter to inflate different items/views inside [RecyclerView] for Ongoing Story List. */ -class OngoingListAdapter( - private val itemList: MutableList -) : RecyclerView.Adapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - // TODO(#216): Generalize this binding to make adding future items easier. - VIEW_TYPE_SECTION_TITLE_TEXT -> { - val inflater = LayoutInflater.from(parent.context) - val binding = - SectionTitleBinding.inflate( - inflater, - parent, - /* attachToParent= */ false - ) - SectionTitleViewHolder(binding) - } - VIEW_TYPE_SECTION_STORY_ITEM -> { - val inflater = LayoutInflater.from(parent.context) - val binding = - OngoingStoryCardBinding.inflate( - inflater, - parent, - /* attachToParent= */ false - ) - OngoingStoryViewHolder(binding) - } - else -> throw IllegalArgumentException("Invalid view type: $viewType") - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder.itemViewType) { - VIEW_TYPE_SECTION_TITLE_TEXT -> { - (holder as SectionTitleViewHolder).bind(itemList[position] as SectionTitleViewModel) - } - VIEW_TYPE_SECTION_STORY_ITEM -> { - (holder as OngoingStoryViewHolder).bind(itemList[position] as OngoingStoryViewModel) - } - else -> throw IllegalArgumentException("Invalid item view type: ${holder.itemViewType}") - } - } - - override fun getItemViewType(position: Int): Int { - return when (itemList[position]) { - is SectionTitleViewModel -> { - VIEW_TYPE_SECTION_TITLE_TEXT - } - is OngoingStoryViewModel -> { - VIEW_TYPE_SECTION_STORY_ITEM - } - else -> throw IllegalArgumentException("Invalid type of data $position with item ${itemList[position]}") - } - } - - override fun getItemCount(): Int { - return itemList.size - } - - private class SectionTitleViewHolder( - val binding: SectionTitleBinding - ) : RecyclerView.ViewHolder(binding.root) { - internal fun bind(sectionTitleViewModel: SectionTitleViewModel) { - binding.viewModel = sectionTitleViewModel - } - } - - private class OngoingStoryViewHolder( - val binding: OngoingStoryCardBinding - ) : RecyclerView.ViewHolder(binding.root) { - internal fun bind(ongoingStoryViewModel: OngoingStoryViewModel) { - binding.viewModel = ongoingStoryViewModel - } - } -} diff --git a/app/src/main/res/layout/continue_playing_fragment.xml b/app/src/main/res/layout/continue_playing_fragment.xml index 213b0eac59e..df9e08e884b 100644 --- a/app/src/main/res/layout/continue_playing_fragment.xml +++ b/app/src/main/res/layout/continue_playing_fragment.xml @@ -2,6 +2,13 @@ + + + + +