diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 111a462042bcd08f310737a1750617d7a8d4fa16..f6ddab6b4a7fbce44f9c5564d87b14146d904739 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -1616,7 +1616,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect Recipient.resolved(messageRecord.getQuote().getAuthor()).shouldHideStory(), null, null, - null + null, + Collections.emptyList() )); return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 7872f9264b61bf3763bda6862a64f84fc4706d96..af4416e0fb7afa77d7a826665a124fcb022705c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -1244,7 +1244,7 @@ public class ConversationParentFragment extends Fragment } private void handleStoryRingClick() { - startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null, null)); + startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null, null, Collections.emptyList())); } private void handleConversationSettings() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index d13a1cfe6cd773012114dd341b0c9b65c32fb0d4..5aafd18b0e50fad09c85b6283f4c10f2e4d2eb83 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; +import java.util.Collections; import java.util.Objects; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -140,7 +141,7 @@ final class RecipientDialogViewModel extends ViewModel { if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) { onMessageClicked(activity); } else { - activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null, Collections.emptyList())); } } @@ -176,7 +177,7 @@ final class RecipientDialogViewModel extends ViewModel { if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) { activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId())); } else { - activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null, Collections.emptyList())); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt index 53ab7b1cccca7eec87efdc5a16105e508869e6cc..86950e8dc299e1731eaec8d4f35d68cf4e5af0df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt @@ -206,7 +206,19 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l null to record.slideDeck.thumbnailSlide?.uri } - startActivityIfAble(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden, text, image, blur), options.toBundle()) + startActivityIfAble( + StoryViewerActivity.createIntent( + context = requireContext(), + recipientId = model.data.storyRecipient.id, + storyId = -1L, + onlyIncludeHiddenStories = model.data.isHidden, + storyThumbTextModel = text, + storyThumbUri = image, + storyThumbBlur = blur, + recipientIds = viewModel.getRecipientIds(model.data.isHidden) + ), + options.toBundle() + ) } }, onForwardStory = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt index fab28b69e072a74c4d3bc97dfd681516dc249dde..deaed8b536fcdbf176494ee9657b7edc53505740 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingViewModel.kt @@ -8,6 +8,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.util.livedata.Store class StoriesLandingViewModel(private val storiesLandingRepository: StoriesLandingRepository) : ViewModel() { @@ -45,6 +46,10 @@ class StoriesLandingViewModel(private val storiesLandingRepository: StoriesLandi store.update { it.copy(isHiddenContentVisible = isExpanded) } } + fun getRecipientIds(hidden: Boolean): List<RecipientId> { + return store.state.storiesLandingItems.filter { it.isHidden == hidden }.map { it.storyRecipient.id } + } + class Factory(private val storiesLandingRepository: StoriesLandingRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { return modelClass.cast(StoriesLandingViewModel(storiesLandingRepository)) as T diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt index 1aa5d5949cecbe7944a91bb9bb870d85598e4ef9..124cffc200f95338473d70cb7da233230dfe5b3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt @@ -41,7 +41,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll intent.getBooleanExtra(ARG_HIDDEN_STORIES, false), intent.getParcelableExtra(ARG_CROSSFADE_TEXT_MODEL), intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI), - intent.getStringExtra(ARG_CROSSFADE_IMAGE_BLUR) + intent.getStringExtra(ARG_CROSSFADE_IMAGE_BLUR), + intent.getParcelableArrayListExtra(ARG_RECIPIENT_IDS)!! ) ) .commit() @@ -61,6 +62,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model" private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri" private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur" + private const val ARG_RECIPIENT_IDS = "recipient_ids" @JvmStatic fun createIntent( @@ -70,7 +72,8 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll onlyIncludeHiddenStories: Boolean = false, storyThumbTextModel: StoryTextPostModel? = null, storyThumbUri: Uri? = null, - storyThumbBlur: BlurHash? = null + storyThumbBlur: BlurHash? = null, + recipientIds: List<RecipientId> = emptyList() ): Intent { return Intent(context, StoryViewerActivity::class.java) .putExtra(ARG_START_RECIPIENT_ID, recipientId) @@ -79,6 +82,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll .putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel) .putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri) .putExtra(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur?.hash) + .putParcelableArrayListExtra(ARG_RECIPIENT_IDS, ArrayList(recipientIds)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt index 8b8f76905986c59be2e8afc0da72e51fd63262b0..560c757d5850497747e2fd34191469c3b219d949 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt @@ -24,7 +24,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val viewModel: StoryViewerViewModel by viewModels( factoryProducer = { - StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storuThumbBlur, StoryViewerRepository()) + StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storuThumbBlur, recipientIds, StoryViewerRepository()) } ) @@ -46,6 +46,9 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val storuThumbBlur: BlurHash? get() = requireArguments().getString(ARG_CROSSFADE_IMAGE_BLUR)?.let { BlurHash.parseOrNull(it) } + private val recipientIds: List<RecipientId> + get() = requireArguments().getParcelableArrayList(ARG_RECIPIENT_IDS)!! + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { storyPager = view.findViewById(R.id.story_item_pager) @@ -113,6 +116,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model" private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri" private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur" + private const val ARG_RECIPIENT_IDS = "start.recipient.ids" fun create( storyRecipientId: RecipientId, @@ -120,7 +124,8 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie onlyIncludeHiddenStories: Boolean, storyThumbTextModel: StoryTextPostModel? = null, storyThumbUri: Uri? = null, - storyThumbBlur: String? = null + storyThumbBlur: String? = null, + recipientIds: List<RecipientId> = emptyList() ): Fragment { return StoryViewerFragment().apply { arguments = Bundle().apply { @@ -130,6 +135,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel) putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri) putString(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur) + putParcelableArrayList(ARG_RECIPIENT_IDS, ArrayList(recipientIds)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt index e22dc87e73f1792f66e88e343db9cda0d1c15a35..977d89171221dd6d458e3aaa16159ef1580ff631 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import org.thoughtcrime.securesms.blurhash.BlurHash @@ -20,6 +21,7 @@ class StoryViewerViewModel( storyThumbTextModel: StoryTextPostModel?, storyThumbUri: Uri?, storyThumbBlur: BlurHash?, + private val recipientIds: List<RecipientId>, private val repository: StoryViewerRepository, ) : ViewModel() { @@ -64,9 +66,17 @@ class StoryViewerViewModel( scrollStatePublisher.value = isScrolling } + private fun getStories(): Single<List<RecipientId>> { + return if (recipientIds.isNotEmpty()) { + Single.just(recipientIds) + } else { + repository.getStories(onlyIncludeHiddenStories) + } + } + private fun refresh() { disposables.clear() - disposables += repository.getStories(onlyIncludeHiddenStories).subscribe { recipientIds -> + disposables += getStories().subscribe { recipientIds -> store.update { val page: Int = if (it.pages.isNotEmpty()) { val oldPage = it.page @@ -157,10 +167,21 @@ class StoryViewerViewModel( private val storyThumbTextModel: StoryTextPostModel?, private val storyThumbUri: Uri?, private val storyThumbBlur: BlurHash?, + private val recipientIds: List<RecipientId>, private val repository: StoryViewerRepository ) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { - return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storyThumbBlur, repository)) as T + return modelClass.cast( + StoryViewerViewModel( + startRecipientId, + onlyIncludeHiddenStories, + storyThumbTextModel, + storyThumbUri, + storyThumbBlur, + recipientIds, + repository + ) + ) as T } } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt index 2d8c28d0dc843d177c8634b13ec7fe974e62cb1b..8585510b16b3c657bc11856459f7752d735914ab 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt @@ -10,6 +10,8 @@ import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.thoughtcrime.securesms.recipients.RecipientId @@ -28,6 +30,23 @@ class StoryViewerViewModelTest { RxJavaPlugins.reset() } + @Test + fun `Given a list of recipients, when I initialize, then I expect the list`() { + // GIVEN + val repoStories: List<RecipientId> = (1L..5L).map(RecipientId::from) + whenever(repository.getStories(any())).doReturn(Single.just(repoStories)) + + val injectedStories: List<RecipientId> = (6L..10L).map(RecipientId::from) + + // WHEN + val testSubject = StoryViewerViewModel(injectedStories.first(), false, null, null, null, injectedStories, repository) + testScheduler.triggerActions() + + // THEN + verify(repository, never()).getStories(any()) + assertEquals(injectedStories, testSubject.stateSnapshot.pages) + } + @Test fun `Given five stories, when I initialize with story 2, then I expect to be on the right page`() { // GIVEN @@ -36,7 +55,7 @@ class StoryViewerViewModelTest { whenever(repository.getStories(any())).doReturn(Single.just(stories)) // WHEN - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // THEN @@ -52,7 +71,7 @@ class StoryViewerViewModelTest { val stories: List<RecipientId> = (1L..5L).map(RecipientId::from) val startStory = RecipientId.from(1L) whenever(repository.getStories(any())).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // WHEN @@ -72,7 +91,7 @@ class StoryViewerViewModelTest { val stories: List<RecipientId> = (1L..5L).map(RecipientId::from) val startStory = stories.last() whenever(repository.getStories(any())).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // WHEN @@ -92,7 +111,7 @@ class StoryViewerViewModelTest { val stories: List<RecipientId> = (1L..5L).map(RecipientId::from) val startStory = stories.last() whenever(repository.getStories(any())).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // WHEN @@ -112,7 +131,7 @@ class StoryViewerViewModelTest { val stories: List<RecipientId> = (1L..5L).map(RecipientId::from) val startStory = stories.first() whenever(repository.getStories(any())).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // WHEN @@ -132,7 +151,7 @@ class StoryViewerViewModelTest { val stories: List<RecipientId> = (1L..5L).map(RecipientId::from) val startStory = stories.first() whenever(repository.getStories(any())).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, false, null, null, null, repository) + val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository) testScheduler.triggerActions() // WHEN