diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt index f4d5c2e21..99b0620b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryDialogs.kt @@ -41,4 +41,21 @@ object StoryDialogs { } .show() } + + fun hideStory( + context: Context, + recipientName: String, + onCancelled: () -> Unit = {}, + onHideStoryConfirmed: () -> Unit, + ) { + MaterialAlertDialogBuilder(context, R.style.ThemeOverlay_Signal_MaterialAlertDialog) + .setTitle(R.string.StoriesLandingFragment__hide_story) + .setMessage(context.getString(R.string.StoriesLandingFragment__new_story_updates, recipientName)) + .setPositiveButton(R.string.StoriesLandingFragment__hide) { _, _ -> + onHideStoryConfirmed() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> onCancelled() } + .setOnCancelListener { onCancelled() } + .show() + } } 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 5bef72bea..2a39394e1 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 @@ -16,7 +16,6 @@ import androidx.core.app.SharedElementCallback import androidx.core.view.ViewCompat import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar @@ -33,7 +32,6 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.permissions.Permissions @@ -273,8 +271,8 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l storyThumbTextModel = text, storyThumbUri = image, storyThumbBlur = blur, - recipientIds = viewModel.getRecipientIds(model.data.isHidden, true), - isUnviewedOnly = model.data.storyViewState == StoryViewState.UNVIEWED, + recipientIds = viewModel.getRecipientIds(model.data.isHidden, false), + isUnviewedOnly = false, isFromInfoContextMenuAction = isFromInfoContextMenuAction ) ), @@ -288,19 +286,14 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l } private fun handleHideStory(model: StoriesLandingItem.Model) { - MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Signal_MaterialAlertDialog) - .setTitle(R.string.StoriesLandingFragment__hide_story) - .setMessage(getString(R.string.StoriesLandingFragment__new_story_updates, model.data.storyRecipient.getShortDisplayName(requireContext()))) - .setPositiveButton(R.string.StoriesLandingFragment__hide) { _, _ -> - viewModel.setHideStory(model.data.storyRecipient, true).subscribe { - Snackbar.make(cameraFab, R.string.StoriesLandingFragment__story_hidden, Snackbar.LENGTH_SHORT) - .setAnchorView(cameraFab) - .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_FADE) - .show() - } + StoryDialogs.hideStory(requireContext(), model.data.storyRecipient.getShortDisplayName(requireContext())) { + viewModel.setHideStory(model.data.storyRecipient, true).subscribe { + Snackbar.make(cameraFab, R.string.StoriesLandingFragment__story_hidden, Snackbar.LENGTH_SHORT) + .setAnchorView(cameraFab) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_FADE) + .show() } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { 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 53a0942cf..8d3ce5e9c 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 @@ -66,6 +66,10 @@ class StoryViewerFragment : lifecycleDisposable.bindTo(viewLifecycleOwner) lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state -> + if (state.noPosts) { + requireActivity().finish() + } + adapter.setPages(state.pages) if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) { pagerOnPageSelectedLock = true @@ -96,6 +100,17 @@ class StoryViewerFragment : storyCrossfader.alpha = 0f } } + + if (savedInstanceState != null && savedInstanceState.containsKey(HIDDEN)) { + val ids: List = savedInstanceState.getParcelableArrayList(HIDDEN)!! + viewModel.addHiddenAndRefresh(ids.toSet()) + } else { + viewModel.refresh() + } + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putParcelableArrayList(HIDDEN, ArrayList(viewModel.getHidden())) } override fun onResume() { @@ -119,7 +134,7 @@ class StoryViewerFragment : } override fun onStoryHidden(recipientId: RecipientId) { - viewModel.onRecipientHidden() + viewModel.addHiddenAndRefresh(setOf(recipientId)) } override fun onReadyToAnimate() { @@ -150,6 +165,7 @@ class StoryViewerFragment : companion object { private const val ARGS = "args" + private const val HIDDEN = "hidden" fun create(storyViewerArgs: StoryViewerArgs): Fragment { return StoryViewerFragment().apply { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt index 0262f9d3e..a6d6d83be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt @@ -17,11 +17,12 @@ class StoryViewerPagerAdapter( private val isFromInfoContextMenuAction: Boolean ) : FragmentStateAdapter(fragment) { - private var pages: List = emptyList() + private val pages: MutableList = mutableListOf() fun setPages(newPages: List) { - val oldPages = pages - pages = newPages + val oldPages = ArrayList(pages) + pages.clear() + pages.addAll(newPages) val callback = Callback(oldPages, pages) DiffUtil.calculateDiff(callback).dispatchUpdatesTo(this) @@ -34,6 +35,10 @@ class StoryViewerPagerAdapter( override fun getItemCount(): Int = pages.size + override fun getItemId(position: Int): Long { + return pages[position].toLong() + } + override fun createFragment(position: Int): Fragment { return StoryViewerPageFragment.create(pages[position], initialStoryId, isFromNotification, groupReplyStartPosition, isUnviewedOnly, isOutgoingOnly, isFromInfoContextMenuAction) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt index 7b791a36e..3f7c13a05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerState.kt @@ -13,7 +13,8 @@ data class StoryViewerState( val crossfadeSource: CrossfadeSource, val crossfadeTarget: CrossfadeTarget? = null, val loadState: LoadState = LoadState(), - val skipCrossfade: Boolean = false + val skipCrossfade: Boolean = false, + val noPosts: Boolean = false ) { sealed class CrossfadeSource { object None : CrossfadeSource() 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 95834eff2..91f775815 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 @@ -39,6 +39,8 @@ class StoryViewerViewModel( val stateSnapshot: StoryViewerState get() = store.state val state: Flowable = store.stateFlowable + private val hidden = mutableSetOf() + private val scrollStatePublisher: MutableLiveData = MutableLiveData(false) val isScrolling: LiveData = scrollStatePublisher @@ -53,10 +55,13 @@ class StoryViewerViewModel( val isChildScrolling: Observable = childScrollStatePublisher.distinctUntilChanged() - init { + fun addHiddenAndRefresh(hidden: Set) { + this.hidden.addAll(hidden) refresh() } + fun getHidden(): Set = hidden + fun setCrossfadeTarget(messageRecord: MmsMessageRecord) { store.update { it.copy(crossfadeTarget = StoryViewerState.CrossfadeTarget.Record(messageRecord)) @@ -85,7 +90,7 @@ class StoryViewerViewModel( private fun getStories(): Single> { return if (storyViewerArgs.recipientIds.isNotEmpty()) { - Single.just(storyViewerArgs.recipientIds) + Single.just(storyViewerArgs.recipientIds - hidden) } else { repository.getStories( hiddenStories = storyViewerArgs.isInHiddenStoryMode, @@ -95,7 +100,7 @@ class StoryViewerViewModel( } } - private fun refresh() { + fun refresh() { disposables.clear() disposables += repository.getFirstStory(storyViewerArgs.recipientId, storyViewerArgs.isUnviewedOnly, storyViewerArgs.storyId).subscribe { record -> store.update { @@ -119,7 +124,7 @@ class StoryViewerViewModel( } else { it.page } - updatePages(it.copy(pages = recipientIds), page) + updatePages(it.copy(pages = recipientIds), page).copy(noPosts = recipientIds.isEmpty()) } } disposables += state @@ -167,10 +172,6 @@ class StoryViewerViewModel( } } - fun onRecipientHidden() { - refresh() - } - private fun updatePages(state: StoryViewerState, page: Int): StoryViewerState { val newPage = resolvePage(page, state.pages) val prevPage = if (newPage == state.page) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index 424222d9e..e127eaa41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView import org.thoughtcrime.securesms.stories.StorySlateView import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu +import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs import org.thoughtcrime.securesms.stories.viewer.StoryViewerViewModel import org.thoughtcrime.securesms.stories.viewer.StoryVolumeViewModel import org.thoughtcrime.securesms.stories.viewer.info.StoryInfoBottomSheetDialogFragment @@ -979,8 +980,11 @@ class StoryViewerPageFragment : startActivity(ConversationIntents.createBuilder(requireContext(), storyRecipientId, -1L).build()) }, onHide = { - lifecycleDisposable += viewModel.hideStory().subscribe { - callback.onStoryHidden(storyRecipientId) + viewModel.setIsDisplayingHideDialog(true) + StoryDialogs.hideStory(requireContext(), Recipient.resolved(storyRecipientId).getDisplayName(requireContext()), { viewModel.setIsDisplayingHideDialog(true) }) { + lifecycleDisposable += viewModel.hideStory().subscribe { + callback.onStoryHidden(storyRecipientId) + } } }, onShare = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt index c1914bec5..0011eee7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt @@ -222,6 +222,10 @@ class StoryViewerPageViewModel( storyViewerPlaybackStore.update { it.copy(isDisplayingDeleteDialog = isDisplayingDeleteDialog) } } + fun setIsDisplayingHideDialog(isDisplayingHideDialog: Boolean) { + storyViewerPlaybackStore.update { it.copy(isDisplayingHideDialog = isDisplayingHideDialog) } + } + fun setIsDisplayingViewsAndRepliesDialog(isDisplayingViewsAndRepliesDialog: Boolean) { storyViewerPlaybackStore.update { it.copy(isDisplayingViewsAndRepliesDialog = isDisplayingViewsAndRepliesDialog) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt index 585cc5596..7f51a6b95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt @@ -5,6 +5,7 @@ data class StoryViewerPlaybackState( val isUserTouching: Boolean = false, val isDisplayingForwardDialog: Boolean = false, val isDisplayingDeleteDialog: Boolean = false, + val isDisplayingHideDialog: Boolean = false, val isDisplayingContextMenu: Boolean = false, val isDisplayingViewsAndRepliesDialog: Boolean = false, val isDisplayingDirectReplyDialog: Boolean = false, @@ -44,5 +45,6 @@ data class StoryViewerPlaybackState( isRunningSharedElementAnimation || isDisplayingFirstTimeNavigation || isDisplayingInfoDialog || - isUserScaling + isUserScaling || + isDisplayingHideDialog } 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 cfc4201b1..792e351df 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 @@ -63,6 +63,7 @@ class StoryViewerViewModelTest { ), repository ) + testSubject.refresh() testScheduler.triggerActions() // THEN @@ -107,6 +108,7 @@ class StoryViewerViewModelTest { ), repository ) + testSubject.refresh() testScheduler.triggerActions() // WHEN @@ -133,6 +135,7 @@ class StoryViewerViewModelTest { ), repository ) + testSubject.refresh() testScheduler.triggerActions() // WHEN @@ -159,6 +162,7 @@ class StoryViewerViewModelTest { ), repository ) + testSubject.refresh() testScheduler.triggerActions() // WHEN @@ -185,6 +189,7 @@ class StoryViewerViewModelTest { ), repository ) + testSubject.refresh() testScheduler.triggerActions() // WHEN