kopia lustrzana https://github.com/ryukoposting/Signal-Android
Implement story ring support.
rodzic
fe088c39c7
commit
2d7655a6bb
|
@ -6,6 +6,7 @@ import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
@ -46,6 +47,14 @@ class AvatarView @JvmOverloads constructor(
|
||||||
avatar.scaleY = 1f
|
avatar.scaleY = 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setStoryRingFromState(storyViewState: StoryViewState) {
|
||||||
|
when (storyViewState) {
|
||||||
|
StoryViewState.NONE -> hideStoryRing()
|
||||||
|
StoryViewState.UNVIEWED -> showStoryRing(true)
|
||||||
|
StoryViewState.VIEWED -> showStoryRing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays Note-to-Self
|
* Displays Note-to-Self
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -266,6 +266,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||||
customPref(
|
customPref(
|
||||||
AvatarPreference.Model(
|
AvatarPreference.Model(
|
||||||
recipient = state.recipient,
|
recipient = state.recipient,
|
||||||
|
storyViewState = state.storyViewState,
|
||||||
onAvatarClick = { avatar ->
|
onAvatarClick = { avatar ->
|
||||||
if (!state.recipient.isSelf) {
|
if (!state.recipient.isSelf) {
|
||||||
// startActivity(StoryViewerActivity.createIntent(requireContext(), state.recipient.id))
|
// startActivity(StoryViewerActivity.createIntent(requireContext(), state.recipient.id))
|
||||||
|
|
|
@ -4,6 +4,8 @@ import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.signal.core.util.concurrent.SignalExecutors
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||||
|
@ -13,6 +15,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.groups.GroupId
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
|
@ -44,6 +47,14 @@ class ConversationSettingsRepository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getStoryViewState(groupId: GroupId): Observable<StoryViewState> {
|
||||||
|
return Observable.fromCallable {
|
||||||
|
SignalDatabase.recipients.getByGroupId(groupId)
|
||||||
|
}.flatMap {
|
||||||
|
StoryViewState.getForRecipientId(it.get())
|
||||||
|
}.observeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
|
||||||
fun getThreadId(recipientId: RecipientId, consumer: (Long) -> Unit) {
|
fun getThreadId(recipientId: RecipientId, consumer: (Long) -> Unit) {
|
||||||
SignalExecutors.BOUNDED.execute {
|
SignalExecutors.BOUNDED.execute {
|
||||||
consumer(SignalDatabase.threads.getThreadIdIfExistsFor(recipientId))
|
consumer(SignalDatabase.threads.getThreadIdIfExistsFor(recipientId))
|
||||||
|
|
|
@ -4,12 +4,14 @@ import android.database.Cursor
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
|
||||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.groups.GroupId
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
|
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
data class ConversationSettingsState(
|
data class ConversationSettingsState(
|
||||||
val threadId: Long = -1,
|
val threadId: Long = -1,
|
||||||
|
val storyViewState: StoryViewState = StoryViewState.NONE,
|
||||||
val recipient: Recipient = Recipient.UNKNOWN,
|
val recipient: Recipient = Recipient.UNKNOWN,
|
||||||
val buttonStripState: ButtonStripPreference.State = ButtonStripPreference.State(),
|
val buttonStripState: ButtonStripPreference.State = ButtonStripPreference.State(),
|
||||||
val disappearingMessagesLifespan: Int = 0,
|
val disappearingMessagesLifespan: Int = 0,
|
||||||
|
|
|
@ -6,12 +6,15 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
import org.signal.core.util.ThreadUtil
|
import org.signal.core.util.ThreadUtil
|
||||||
import org.signal.core.util.concurrent.SignalExecutors
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.LegacyGroupPreference
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.groups.GroupId
|
import org.thoughtcrime.securesms.groups.GroupId
|
||||||
import org.thoughtcrime.securesms.groups.LiveGroup
|
import org.thoughtcrime.securesms.groups.LiveGroup
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
@ -46,6 +49,8 @@ sealed class ConversationSettingsViewModel(
|
||||||
val state: LiveData<ConversationSettingsState> = store.stateLiveData
|
val state: LiveData<ConversationSettingsState> = store.stateLiveData
|
||||||
val events: LiveData<ConversationSettingsEvent> = internalEvents
|
val events: LiveData<ConversationSettingsEvent> = internalEvents
|
||||||
|
|
||||||
|
protected val disposable = CompositeDisposable()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val threadId: LiveData<Long> = Transformations.distinctUntilChanged(Transformations.map(state) { it.threadId })
|
val threadId: LiveData<Long> = Transformations.distinctUntilChanged(Transformations.map(state) { it.threadId })
|
||||||
val updater: LiveData<Long> = LiveDataUtil.combineLatest(threadId, sharedMediaUpdateTrigger) { tId, _ -> tId }
|
val updater: LiveData<Long> = LiveDataUtil.combineLatest(threadId, sharedMediaUpdateTrigger) { tId, _ -> tId }
|
||||||
|
@ -105,6 +110,7 @@ sealed class ConversationSettingsViewModel(
|
||||||
cleared = true
|
cleared = true
|
||||||
openedMediaCursors.forEach { it.ensureClosed() }
|
openedMediaCursors.forEach { it.ensureClosed() }
|
||||||
store.clear()
|
store.clear()
|
||||||
|
disposable.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Cursor?.ensureClosed() {
|
private fun Cursor?.ensureClosed() {
|
||||||
|
@ -126,6 +132,10 @@ sealed class ConversationSettingsViewModel(
|
||||||
private val liveRecipient = Recipient.live(recipientId)
|
private val liveRecipient = Recipient.live(recipientId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
disposable += StoryViewState.getForRecipientId(recipientId).subscribe { storyViewState ->
|
||||||
|
store.update { it.copy(storyViewState = storyViewState) }
|
||||||
|
}
|
||||||
|
|
||||||
store.update(liveRecipient.liveData) { recipient, state ->
|
store.update(liveRecipient.liveData) { recipient, state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
recipient = recipient,
|
recipient = recipient,
|
||||||
|
@ -240,6 +250,10 @@ sealed class ConversationSettingsViewModel(
|
||||||
private val liveGroup = LiveGroup(groupId)
|
private val liveGroup = LiveGroup(groupId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
disposable += repository.getStoryViewState(groupId).subscribe { storyViewState ->
|
||||||
|
store.update { it.copy(storyViewState = storyViewState) }
|
||||||
|
}
|
||||||
|
|
||||||
val recipientAndIsActive = LiveDataUtil.combineLatest(liveGroup.groupRecipient, liveGroup.isActive) { r, a -> r to a }
|
val recipientAndIsActive = LiveDataUtil.combineLatest(liveGroup.groupRecipient, liveGroup.isActive) { r, a -> r to a }
|
||||||
store.update(recipientAndIsActive) { (recipient, isActive), state ->
|
store.update(recipientAndIsActive) { (recipient, isActive), state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
|
@ -26,6 +27,7 @@ object AvatarPreference {
|
||||||
|
|
||||||
class Model(
|
class Model(
|
||||||
val recipient: Recipient,
|
val recipient: Recipient,
|
||||||
|
val storyViewState: StoryViewState,
|
||||||
val onAvatarClick: (View) -> Unit,
|
val onAvatarClick: (View) -> Unit,
|
||||||
val onBadgeClick: (Badge) -> Unit
|
val onBadgeClick: (Badge) -> Unit
|
||||||
) : PreferenceModel<Model>() {
|
) : PreferenceModel<Model>() {
|
||||||
|
@ -63,6 +65,7 @@ object AvatarPreference {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avatar.setStoryRingFromState(model.storyViewState)
|
||||||
avatar.displayChatAvatar(model.recipient)
|
avatar.displayChatAvatar(model.recipient)
|
||||||
avatar.disableQuickContact()
|
avatar.disableQuickContact()
|
||||||
avatar.setOnClickListener { model.onAvatarClick(avatar) }
|
avatar.setOnClickListener { model.onAvatarClick(avatar) }
|
||||||
|
|
|
@ -553,6 +553,8 @@ public class ConversationParentFragment extends Fragment
|
||||||
initializeInsightObserver();
|
initializeInsightObserver();
|
||||||
initializeActionBar();
|
initializeActionBar();
|
||||||
|
|
||||||
|
viewModel.getStoryViewState(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), titleView::setStoryRingFromState);
|
||||||
|
|
||||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||||
@Override
|
@Override
|
||||||
public void handleOnBackPressed() {
|
public void handleOnBackPressed() {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.avatar.view.AvatarView;
|
import org.thoughtcrime.securesms.avatar.view.AvatarView;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
@ -124,6 +125,10 @@ public class ConversationTitleView extends RelativeLayout {
|
||||||
updateVerifiedSubtitleVisibility();
|
updateVerifiedSubtitleVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStoryRingFromState(@NonNull StoryViewState storyViewState) {
|
||||||
|
avatar.setStoryRingFromState(storyViewState);
|
||||||
|
}
|
||||||
|
|
||||||
public void setVerified(boolean verified) {
|
public void setVerified(boolean verified) {
|
||||||
this.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
this.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.Application;
|
||||||
import androidx.annotation.MainThread;
|
import androidx.annotation.MainThread;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.LiveDataReactiveStreams;
|
import androidx.lifecycle.LiveDataReactiveStreams;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
@ -18,6 +19,7 @@ import com.annimon.stream.Stream;
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
import org.signal.core.util.MapUtil;
|
import org.signal.core.util.MapUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.paging.PagedData;
|
import org.signal.paging.PagedData;
|
||||||
|
@ -30,6 +32,7 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette;
|
||||||
import org.thoughtcrime.securesms.conversation.colors.NameColor;
|
import org.thoughtcrime.securesms.conversation.colors.NameColor;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||||
|
@ -61,6 +64,7 @@ import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.BackpressureStrategy;
|
import io.reactivex.rxjava3.core.BackpressureStrategy;
|
||||||
|
import io.reactivex.rxjava3.core.Flowable;
|
||||||
import io.reactivex.rxjava3.core.Observable;
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
|
||||||
public class ConversationViewModel extends ViewModel {
|
public class ConversationViewModel extends ViewModel {
|
||||||
|
@ -198,6 +202,14 @@ public class ConversationViewModel extends ViewModel {
|
||||||
threadAnimationStateStore.getStateLiveData().observeForever(threadAnimationStateStoreDriver);
|
threadAnimationStateStore.getStateLiveData().observeForever(threadAnimationStateStoreDriver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<StoryViewState> getStoryViewState(@NonNull LifecycleOwner lifecycle) {
|
||||||
|
Publisher<RecipientId> recipientIdPublisher = LiveDataReactiveStreams.toPublisher(lifecycle, recipientId);
|
||||||
|
Flowable<StoryViewState> storyViewState = Flowable.fromPublisher(recipientIdPublisher)
|
||||||
|
.flatMap(id -> StoryViewState.getForRecipientId(id).toFlowable(BackpressureStrategy.LATEST));
|
||||||
|
|
||||||
|
return LiveDataReactiveStreams.fromPublisher(storyViewState);
|
||||||
|
}
|
||||||
|
|
||||||
void onMessagesCommitted(@NonNull List<ConversationMessage> conversationMessages) {
|
void onMessagesCommitted(@NonNull List<ConversationMessage> conversationMessages) {
|
||||||
if (Util.hasItems(conversationMessages)) {
|
if (Util.hasItems(conversationMessages)) {
|
||||||
threadAnimationStateStore.update(state -> {
|
threadAnimationStateStore.update(state -> {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
import org.thoughtcrime.securesms.insights.InsightsConstants;
|
import org.thoughtcrime.securesms.insights.InsightsConstants;
|
||||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||||
|
@ -193,6 +194,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
||||||
public abstract long getUnreadStoryCount();
|
public abstract long getUnreadStoryCount();
|
||||||
public abstract @Nullable Long getOldestStorySendTimestamp();
|
public abstract @Nullable Long getOldestStorySendTimestamp();
|
||||||
public abstract int deleteStoriesOlderThan(long timestamp);
|
public abstract int deleteStoriesOlderThan(long timestamp);
|
||||||
|
public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId);
|
||||||
|
|
||||||
final @NonNull String getOutgoingTypeClause() {
|
final @NonNull String getOutgoingTypeClause() {
|
||||||
List<String> segments = new ArrayList<>(Types.OUTGOING_MESSAGE_TYPES.length);
|
List<String> segments = new ArrayList<>(Types.OUTGOING_MESSAGE_TYPES.length);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.Quote;
|
import org.thoughtcrime.securesms.database.model.Quote;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||||
|
@ -74,6 +76,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||||
|
@ -591,6 +594,46 @@ public class MmsDatabase extends MessageDatabase {
|
||||||
return new Reader(cursor);
|
return new Reader(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) {
|
||||||
|
if (!FeatureFlags.stories() || SignalStore.storyValues().isFeatureDisabled()) {
|
||||||
|
return StoryViewState.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipientId);
|
||||||
|
|
||||||
|
return getStoryViewState(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull StoryViewState getStoryViewState(long threadId) {
|
||||||
|
final String hasStoryQuery = "SELECT EXISTS(SELECT 1 FROM " + TABLE_NAME + " WHERE " + IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE + " LIMIT 1)";
|
||||||
|
final String[] hasStoryArgs = SqlUtil.buildArgs(1, 0, threadId);
|
||||||
|
final boolean hasStories;
|
||||||
|
|
||||||
|
try (Cursor cursor = getReadableDatabase().rawQuery(hasStoryQuery, hasStoryArgs)) {
|
||||||
|
hasStories = cursor != null && cursor.moveToFirst() && !cursor.isNull(0) && cursor.getInt(0) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStories) {
|
||||||
|
return StoryViewState.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String hasUnviewedStoriesQuery = "SELECT EXISTS(SELECT 1 FROM " + TABLE_NAME + " WHERE " + IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE + " AND " + VIEWED_RECEIPT_COUNT + " = ? " + "AND NOT (" + getOutgoingTypeClause() + ") LIMIT 1)";
|
||||||
|
final String[] hasUnviewedStoriesArgs = SqlUtil.buildArgs(1, 0, threadId, 0);
|
||||||
|
final boolean hasUnviewedStories;
|
||||||
|
|
||||||
|
try (Cursor cursor = getReadableDatabase().rawQuery(hasUnviewedStoriesQuery, hasUnviewedStoriesArgs)) {
|
||||||
|
hasUnviewedStories = cursor != null && cursor.moveToFirst() && !cursor.isNull(0) && cursor.getInt(0) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUnviewedStories) {
|
||||||
|
return StoryViewState.UNVIEWED;
|
||||||
|
} else {
|
||||||
|
return StoryViewState.VIEWED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull MessageId getStoryId(@NonNull RecipientId authorId, long sentTimestamp) throws NoSuchMessageException {
|
public @NonNull MessageId getStoryId(@NonNull RecipientId authorId, long sentTimestamp) throws NoSuchMessageException {
|
||||||
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
SQLiteDatabase database = databaseHelper.getSignalReadableDatabase();
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
@ -1431,6 +1432,11 @@ public class SmsDatabase extends MessageDatabase {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int deleteStoriesOlderThan(long timestamp) {
|
public int deleteStoriesOlderThan(long timestamp) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.thoughtcrime.securesms.database.model
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
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.RecipientId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denotes whether a given recipient has stories, and whether those stories are viewed or unviewed.
|
||||||
|
*/
|
||||||
|
enum class StoryViewState {
|
||||||
|
NONE,
|
||||||
|
UNVIEWED,
|
||||||
|
VIEWED;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun getForRecipientId(recipientId: RecipientId): Observable<StoryViewState> {
|
||||||
|
return Observable.create<StoryViewState> { emitter ->
|
||||||
|
fun refresh() {
|
||||||
|
emitter.onNext(SignalDatabase.mms.getStoryViewState(recipientId))
|
||||||
|
}
|
||||||
|
|
||||||
|
val storyObserver = DatabaseObserver.Observer {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getDatabaseObserver().registerStoryObserver(recipientId, storyObserver)
|
||||||
|
emitter.setCancellable {
|
||||||
|
ApplicationDependencies.getDatabaseObserver().unregisterObserver(storyObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}.observeOn(Schedulers.io())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,8 @@ package org.thoughtcrime.securesms.recipients.ui.bottomsheet;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -20,10 +18,9 @@ import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.widget.TextViewCompat;
|
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
|
||||||
|
@ -32,7 +29,6 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.avatar.view.AvatarView;
|
import org.thoughtcrime.securesms.avatar.view.AvatarView;
|
||||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||||
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon;
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon;
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference;
|
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
|
||||||
|
@ -50,7 +46,6 @@ import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -150,7 +145,11 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
|
||||||
|
|
||||||
RecipientDialogViewModel.Factory factory = new RecipientDialogViewModel.Factory(requireContext().getApplicationContext(), recipientId, groupId);
|
RecipientDialogViewModel.Factory factory = new RecipientDialogViewModel.Factory(requireContext().getApplicationContext(), recipientId, groupId);
|
||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, factory).get(RecipientDialogViewModel.class);
|
viewModel = new ViewModelProvider(this, factory).get(RecipientDialogViewModel.class);
|
||||||
|
|
||||||
|
viewModel.getStoryViewState().observe(getViewLifecycleOwner(), state -> {
|
||||||
|
avatar.setStoryRingFromState(state);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getRecipient().observe(getViewLifecycleOwner(), recipient -> {
|
viewModel.getRecipient().observe(getViewLifecycleOwner(), recipient -> {
|
||||||
interactionsContainer.setVisibility(recipient.isSelf() ? View.GONE : View.VISIBLE);
|
interactionsContainer.setVisibility(recipient.isSelf() ? View.GONE : View.VISIBLE);
|
||||||
|
|
|
@ -18,10 +18,10 @@ import androidx.lifecycle.ViewModelProvider;
|
||||||
import org.signal.core.util.ThreadUtil;
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
|
|
||||||
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
|
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||||
|
@ -32,9 +32,13 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||||
|
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
final class RecipientDialogViewModel extends ViewModel {
|
final class RecipientDialogViewModel extends ViewModel {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@ -44,6 +48,8 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||||
private final LiveData<AdminActionStatus> adminActionStatus;
|
private final LiveData<AdminActionStatus> adminActionStatus;
|
||||||
private final LiveData<Boolean> canAddToAGroup;
|
private final LiveData<Boolean> canAddToAGroup;
|
||||||
private final MutableLiveData<Boolean> adminActionBusy;
|
private final MutableLiveData<Boolean> adminActionBusy;
|
||||||
|
private final MutableLiveData<StoryViewState> storyViewState;
|
||||||
|
private final CompositeDisposable disposables;
|
||||||
|
|
||||||
private RecipientDialogViewModel(@NonNull Context context,
|
private RecipientDialogViewModel(@NonNull Context context,
|
||||||
@NonNull RecipientDialogRepository recipientDialogRepository)
|
@NonNull RecipientDialogRepository recipientDialogRepository)
|
||||||
|
@ -52,6 +58,8 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||||
this.recipientDialogRepository = recipientDialogRepository;
|
this.recipientDialogRepository = recipientDialogRepository;
|
||||||
this.identity = new MutableLiveData<>();
|
this.identity = new MutableLiveData<>();
|
||||||
this.adminActionBusy = new MutableLiveData<>(false);
|
this.adminActionBusy = new MutableLiveData<>(false);
|
||||||
|
this.storyViewState = new MutableLiveData<>();
|
||||||
|
this.disposables = new CompositeDisposable();
|
||||||
|
|
||||||
boolean recipientIsSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());
|
boolean recipientIsSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());
|
||||||
|
|
||||||
|
@ -87,6 +95,20 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||||
(r, count) -> count > 0 && r.isRegistered() && !r.isGroup() && !r.isSelf() && !r.isBlocked());
|
(r, count) -> count > 0 && r.isRegistered() && !r.isGroup() && !r.isSelf() && !r.isBlocked());
|
||||||
|
|
||||||
recipientDialogRepository.getActiveGroupCount(localGroupCount::postValue);
|
recipientDialogRepository.getActiveGroupCount(localGroupCount::postValue);
|
||||||
|
|
||||||
|
Disposable storyViewStateDisposable = StoryViewState.getForRecipientId(recipientDialogRepository.getRecipientId())
|
||||||
|
.subscribe(storyViewState::postValue);
|
||||||
|
|
||||||
|
disposables.add(storyViewStateDisposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
disposables.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<StoryViewState> getStoryViewState() {
|
||||||
|
return storyViewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<Recipient> getRecipient() {
|
LiveData<Recipient> getRecipient() {
|
||||||
|
|
|
@ -79,7 +79,7 @@ object StoriesLandingItem {
|
||||||
|
|
||||||
val record = model.data.primaryStory.messageRecord as MediaMmsMessageRecord
|
val record = model.data.primaryStory.messageRecord as MediaMmsMessageRecord
|
||||||
|
|
||||||
avatarView.showStoryRing(model.data.hasUnreadStory)
|
avatarView.setStoryRingFromState(model.data.storyViewState)
|
||||||
storyPreview.setImageResource(GlideApp.with(storyPreview), record.slideDeck.thumbnailSlide!!, false, true)
|
storyPreview.setImageResource(GlideApp.with(storyPreview), record.slideDeck.thumbnailSlide!!, false, true)
|
||||||
|
|
||||||
if (model.data.secondaryStory != null) {
|
if (model.data.secondaryStory != null) {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package org.thoughtcrime.securesms.stories.landing
|
package org.thoughtcrime.securesms.stories.landing
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data required by each row of the Stories Landing Page for proper rendering.
|
* Data required by each row of the Stories Landing Page for proper rendering.
|
||||||
*/
|
*/
|
||||||
data class StoriesLandingItemData(
|
data class StoriesLandingItemData(
|
||||||
val hasUnreadStory: Boolean,
|
val storyViewState: StoryViewState,
|
||||||
val hasReplies: Boolean,
|
val hasReplies: Boolean,
|
||||||
val hasRepliesFromSelf: Boolean,
|
val hasRepliesFromSelf: Boolean,
|
||||||
val isHidden: Boolean,
|
val isHidden: Boolean,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver
|
||||||
|
@ -66,7 +67,7 @@ class StoriesLandingRepository(context: Context) {
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
val itemData = StoriesLandingItemData(
|
val itemData = StoriesLandingItemData(
|
||||||
storyRecipient = sender,
|
storyRecipient = sender,
|
||||||
hasUnreadStory = messageRecords.any { it.viewedReceiptCount == 0 && !it.isOutgoing },
|
storyViewState = getStoryViewState(messageRecords),
|
||||||
hasReplies = messageRecords.any { SignalDatabase.mms.getNumberOfStoryReplies(it.id) > 0 },
|
hasReplies = messageRecords.any { SignalDatabase.mms.getNumberOfStoryReplies(it.id) > 0 },
|
||||||
hasRepliesFromSelf = messageRecords.any { SignalDatabase.mms.hasSelfReplyInStory(it.id) },
|
hasRepliesFromSelf = messageRecords.any { SignalDatabase.mms.hasSelfReplyInStory(it.id) },
|
||||||
isHidden = Recipient.resolved(messageRecords.first().recipient.id).shouldHideStory(),
|
isHidden = Recipient.resolved(messageRecords.first().recipient.id).shouldHideStory(),
|
||||||
|
@ -105,4 +106,17 @@ class StoriesLandingRepository(context: Context) {
|
||||||
SignalDatabase.recipients.setHideStory(recipientId, hideStory)
|
SignalDatabase.recipients.setHideStory(recipientId, hideStory)
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getStoryViewState(messageRecords: List<MessageRecord>): StoryViewState {
|
||||||
|
val incoming = messageRecords.filterNot { it.isOutgoing }
|
||||||
|
if (incoming.isEmpty()) {
|
||||||
|
return StoryViewState.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incoming.any { it.viewedReceiptCount == 0 }) {
|
||||||
|
return StoryViewState.UNVIEWED
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoryViewState.VIEWED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types
|
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryViewState
|
||||||
import org.thoughtcrime.securesms.testing.TestDatabaseUtil
|
import org.thoughtcrime.securesms.testing.TestDatabaseUtil
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@ -71,4 +72,43 @@ class MmsDatabaseTest {
|
||||||
TestMms.insert(db, threadId = 1, sentTimeMillis = 3, type = Types.BASE_SENDING_TYPE or Types.SECURE_MESSAGE_BIT or Types.PUSH_MESSAGE_BIT or Types.GROUP_LEAVE_BIT or Types.GROUP_V2_BIT or Types.GROUP_UPDATE_BIT)
|
TestMms.insert(db, threadId = 1, sentTimeMillis = 3, type = Types.BASE_SENDING_TYPE or Types.SECURE_MESSAGE_BIT or Types.PUSH_MESSAGE_BIT or Types.GROUP_LEAVE_BIT or Types.GROUP_V2_BIT or Types.GROUP_UPDATE_BIT)
|
||||||
assertEquals(-1, mmsDatabase.getLatestGroupQuitTimestamp(1, 4))
|
assertEquals(-1, mmsDatabase.getLatestGroupQuitTimestamp(1, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given no stories in database, when I getStoryViewState, then I expect NONE`() {
|
||||||
|
assertEquals(StoryViewState.NONE, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given stories in database not in thread 1, when I getStoryViewState for thread 1, then I expect NONE`() {
|
||||||
|
TestMms.insert(db, threadId = 2, isStory = true)
|
||||||
|
TestMms.insert(db, threadId = 2, isStory = true)
|
||||||
|
assertEquals(StoryViewState.NONE, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given viewed incoming stories in database, when I getStoryViewState, then I expect VIEWED`() {
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||||
|
assertEquals(StoryViewState.VIEWED, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given unviewed incoming stories in database, when I getStoryViewState, then I expect UNVIEWED`() {
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||||
|
assertEquals(StoryViewState.UNVIEWED, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given mix of viewed and unviewed incoming stories in database, when I getStoryViewState, then I expect UNVIEWED`() {
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = true)
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, viewed = false)
|
||||||
|
assertEquals(StoryViewState.UNVIEWED, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Given only outgoing story in database, when I getStoryViewState, then I expect VIEWED`() {
|
||||||
|
TestMms.insert(db, threadId = 1, isStory = true, type = Types.BASE_OUTBOX_TYPE)
|
||||||
|
assertEquals(StoryViewState.VIEWED, mmsDatabase.getStoryViewState(1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,9 @@ object TestMms {
|
||||||
distributionType: Int = ThreadDatabase.DistributionTypes.DEFAULT,
|
distributionType: Int = ThreadDatabase.DistributionTypes.DEFAULT,
|
||||||
type: Long = MmsSmsColumns.Types.BASE_INBOX_TYPE,
|
type: Long = MmsSmsColumns.Types.BASE_INBOX_TYPE,
|
||||||
unread: Boolean = false,
|
unread: Boolean = false,
|
||||||
threadId: Long = 1
|
viewed: Boolean = false,
|
||||||
|
threadId: Long = 1,
|
||||||
|
isStory: Boolean = false
|
||||||
): Long {
|
): Long {
|
||||||
val message = OutgoingMediaMessage(
|
val message = OutgoingMediaMessage(
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -34,7 +36,7 @@ object TestMms {
|
||||||
expiresIn,
|
expiresIn,
|
||||||
viewOnce,
|
viewOnce,
|
||||||
distributionType,
|
distributionType,
|
||||||
false,
|
isStory,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
@ -50,6 +52,7 @@ object TestMms {
|
||||||
body = body,
|
body = body,
|
||||||
type = type,
|
type = type,
|
||||||
unread = unread,
|
unread = unread,
|
||||||
|
viewed = viewed,
|
||||||
threadId = threadId,
|
threadId = threadId,
|
||||||
receivedTimestampMillis = receivedTimestampMillis
|
receivedTimestampMillis = receivedTimestampMillis
|
||||||
)
|
)
|
||||||
|
@ -61,6 +64,7 @@ object TestMms {
|
||||||
body: String = message.body,
|
body: String = message.body,
|
||||||
type: Long = MmsSmsColumns.Types.BASE_INBOX_TYPE,
|
type: Long = MmsSmsColumns.Types.BASE_INBOX_TYPE,
|
||||||
unread: Boolean = false,
|
unread: Boolean = false,
|
||||||
|
viewed: Boolean = false,
|
||||||
threadId: Long = 1,
|
threadId: Long = 1,
|
||||||
receivedTimestampMillis: Long = System.currentTimeMillis(),
|
receivedTimestampMillis: Long = System.currentTimeMillis(),
|
||||||
): Long {
|
): Long {
|
||||||
|
@ -78,6 +82,8 @@ object TestMms {
|
||||||
put(MmsSmsColumns.RECIPIENT_ID, message.recipient.id.serialize())
|
put(MmsSmsColumns.RECIPIENT_ID, message.recipient.id.serialize())
|
||||||
put(MmsSmsColumns.DELIVERY_RECEIPT_COUNT, 0)
|
put(MmsSmsColumns.DELIVERY_RECEIPT_COUNT, 0)
|
||||||
put(MmsSmsColumns.RECEIPT_TIMESTAMP, 0)
|
put(MmsSmsColumns.RECEIPT_TIMESTAMP, 0)
|
||||||
|
put(MmsSmsColumns.VIEWED_RECEIPT_COUNT, if (viewed) 1 else 0)
|
||||||
|
put(MmsDatabase.IS_STORY, if (message.isStory) 1 else 0)
|
||||||
|
|
||||||
put(MmsSmsColumns.BODY, body)
|
put(MmsSmsColumns.BODY, body)
|
||||||
put(MmsDatabase.PART_COUNT, 0)
|
put(MmsDatabase.PART_COUNT, 0)
|
||||||
|
|
Ładowanie…
Reference in New Issue