diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt index 308956935e..9c2d2a3175 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsActivity.kt @@ -62,6 +62,7 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent { StartLocation.NOTIFICATION_PROFILE_DETAILS -> AppSettingsFragmentDirections.actionDirectToNotificationProfileDetails( EditNotificationProfileScheduleFragmentArgs.fromBundle(intent.getBundleExtra(START_ARGUMENTS)!!).profileId ) + StartLocation.PRIVACY -> AppSettingsFragmentDirections.actionDirectToPrivacy() } } @@ -168,6 +169,9 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent { @JvmStatic fun createNotificationProfile(context: Context): Intent = getIntentForStartLocation(context, StartLocation.CREATE_NOTIFICATION_PROFILE) + @JvmStatic + fun privacy(context: Context): Intent = getIntentForStartLocation(context, StartLocation.PRIVACY) + @JvmStatic fun notificationProfileDetails(context: Context, profileId: Long): Intent { val arguments = EditNotificationProfileScheduleFragmentArgs.Builder(profileId, false) @@ -197,7 +201,8 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent { MANAGE_SUBSCRIPTIONS(8), NOTIFICATION_PROFILES(9), CREATE_NOTIFICATION_PROFILE(10), - NOTIFICATION_PROFILE_DETAILS(11); + NOTIFICATION_PROFILE_DETAILS(11), + PRIVACY(12); companion object { fun fromCode(code: Int?): StartLocation { 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 368b0d9864..ce98348103 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 @@ -339,7 +339,7 @@ class StoryViewerPageFragment : if (state.posts.isNotEmpty() && state.selectedPostIndex in state.posts.indices) { val post = state.posts[state.selectedPostIndex] - presentViewsAndReplies(post, state.replyState) + presentViewsAndReplies(post, state.replyState, state.isReceiptsEnabled) presentSenderAvatar(senderAvatar, post) presentGroupAvatar(groupAvatar, post) presentFrom(from, post) @@ -449,6 +449,7 @@ class StoryViewerPageFragment : override fun onResume() { super.onResume() viewModel.setIsFragmentResumed(true) + viewModel.checkReadReceiptState() } override fun onPause() { @@ -823,7 +824,7 @@ class StoryViewerPageFragment : } } - private fun presentViewsAndReplies(post: StoryPost, replyState: StoryViewerPageState.ReplyState) { + private fun presentViewsAndReplies(post: StoryPost, replyState: StoryViewerPageState.ReplyState, isReceiptsEnabled: Boolean) { if (replyState == StoryViewerPageState.ReplyState.NONE) { viewsAndReplies.visible = false return @@ -835,14 +836,25 @@ class StoryViewerPageFragment : val replies = resources.getQuantityString(R.plurals.StoryViewerFragment__d_replies, post.replyCount, post.replyCount) if (Recipient.self() == post.sender) { - if (post.replyCount == 0) { - viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) - viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END - viewsAndReplies.text = views + if (isReceiptsEnabled) { + if (post.replyCount == 0) { + viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) + viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END + viewsAndReplies.text = views + } else { + viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) + viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END + viewsAndReplies.text = getString(R.string.StoryViewerFragment__s_s, views, replies) + } } else { - viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) - viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END - viewsAndReplies.text = getString(R.string.StoryViewerFragment__s_s, views, replies) + if (post.replyCount == 0) { + viewsAndReplies.icon = null + viewsAndReplies.setText(R.string.StoryViewerPageFragment__views_off) + } else { + viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) + viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_END + viewsAndReplies.text = replies + } } } else if (post.replyCount > 0) { viewsAndReplies.setIconResource(R.drawable.ic_chevron_end_24) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt index 5196e0c84c..95f805f146 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stories.Stories import org.thoughtcrime.securesms.util.Base64 +import org.thoughtcrime.securesms.util.TextSecurePreferences /** * Open for testing. @@ -35,6 +36,8 @@ open class StoryViewerPageRepository(context: Context) { private val context = context.applicationContext + fun isReadReceiptsEnabled(): Boolean = TextSecurePreferences.isReadReceiptsEnabled(context) + private fun getStoryRecords(recipientId: RecipientId, isUnviewedOnly: Boolean): Observable> { return Observable.create { emitter -> val recipient = Recipient.resolved(recipientId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt index 429735d503..a28a22bbdb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt @@ -6,7 +6,8 @@ data class StoryViewerPageState( val replyState: ReplyState = ReplyState.NONE, val isFirstPage: Boolean = false, val isDisplayingInitialState: Boolean = false, - val isReady: Boolean = false + val isReady: Boolean = false, + val isReceiptsEnabled: Boolean ) { /** * Indicates which Reply method is available when the user swipes on the dialog 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 88ad50bedc..c6bf77a9ac 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 @@ -28,7 +28,7 @@ class StoryViewerPageViewModel( private val repository: StoryViewerPageRepository ) : ViewModel() { - private val store = RxStore(StoryViewerPageState()) + private val store = RxStore(StoryViewerPageState(isReceiptsEnabled = repository.isReadReceiptsEnabled())) private val disposables = CompositeDisposable() private val storyViewerDialogSubject: Subject> = PublishSubject.create() @@ -46,6 +46,16 @@ class StoryViewerPageViewModel( refresh() } + fun checkReadReceiptState() { + val isReceiptsEnabledInState = getStateSnapshot().isReceiptsEnabled + val isReceiptsEnabledInRepository = repository.isReadReceiptsEnabled() + if (isReceiptsEnabledInState xor isReceiptsEnabledInRepository) { + store.update { + it.copy(isReceiptsEnabled = isReceiptsEnabledInRepository) + } + } + } + fun refresh() { disposables.clear() disposables += repository.getStoryPostsFor(recipientId, isUnviewedOnly).subscribe { posts -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsFragment.kt index 72bb1719af..c2f948222b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsFragment.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment +import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerChild import org.thoughtcrime.securesms.stories.viewer.reply.StoryViewsAndRepliesPagerParent @@ -37,15 +38,28 @@ class StoryViewsFragment : StoryViewItem.register(adapter) val emptyNotice: View = requireView().findViewById(R.id.empty_notice) + val disabledNotice: View = requireView().findViewById(R.id.disabled_notice) + val disabledButton: View = requireView().findViewById(R.id.disabled_button) + + disabledButton.setOnClickListener { + startActivity(AppSettingsActivity.privacy(requireContext())) + } onPageSelected(findListener()?.selectedChild ?: StoryViewsAndRepliesPagerParent.Child.VIEWS) viewModel.state.observe(viewLifecycleOwner) { emptyNotice.visible = it.loadState == StoryViewsState.LoadState.READY && it.views.isEmpty() + disabledNotice.visible = it.loadState == StoryViewsState.LoadState.DISABLED + recyclerView?.visible = it.loadState == StoryViewsState.LoadState.READY adapter.submitList(getConfiguration(it).toMappingModelList()) } } + override fun onResume() { + super.onResume() + viewModel.refresh() + } + override fun onPageSelected(child: StoryViewsAndRepliesPagerParent.Child) { recyclerView?.isNestedScrollingEnabled = child == StoryViewsAndRepliesPagerParent.Child.VIEWS } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsRepository.kt index 6c32acdf9e..b1324bcec4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsRepository.kt @@ -7,8 +7,12 @@ import org.thoughtcrime.securesms.database.GroupReceiptDatabase import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.TextSecurePreferences class StoryViewsRepository { + + fun isReadReceiptsEnabled(): Boolean = TextSecurePreferences.isReadReceiptsEnabled(ApplicationDependencies.getApplication()) + fun getViews(storyId: Long): Observable> { return Observable.create> { emitter -> fun refresh() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsState.kt index 079f4cbf0a..b066ebebb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsState.kt @@ -6,6 +6,7 @@ data class StoryViewsState( ) { enum class LoadState { INIT, - READY + READY, + DISABLED } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsViewModel.kt index 0571700a03..b19180ad3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewsViewModel.kt @@ -7,19 +7,27 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import org.thoughtcrime.securesms.util.livedata.Store -class StoryViewsViewModel(storyId: Long, repository: StoryViewsRepository) : ViewModel() { +class StoryViewsViewModel(private val storyId: Long, private val repository: StoryViewsRepository) : ViewModel() { - private val store = Store(StoryViewsState()) + private val store = Store(StoryViewsState(StoryViewsState.LoadState.INIT)) private val disposables = CompositeDisposable() val state: LiveData = store.stateLiveData - init { - disposables += repository.getViews(storyId).subscribe { data -> + fun refresh() { + if (repository.isReadReceiptsEnabled()) { + disposables += repository.getViews(storyId).subscribe { data -> + store.update { + it.copy( + views = data, + loadState = StoryViewsState.LoadState.READY + ) + } + } + } else { store.update { it.copy( - views = data, - loadState = StoryViewsState.LoadState.READY + loadState = StoryViewsState.LoadState.DISABLED ) } } diff --git a/app/src/main/res/color/button_outline_color_selector.xml b/app/src/main/res/color/button_outline_color_selector.xml new file mode 100644 index 0000000000..10dec48f8e --- /dev/null +++ b/app/src/main/res/color/button_outline_color_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/stories_views_fragment.xml b/app/src/main/res/layout/stories_views_fragment.xml index 479da1b1b3..1a0547845f 100644 --- a/app/src/main/res/layout/stories_views_fragment.xml +++ b/app/src/main/res/layout/stories_views_fragment.xml @@ -17,6 +17,45 @@ app:layout_constraintTop_toTopOf="parent" tools:visibility="visible" /> + + + + + + + + diff --git a/app/src/main/res/values/signal_styles.xml b/app/src/main/res/values/signal_styles.xml index 2dd9a1a5ee..03424cfe2a 100644 --- a/app/src/main/res/values/signal_styles.xml +++ b/app/src/main/res/values/signal_styles.xml @@ -127,6 +127,11 @@ 0dp + +