Implement Stories read receipt off state.

This commit is contained in:
Alex Hart
2022-06-30 14:02:35 -03:00
committed by Greyson Parrelli
parent f3873c8a7c
commit e412cac419
14 changed files with 142 additions and 19 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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<List<MessageRecord>> {
return Observable.create { emitter ->
val recipient = Recipient.resolved(recipientId)

View File

@@ -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

View File

@@ -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<Optional<StoryViewerDialog>> = 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 ->

View File

@@ -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<StoryViewsAndRepliesPagerParent>()?.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
}

View File

@@ -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<List<StoryViewItemData>> {
return Observable.create<List<StoryViewItemData>> { emitter ->
fun refresh() {

View File

@@ -6,6 +6,7 @@ data class StoryViewsState(
) {
enum class LoadState {
INIT,
READY
READY,
DISABLED
}
}

View File

@@ -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<StoryViewsState> = 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
)
}
}