Skip to content

Commit

Permalink
Implement several caching improvements for the Story Viewer.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal committed Jul 11, 2022
1 parent 8f85b58 commit 32312da
Show file tree
Hide file tree
Showing 25 changed files with 603 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ public abstract void insertOrUpdateGroupCall(@NonNull RecipientId groupRecipient

public abstract boolean isStory(long messageId);
public abstract @NonNull Reader getOutgoingStoriesTo(@NonNull RecipientId recipientId);
public abstract @NonNull Reader getAllOutgoingStories(boolean reverse);
public abstract @NonNull Reader getAllOutgoingStories(boolean reverse, int limit);
public abstract @NonNull Reader getAllOutgoingStoriesAt(long sentTimestamp);
public abstract @NonNull List<StoryResult> getOrderedStoryRecipientsAndIds();
public abstract @NonNull Reader getAllStoriesFor(@NonNull RecipientId recipientId);
public abstract @NonNull Reader getAllStoriesFor(@NonNull RecipientId recipientId, int limit);
public abstract @NonNull MessageId getStoryId(@NonNull RecipientId authorId, long sentTimestamp) throws NoSuchMessageException;
public abstract int getNumberOfStoryReplies(long parentStoryId);
public abstract @NonNull List<RecipientId> getUnreadStoryThreadRecipientIds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,10 @@ public boolean isStory(long messageId) {
}

@Override
public @NonNull MessageDatabase.Reader getAllOutgoingStories(boolean reverse) {
public @NonNull MessageDatabase.Reader getAllOutgoingStories(boolean reverse, int limit) {
String where = IS_STORY_CLAUSE + " AND (" + getOutgoingTypeClause() + ")";

return new Reader(rawQuery(where, null, reverse, -1L));
return new Reader(rawQuery(where, null, reverse, limit));
}

@Override
Expand All @@ -636,11 +636,11 @@ public boolean isStory(long messageId) {
}

@Override
public @NonNull MessageDatabase.Reader getAllStoriesFor(@NonNull RecipientId recipientId) {
public @NonNull MessageDatabase.Reader getAllStoriesFor(@NonNull RecipientId recipientId, int limit) {
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipientId);
String where = IS_STORY_CLAUSE + " AND " + THREAD_ID_WHERE;
String[] whereArgs = SqlUtil.buildArgs(threadId);
Cursor cursor = rawQuery(where, whereArgs, false, -1L);
Cursor cursor = rawQuery(where, whereArgs, false, limit);

return new Reader(cursor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ public boolean isStory(long messageId) {
}

@Override
public @NonNull MessageDatabase.Reader getAllOutgoingStories(boolean reverse) {
public @NonNull MessageDatabase.Reader getAllOutgoingStories(boolean reverse, int limit) {
throw new UnsupportedOperationException();
}

Expand All @@ -1418,7 +1418,7 @@ public boolean isStory(long messageId) {
}

@Override
public @NonNull MessageDatabase.Reader getAllStoriesFor(@NonNull RecipientId recipientId) {
public @NonNull MessageDatabase.Reader getAllStoriesFor(@NonNull RecipientId recipientId, int limit) {
throw new UnsupportedOperationException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class StoryOnboardingDownloadJob private constructor(parameters: Parameters) : B
throw Exception("No release channel recipient.")
}

SignalDatabase.mms.getAllStoriesFor(releaseChannelRecipientId).use { reader ->
SignalDatabase.mms.getAllStoriesFor(releaseChannelRecipientId, -1).use { reader ->
reader.forEach { messageRecord ->
SignalDatabase.mms.deleteMessage(messageRecord.id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ private void handleStoryMessage(@NonNull SignalServiceContent content, @NonNull
}

if (insertResult.isPresent()) {
Stories.enqueueNextStoriesForDownload(threadRecipient.getId(), false);
Stories.enqueueNextStoriesForDownload(threadRecipient.getId(), false, FeatureFlags.storiesAutoDownloadMaximum());
ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary();
}
}
Expand Down
16 changes: 10 additions & 6 deletions app/src/main/java/org/thoughtcrime/securesms/stories/Stories.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import kotlin.math.min

object Stories {

private val TAG = Log.tag(Stories::class.java)

const val MAX_BODY_SIZE = 700

@JvmField
Expand Down Expand Up @@ -97,16 +99,18 @@ object Stories {

@JvmStatic
@WorkerThread
fun enqueueNextStoriesForDownload(recipientId: RecipientId, ignoreAutoDownloadConstraints: Boolean = false) {
fun enqueueNextStoriesForDownload(recipientId: RecipientId, force: Boolean = false, limit: Int) {
val recipient = Recipient.resolved(recipientId)
if (!recipient.isSelf && (recipient.shouldHideStory() || !recipient.hasViewedStory())) {
if (!force && !recipient.isSelf && (recipient.shouldHideStory() || !recipient.hasViewedStory())) {
return
}

val unreadStoriesReader = SignalDatabase.mms.getUnreadStories(recipientId, FeatureFlags.storiesAutoDownloadMaximum())
while (unreadStoriesReader.next != null) {
val record = unreadStoriesReader.current as MmsMessageRecord
enqueueAttachmentsFromStoryForDownloadSync(record, ignoreAutoDownloadConstraints)
Log.d(TAG, "Enqueuing downloads for up to $limit stories for $recipientId (force: $force)")
SignalDatabase.mms.getUnreadStories(recipientId, limit).use {
while (it.next != null) {
val record = it.current as MmsMessageRecord
enqueueAttachmentsFromStoryForDownloadSync(record, force)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class StorySlateView @JvmOverloads constructor(
private val background: ImageView = findViewById(R.id.background)
private val loadingSpinner: View = findViewById(R.id.loading_spinner)
private val errorCircle: View = findViewById(R.id.error_circle)
private val errorBackground: View = findViewById(R.id.stories_error_background)
private val unavailableText: View = findViewById(R.id.unavailable)
private val errorText: TextView = findViewById(R.id.error_text)

Expand Down Expand Up @@ -87,6 +88,7 @@ class StorySlateView @JvmOverloads constructor(
background.visible = true
loadingSpinner.visible = true
errorCircle.visible = false
errorBackground.visible = false
unavailableText.visible = false
errorText.visible = false
}
Expand All @@ -97,6 +99,7 @@ class StorySlateView @JvmOverloads constructor(
background.visible = true
loadingSpinner.visible = false
errorCircle.visible = true
errorBackground.visible = true
unavailableText.visible = false
errorText.visible = true

Expand All @@ -113,6 +116,7 @@ class StorySlateView @JvmOverloads constructor(
background.visible = true
loadingSpinner.visible = false
errorCircle.visible = false
errorBackground.visible = false
unavailableText.visible = true
errorText.visible = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class MyStoriesRepository(context: Context) {
return Observable.create { emitter ->
fun refresh() {
val storiesMap = mutableMapOf<Recipient, List<MessageRecord>>()
SignalDatabase.mms.getAllOutgoingStories(true).use {
SignalDatabase.mms.getAllOutgoingStories(true, -1).use {
while (it.next != null) {
val messageRecord = it.current
val currentList = storiesMap[messageRecord.recipient] ?: emptyList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import android.view.KeyEvent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate
import androidx.media.AudioManagerCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.MemoryCategory
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
Expand All @@ -31,6 +33,7 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll

override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
StoryMutePolicy.initialize()
Glide.get(this).setMemoryCategory(MemoryCategory.HIGH)

supportPostponeEnterTransition()

Expand All @@ -44,6 +47,11 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
}
}

override fun onDestroy() {
super.onDestroy()
Glide.get(this).setMemoryCategory(MemoryCategory.NORMAL)
}

override fun onResume() {
super.onResume()
if (StoryMutePolicy.isContentMuted) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
import org.thoughtcrime.securesms.stories.viewer.reply.StoriesSharedElementCrossFaderView
import org.thoughtcrime.securesms.util.LifecycleDisposable

/**
* Fragment which manages a vertical pager fragment of stories.
*/
class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryViewerPageFragment.Callback {
class StoryViewerFragment :
Fragment(R.layout.stories_viewer_fragment),
StoryViewerPageFragment.Callback,
StoriesSharedElementCrossFaderView.Callback {

private val onPageChanged = OnPageChanged()

Expand All @@ -31,9 +35,16 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie

private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelable(ARGS)!! }

private lateinit var storyCrossfader: StoriesSharedElementCrossFaderView

private var pagerOnPageSelectedLock: Boolean = false

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
storyCrossfader = view.findViewById(R.id.story_content_crossfader)
storyPager = view.findViewById(R.id.story_item_pager)

storyCrossfader.callback = this

val adapter = StoryViewerPagerAdapter(
this,
storyViewerArgs.storyId,
Expand All @@ -45,24 +56,38 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
storyPager.adapter = adapter
storyPager.overScrollMode = ViewPager2.OVER_SCROLL_NEVER

viewModel.isChildScrolling.observe(viewLifecycleOwner) {
storyPager.isUserInputEnabled = !it
lifecycleDisposable += viewModel.allowParentScrolling.observeOn(AndroidSchedulers.mainThread()).subscribe {
storyPager.isUserInputEnabled = it
}

storyPager.offscreenPageLimit = 1

lifecycleDisposable.bindTo(viewLifecycleOwner)
lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
adapter.setPages(state.pages)
if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
pagerOnPageSelectedLock = true
storyPager.setCurrentItem(state.page, state.previousPage > -1)
pagerOnPageSelectedLock = false

if (state.page >= state.pages.size) {
requireActivity().onBackPressed()
}
}

if (state.loadState.isCrossfaderReady) {
when (state.crossfadeSource) {
is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(state.crossfadeSource.storyTextPostModel)
is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(state.crossfadeSource.imageUri, state.crossfadeSource.imageBlur)
}

if (state.crossfadeTarget is StoryViewerState.CrossfadeTarget.Record) {
storyCrossfader.setTargetView(state.crossfadeTarget.messageRecord)
requireActivity().supportStartPostponedEnterTransition()
}

if (state.loadState.isReady()) {
storyCrossfader.alpha = 0f
}
}
}

Expand Down Expand Up @@ -90,9 +115,22 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
viewModel.onRecipientHidden()
}

override fun onReadyToAnimate() {
}

override fun onAnimationStarted() {
viewModel.setCrossfaderIsReady(false)
}

override fun onAnimationFinished() {
viewModel.setCrossfaderIsReady(true)
}

inner class OnPageChanged : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
viewModel.setSelectedPage(position)
if (!pagerOnPageSelectedLock) {
viewModel.setSelectedPage(position)
}
}

override fun onPageScrollStateChanged(state: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.stories.viewer

import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.database.MessageDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
Expand All @@ -13,6 +15,27 @@ import org.thoughtcrime.securesms.recipients.RecipientId
* Open for testing
*/
open class StoryViewerRepository {
fun getFirstStory(recipientId: RecipientId, unviewedOnly: Boolean, storyId: Long): Single<MmsMessageRecord> {
return if (storyId > 0) {
Single.fromCallable {
SignalDatabase.mms.getMessageRecord(storyId) as MmsMessageRecord
}
} else {
Single.fromCallable {
val recipient = Recipient.resolved(recipientId)
val reader: MessageDatabase.Reader = if (recipient.isMyStory || recipient.isSelf) {
SignalDatabase.mms.getAllOutgoingStories(false, 1)
} else if (unviewedOnly) {
SignalDatabase.mms.getUnreadStories(recipientId, 1)
} else {
SignalDatabase.mms.getAllStoriesFor(recipientId, 1)
}

reader.use { it.next } as MmsMessageRecord
}
}
}

fun getStories(hiddenStories: Boolean, unviewedOnly: Boolean): Single<List<RecipientId>> {
return Single.create<List<RecipientId>> { emitter ->
val myStoriesId = SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.stories.viewer

import android.net.Uri
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryTextPostModel

Expand All @@ -10,6 +11,7 @@ data class StoryViewerState(
val previousPage: Int = -1,
val page: Int = -1,
val crossfadeSource: CrossfadeSource,
val crossfadeTarget: CrossfadeTarget? = null,
val loadState: LoadState = LoadState()
) {
sealed class CrossfadeSource {
Expand All @@ -18,6 +20,11 @@ data class StoryViewerState(
class TextModel(val storyTextPostModel: StoryTextPostModel) : CrossfadeSource()
}

sealed class CrossfadeTarget {
object None : CrossfadeTarget()
data class Record(val messageRecord: MmsMessageRecord) : CrossfadeTarget()
}

data class LoadState(
val isContentReady: Boolean = false,
val isCrossfaderReady: Boolean = false
Expand Down

0 comments on commit 32312da

Please sign in to comment.