From 7fb5ceeda46a0ef9a14d34061036b60f943c4a85 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 6 Apr 2022 14:37:25 -0300 Subject: [PATCH] Allow hidden story viewing. --- .../ConversationParentFragment.java | 2 +- .../securesms/database/MessageDatabase.java | 2 +- .../securesms/database/MmsDatabase.java | 11 +++++--- .../securesms/database/SmsDatabase.java | 2 +- .../bottomsheet/RecipientDialogViewModel.java | 4 +-- .../stories/landing/StoriesLandingFragment.kt | 2 +- .../tabs/ConversationListTabRepository.kt | 13 +++++++--- .../stories/viewer/StoryViewerActivity.kt | 7 ++++-- .../stories/viewer/StoryViewerFragment.kt | 9 +++++-- .../stories/viewer/StoryViewerRepository.kt | 10 ++++++-- .../stories/viewer/StoryViewerViewModel.kt | 6 +++-- .../viewer/StoryViewerViewModelTest.kt | 25 ++++++++++--------- 12 files changed, 60 insertions(+), 33 deletions(-) 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 01a14ed2a..b93d72747 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)); + startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory())); } private void handleConversationSettings() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index 16abe806f..242d44aec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -190,7 +190,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract @NonNull Reader getAllStoriesFor(@NonNull RecipientId recipientId); public abstract @NonNull MessageId getStoryId(@NonNull RecipientId authorId, long sentTimestamp) throws NoSuchMessageException; public abstract int getNumberOfStoryReplies(long parentStoryId); - public abstract long getUnreadStoryThreadCount(); + public abstract @NonNull List getUnreadStoryThreadRecipientIds(); public abstract boolean containsStories(long threadId); public abstract boolean hasSelfReplyInStory(long parentStoryId); public abstract @NonNull Cursor getStoryReplies(long parentStoryId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index f4b5c0bf6..1f1c8fb55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -650,7 +650,7 @@ public class MmsDatabase extends MessageDatabase { } @Override - public long getUnreadStoryThreadCount() { + public @NonNull List getUnreadStoryThreadRecipientIds() { SQLiteDatabase db = getReadableDatabase(); String query = "SELECT DISTINCT " + ThreadDatabase.RECIPIENT_ID + "\n" + "FROM " + TABLE_NAME + "\n" @@ -660,11 +660,16 @@ public class MmsDatabase extends MessageDatabase { try (Cursor cursor = db.rawQuery(query, null)) { if (cursor != null) { - return cursor.getCount(); + List recipientIds = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + recipientIds.add(RecipientId.from(cursor.getLong(0))); + } + + return recipientIds; } } - return 0; + return Collections.emptyList(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index e7b4011a1..e383d93fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -1427,7 +1427,7 @@ public class SmsDatabase extends MessageDatabase { } @Override - public long getUnreadStoryThreadCount() { + public @NonNull List getUnreadStoryThreadRecipientIds() { throw new UnsupportedOperationException(); } 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 4c3463101..f17e1e7cf 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 @@ -140,7 +140,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)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory())); } } @@ -176,7 +176,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)); + activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory())); } } 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 ee06a7735..541997bf1 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 @@ -165,7 +165,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l Toast.makeText(requireContext(), R.string.message_recipients_list_item__resend, Toast.LENGTH_SHORT).show() } else { val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "") - startActivity(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id), options.toBundle()) + startActivity(StoryViewerActivity.createIntent(requireContext(), model.data.storyRecipient.id, -1L, model.data.isHidden), options.toBundle()) } }, onForwardStory = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabRepository.kt index 72f215967..d94272571 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabRepository.kt @@ -5,6 +5,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import org.thoughtcrime.securesms.database.DatabaseObserver import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient class ConversationListTabRepository { @@ -21,14 +22,18 @@ class ConversationListTabRepository { } fun getNumberOfUnseenStories(): Observable { - return Observable.create { + return Observable.create { emitter -> + fun refresh() { + emitter.onNext(SignalDatabase.mms.unreadStoryThreadRecipientIds.map { Recipient.resolved(it) }.filterNot { it.shouldHideStory() }.size.toLong()) + } + val listener = DatabaseObserver.Observer { - it.onNext(SignalDatabase.mms.unreadStoryThreadCount) + refresh() } ApplicationDependencies.getDatabaseObserver().registerConversationListObserver(listener) - it.setCancellable { ApplicationDependencies.getDatabaseObserver().unregisterObserver(listener) } - it.onNext(SignalDatabase.mms.unreadStoryThreadCount) + emitter.setCancellable { ApplicationDependencies.getDatabaseObserver().unregisterObserver(listener) } + refresh() }.subscribeOn(Schedulers.io()) } } 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 cfd64e7bb..eccb37902 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 @@ -27,7 +27,8 @@ class StoryViewerActivity : PassphraseRequiredActivity() { R.id.fragment_container, StoryViewerFragment.create( intent.getParcelableExtra(ARG_START_RECIPIENT_ID)!!, - intent.getLongExtra(ARG_START_STORY_ID, -1L) + intent.getLongExtra(ARG_START_STORY_ID, -1L), + intent.getBooleanExtra(ARG_HIDDEN_STORIES, false) ) ) .commit() @@ -37,12 +38,14 @@ class StoryViewerActivity : PassphraseRequiredActivity() { companion object { private const val ARG_START_RECIPIENT_ID = "start.recipient.id" private const val ARG_START_STORY_ID = "start.story.id" + private const val ARG_HIDDEN_STORIES = "hidden_stories" @JvmStatic - fun createIntent(context: Context, recipientId: RecipientId, storyId: Long = -1L): Intent { + fun createIntent(context: Context, recipientId: RecipientId, storyId: Long = -1L, onlyIncludeHiddenStories: Boolean = false): Intent { return Intent(context, StoryViewerActivity::class.java) .putExtra(ARG_START_RECIPIENT_ID, recipientId) .putExtra(ARG_START_STORY_ID, storyId) + .putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories) } } } 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 0fb0c725f..70b610c74 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 @@ -21,7 +21,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val viewModel: StoryViewerViewModel by viewModels( factoryProducer = { - StoryViewerViewModel.Factory(storyRecipientId, StoryViewerRepository()) + StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, StoryViewerRepository()) } ) @@ -31,6 +31,9 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie private val storyId: Long get() = requireArguments().getLong(ARG_START_STORY_ID, -1L) + private val onlyIncludeHiddenStories: Boolean + get() = requireArguments().getBoolean(ARG_HIDDEN_STORIES) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { storyPager = view.findViewById(R.id.story_item_pager) @@ -90,12 +93,14 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie companion object { private const val ARG_START_RECIPIENT_ID = "start.recipient.id" private const val ARG_START_STORY_ID = "start.story.id" + private const val ARG_HIDDEN_STORIES = "hidden_stories" - fun create(storyRecipientId: RecipientId, storyId: Long): Fragment { + fun create(storyRecipientId: RecipientId, storyId: Long, onlyIncludeHiddenStories: Boolean): Fragment { return StoryViewerFragment().apply { arguments = Bundle().apply { putParcelable(ARG_START_RECIPIENT_ID, storyRecipientId) putLong(ARG_START_STORY_ID, storyId) + putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerRepository.kt index 0db6acc2c..afa48cdb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerRepository.kt @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId * Open for testing */ open class StoryViewerRepository { - fun getStories(): Single> { + fun getStories(hiddenStories: Boolean): Single> { return Single.create { emitter -> val myStoriesId = SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY) val myStories = Recipient.resolved(myStoriesId) @@ -21,7 +21,13 @@ open class StoryViewerRepository { } else { recipient } - }.keys.filterNot { it.shouldHideStory() }.map { it.id } + }.keys.filter { + if (hiddenStories) { + it.shouldHideStory() + } else { + !it.shouldHideStory() + } + }.map { it.id } emitter.onSuccess( if (recipientIds.contains(myStoriesId)) { 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 9324ccc2c..885d1f96f 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 @@ -13,6 +13,7 @@ import kotlin.math.max class StoryViewerViewModel( private val startRecipientId: RecipientId, + private val onlyIncludeHiddenStories: Boolean, private val repository: StoryViewerRepository ) : ViewModel() { @@ -38,7 +39,7 @@ class StoryViewerViewModel( private fun refresh() { disposables.clear() - disposables += repository.getStories().subscribe { recipientIds -> + disposables += repository.getStories(onlyIncludeHiddenStories).subscribe { recipientIds -> store.update { val page: Int = if (it.pages.isNotEmpty()) { val oldPage = it.page @@ -125,10 +126,11 @@ class StoryViewerViewModel( class Factory( private val startRecipientId: RecipientId, + private val onlyIncludeHiddenStories: Boolean, private val repository: StoryViewerRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(StoryViewerViewModel(startRecipientId, repository)) as T + return modelClass.cast(StoryViewerViewModel(startRecipientId, onlyIncludeHiddenStories, 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 edd6f99e2..5ca58f142 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 @@ -7,6 +7,7 @@ import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -32,10 +33,10 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = RecipientId.from(2L) - whenever(repository.getStories()).doReturn(Single.just(stories)) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) // WHEN - val testSubject = StoryViewerViewModel(startStory, repository) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // THEN @@ -50,8 +51,8 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = RecipientId.from(1L) - whenever(repository.getStories()).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, repository) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // WHEN @@ -70,8 +71,8 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = stories.last() - whenever(repository.getStories()).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, repository) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // WHEN @@ -90,8 +91,8 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = stories.last() - whenever(repository.getStories()).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, repository) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // WHEN @@ -110,8 +111,8 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = stories.first() - whenever(repository.getStories()).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, repository) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // WHEN @@ -130,8 +131,8 @@ class StoryViewerViewModelTest { // GIVEN val stories: List = (1L..5L).map(RecipientId::from) val startStory = stories.first() - whenever(repository.getStories()).doReturn(Single.just(stories)) - val testSubject = StoryViewerViewModel(startStory, repository) + whenever(repository.getStories(any())).doReturn(Single.just(stories)) + val testSubject = StoryViewerViewModel(startStory, false, repository) testScheduler.triggerActions() // WHEN