From 9805a985b2a509e6a59a6e10a482492782ae8c7e Mon Sep 17 00:00:00 2001
From: Ivan Kupalov <charlag@tuta.io>
Date: Sun, 4 Aug 2019 20:22:57 +0200
Subject: [PATCH] Use cached preview as thumbnail in ViewImageFragment, fix
 #1267 (#1344)

* Use cached preview as thumbnail in ViewImageFragment, fix #1267

* Use cached preview as thumbnail in ViewImageFragment, fix #1267
---
 .../keylesspalace/tusky/ViewMediaActivity.kt  |  78 ++++++-----
 .../tusky/fragment/ViewImageFragment.kt       | 127 +++++++++++-------
 .../tusky/fragment/ViewMediaFragment.kt       |  31 +++--
 .../tusky/fragment/ViewVideoFragment.kt       |  10 +-
 .../tusky/pager/AvatarImagePagerAdapter.kt    |   8 +-
 .../tusky/pager/ImagePagerAdapter.kt          |  20 ++-
 .../tusky/repository/TimelineRepository.kt    |   1 -
 7 files changed, 164 insertions(+), 111 deletions(-)

diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
index 93b69e0a..8806ae43 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt
@@ -29,21 +29,22 @@ import android.graphics.Color
 import android.net.Uri
 import android.os.Bundle
 import android.os.Environment
-import androidx.core.content.FileProvider
-import androidx.viewpager.widget.ViewPager
+import android.transition.Transition
 import android.util.Log
 import android.view.Menu
 import android.view.MenuItem
 import android.view.View
 import android.webkit.MimeTypeMap
 import android.widget.Toast
+import androidx.core.content.FileProvider
 import androidx.lifecycle.Lifecycle
+import androidx.viewpager.widget.PagerAdapter
+import androidx.viewpager.widget.ViewPager
 import com.bumptech.glide.Glide
 import com.bumptech.glide.request.FutureTarget
 import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.fragment.ViewImageFragment
-
 import com.keylesspalace.tusky.pager.AvatarImagePagerAdapter
 import com.keylesspalace.tusky.pager.ImagePagerAdapter
 import com.keylesspalace.tusky.util.getTemporaryMediaFilename
@@ -53,14 +54,14 @@ import com.uber.autodispose.autoDisposable
 import io.reactivex.Single
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.schedulers.Schedulers
-
 import kotlinx.android.synthetic.main.activity_view_media.*
-
 import java.io.File
 import java.io.FileNotFoundException
 import java.io.FileOutputStream
 import java.io.IOException
-import java.util.ArrayList
+import java.util.*
+
+typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
 
 class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener {
     companion object {
@@ -84,25 +85,18 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
         }
     }
 
+    var isToolbarVisible = true
+        private set
+
     private var attachments: ArrayList<AttachmentViewData>? = null
-
-    private var toolbarVisible = true
-    private val toolbarVisibilityListeners = ArrayList<ToolbarVisibilityListener>()
-
-    interface ToolbarVisibilityListener {
-        fun onToolbarVisiblityChanged(isVisible: Boolean)
-    }
+    private val toolbarVisibilityListeners = mutableListOf<ToolbarVisibilityListener>()
 
     fun addToolbarVisibilityListener(listener: ToolbarVisibilityListener): Function0<Boolean> {
         this.toolbarVisibilityListeners.add(listener)
-        listener.onToolbarVisiblityChanged(toolbarVisible)
+        listener(isToolbarVisible)
         return { toolbarVisibilityListeners.remove(listener) }
     }
 
-    fun isToolbarVisible(): Boolean {
-        return toolbarVisible
-    }
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_view_media)
@@ -113,7 +107,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
         attachments = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENTS)
         val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
 
-        val adapter = if (attachments != null) {
+        // Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
+        // but it cannot be expressed and if I don't specify type explicitly compilation fails
+        // (probably a bug in compiler)
+        val adapter: PagerAdapter = if (attachments != null) {
             val realAttachs = attachments!!.map(AttachmentViewData::attachment)
             // Setup the view pager.
             ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition)
@@ -154,6 +151,12 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
 
         window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
         window.statusBarColor = Color.BLACK
+        window.sharedElementEnterTransition.addListener(object : NoopTransitionListener {
+            override fun onTransitionEnd(transition: Transition) {
+                (adapter as SharedElementTransitionListener).onTransitionEnd()
+                window.sharedElementEnterTransition.removeListener(this)
+            }
+        })
     }
 
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -178,20 +181,12 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
     }
 
     override fun onPhotoTap() {
-        toolbarVisible = !toolbarVisible
+        isToolbarVisible = !isToolbarVisible
         for (listener in toolbarVisibilityListeners) {
-            listener.onToolbarVisiblityChanged(toolbarVisible)
-        }
-        val visibility = if (toolbarVisible) {
-            View.VISIBLE
-        } else {
-            View.INVISIBLE
-        }
-        val alpha = if (toolbarVisible) {
-            1.0f
-        } else {
-            0.0f
+            listener(isToolbarVisible)
         }
+        val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE
+        val alpha = if (isToolbarVisible) 1.0f else 0.0f
 
         toolbar.animate().alpha(alpha)
                 .setListener(object : AnimatorListenerAdapter() {
@@ -327,3 +322,24 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
         shareFile(file, mimeType)
     }
 }
+
+interface SharedElementTransitionListener {
+    fun onTransitionEnd()
+}
+
+interface NoopTransitionListener : Transition.TransitionListener {
+    override fun onTransitionEnd(transition: Transition) {
+    }
+
+    override fun onTransitionResume(transition: Transition) {
+    }
+
+    override fun onTransitionPause(transition: Transition) {
+    }
+
+    override fun onTransitionCancel(transition: Transition) {
+    }
+
+    override fun onTransitionStart(transition: Transition) {
+    }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
index dce1ff91..1f6596c3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt
@@ -30,14 +30,15 @@ import com.bumptech.glide.load.DataSource
 import com.bumptech.glide.load.engine.GlideException
 import com.bumptech.glide.request.RequestListener
 import com.bumptech.glide.request.target.Target
-
 import com.github.chrisbanes.photoview.PhotoViewAttacher
 import com.keylesspalace.tusky.R
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.util.hide
 import com.keylesspalace.tusky.util.visible
+import io.reactivex.subjects.BehaviorSubject
 import kotlinx.android.synthetic.main.activity_view_media.*
 import kotlinx.android.synthetic.main.fragment_view_image.*
+import kotlin.math.abs
 
 class ViewImageFragment : ViewMediaFragment() {
     interface PhotoActionsListener {
@@ -49,35 +50,35 @@ class ViewImageFragment : ViewMediaFragment() {
     private lateinit var attacher: PhotoViewAttacher
     private lateinit var photoActionsListener: PhotoActionsListener
     private lateinit var toolbar: View
-    override lateinit var descriptionView: TextView
+    private var transition = BehaviorSubject.create<Unit>()
 
+    override lateinit var descriptionView: TextView
     override fun onAttach(context: Context) {
         super.onAttach(context)
         photoActionsListener = context as PhotoActionsListener
     }
 
-    override fun setupMediaView(url: String) {
+    override fun setupMediaView(url: String, previewUrl: String?) {
         descriptionView = mediaDescription
         photoView.transitionName = url
-        attacher = PhotoViewAttacher(photoView)
+        attacher = PhotoViewAttacher(photoView).apply {
+            // Clicking outside the photo closes the viewer.
+            setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
+            setOnClickListener { onMediaTap() }
 
-        // Clicking outside the photo closes the viewer.
-        attacher.setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
-
-        attacher.setOnClickListener { onMediaTap() }
-
-        /* A vertical swipe motion also closes the viewer. This is especially useful when the photo
-         * mostly fills the screen so clicking outside is difficult. */
-        attacher.setOnSingleFlingListener { _, _, velocityX, velocityY ->
-            var result = false
-            if (Math.abs(velocityY) > Math.abs(velocityX)) {
-                photoActionsListener.onDismiss()
-                result = true
+            /* A vertical swipe motion also closes the viewer. This is especially useful when the photo
+             * mostly fills the screen so clicking outside is difficult. */
+            setOnSingleFlingListener { _, _, velocityX, velocityY ->
+                var result = false
+                if (abs(velocityY) > abs(velocityX)) {
+                    photoActionsListener.onDismiss()
+                    result = true
+                }
+                result
             }
-            result
         }
 
-        loadImageFromNetwork(url, photoView)
+        loadImageFromNetwork(url, previewUrl, photoView)
     }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -103,7 +104,7 @@ class ViewImageFragment : ViewMediaFragment() {
             }
         }
 
-        finalizeViewSetup(url, description)
+        finalizeViewSetup(url, attachment?.previewUrl, description)
     }
 
     private fun onMediaTap() {
@@ -131,49 +132,71 @@ class ViewImageFragment : ViewMediaFragment() {
         super.onDestroyView()
     }
 
-    private fun loadImageFromNetwork(url: String, photoView: ImageView) =
-            //Request image from the any cache
-            Glide.with(this)
-                    .load(url)
-                    .dontAnimate()
-                    .onlyRetrieveFromCache(true)
-                    .error(
-                            //Request image from the network on fail load image from cache
-                            Glide.with(this)
-                                    .load(url)
-                                    .centerInside()
-                                    .addListener(ImageRequestListener(false))
-                    )
-                    .centerInside()
-                    .addListener(ImageRequestListener(true))
-                    .into(photoView)
-
+    private fun loadImageFromNetwork(url: String, previewUrl: String?, photoView: ImageView) {
+        val glide = Glide.with(this)
+        // Request image from the any cache
+        glide
+                .load(url)
+                .dontAnimate()
+                .onlyRetrieveFromCache(true)
+                .let {
+                    if (previewUrl != null)
+                        it.thumbnail(glide
+                                .load(previewUrl)
+                                .dontAnimate()
+                                .onlyRetrieveFromCache(true)
+                                .centerInside()
+                                .addListener(ImageRequestListener(true, isThumnailRequest = true)))
+                    else it
+                }
+                //Request image from the network on fail load image from cache
+                .error(glide.load(url)
+                        .centerInside()
+                        .addListener(ImageRequestListener(false, isThumnailRequest = false))
+                )
+                .centerInside()
+                .addListener(ImageRequestListener(true, isThumnailRequest = false))
+                .into(photoView)
+    }
 
     /**
      * @param isCacheRequest - is this listener for request image from cache or from the network
      */
-    private inner class ImageRequestListener(private val isCacheRequest: Boolean) : RequestListener<Drawable> {
-        override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
-            if (isCacheRequest) //Complete the transition on failed image from cache
-                completeTransition()
-            else
-                progressBar?.hide() //Hide progress bar only on fail request from internet
+    private inner class ImageRequestListener(
+            private val isCacheRequest: Boolean,
+            private val isThumnailRequest: Boolean) : RequestListener<Drawable> {
+
+        override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>,
+                                  isFirstResource: Boolean): Boolean {
+            // If cache for full image failed, complete transition
+            if (isCacheRequest && !isThumnailRequest) photoActionsListener.onBringUp()
+            // Hide progress bar only on fail request from internet
+            if (!isCacheRequest) progressBar?.hide()
             return false
         }
 
-        override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
-            progressBar?.hide() //Always hide the progress bar on success
-            resource?.let {
-                target?.onResourceReady(resource, null)
-                if (isCacheRequest) completeTransition() //Complete transition on cache request only, because transition already completed on Network request
+        override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
+                                     dataSource: DataSource, isFirstResource: Boolean): Boolean {
+            progressBar?.hide() // Always hide the progress bar on success
+                if (isThumnailRequest) {
+                    photoView.post {
+                        target.onResourceReady(resource, null)
+                        photoActionsListener.onBringUp()
+                    }
+                } else {
+                    transition
+                            .take(1)
+                            .subscribe {
+                                target.onResourceReady(resource, null)
+                                photoActionsListener.onBringUp()
+                            }
+                }
                 return true
-            }
-            return false
         }
+
     }
 
-    private fun completeTransition() {
-        attacher.update()
-        photoActionsListener.onBringUp()
+    override fun onTransitionEnd() {
+        this.transition.onNext(Unit)
     }
 }
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt
index 3d88753a..fcc8ddae 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt
@@ -18,25 +18,29 @@ package com.keylesspalace.tusky.fragment
 import android.os.Bundle
 import android.text.TextUtils
 import android.widget.TextView
+import com.keylesspalace.tusky.SharedElementTransitionListener
 
 import com.keylesspalace.tusky.ViewMediaActivity
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.util.visible
 
-abstract class ViewMediaFragment : BaseFragment() {
+abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListener {
     private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
 
-    abstract fun setupMediaView(url: String)
+    abstract fun setupMediaView(url: String, previewUrl: String?)
     abstract fun onToolbarVisibilityChange(visible: Boolean)
-    abstract val descriptionView : TextView
+    abstract val descriptionView: TextView
 
     protected var showingDescription = false
     protected var isDescriptionVisible = false
 
     companion object {
-        @JvmStatic protected val ARG_START_POSTPONED_TRANSITION = "startPostponedTransition"
-        @JvmStatic protected val ARG_ATTACHMENT = "attach"
-        @JvmStatic protected val ARG_AVATAR_URL = "avatarUrl"
+        @JvmStatic
+        protected val ARG_START_POSTPONED_TRANSITION = "startPostponedTransition"
+        @JvmStatic
+        protected val ARG_ATTACHMENT = "attach"
+        @JvmStatic
+        protected val ARG_AVATAR_URL = "avatarUrl"
 
         @JvmStatic
         fun newInstance(attachment: Attachment, shouldStartPostponedTransition: Boolean): ViewMediaFragment {
@@ -66,21 +70,20 @@ abstract class ViewMediaFragment : BaseFragment() {
         }
     }
 
-    protected fun finalizeViewSetup(url: String, description: String?) {
+    protected fun finalizeViewSetup(url: String, previewUrl: String?, description: String?) {
         val mediaActivity = activity as ViewMediaActivity
-        setupMediaView(url)
+        setupMediaView(url, previewUrl)
 
         descriptionView.text = description ?: ""
         showingDescription = !TextUtils.isEmpty(description)
         isDescriptionVisible = showingDescription
 
-        descriptionView.visible(showingDescription && mediaActivity.isToolbarVisible())
+        descriptionView.visible(showingDescription && mediaActivity.isToolbarVisible)
 
-        toolbarVisibiltyDisposable = (activity as ViewMediaActivity).addToolbarVisibilityListener(object: ViewMediaActivity.ToolbarVisibilityListener {
-            override fun onToolbarVisiblityChanged(isVisible: Boolean) {
-                onToolbarVisibilityChange(isVisible)
-            }
-        })
+        toolbarVisibiltyDisposable = (activity as ViewMediaActivity)
+                .addToolbarVisibilityListener { isVisible ->
+                    onToolbarVisibilityChange(isVisible)
+                }
     }
 
     override fun onDestroyView() {
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
index 429ac9e7..fd907c20 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt
@@ -26,7 +26,6 @@ import android.view.View
 import android.view.ViewGroup
 import android.widget.MediaController
 import android.widget.TextView
-
 import com.keylesspalace.tusky.R
 import com.keylesspalace.tusky.ViewMediaActivity
 import com.keylesspalace.tusky.entity.Attachment
@@ -56,7 +55,7 @@ class ViewVideoFragment : ViewMediaFragment() {
         }
 
         if (isVisibleToUser) {
-            if (mediaActivity.isToolbarVisible()) {
+            if (mediaActivity.isToolbarVisible) {
                 handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
             }
             videoPlayer.start()
@@ -68,7 +67,7 @@ class ViewVideoFragment : ViewMediaFragment() {
     }
 
     @SuppressLint("ClickableViewAccessibility")
-    override fun setupMediaView(url: String) {
+    override fun setupMediaView(url: String, previewUrl: String?) {
         descriptionView = mediaDescription
         val videoView = videoPlayer
         videoView.transitionName = url
@@ -114,7 +113,7 @@ class ViewVideoFragment : ViewMediaFragment() {
             throw IllegalArgumentException("attachment has to be set")
         }
         url = attachment.url
-        finalizeViewSetup(url, attachment.description)
+        finalizeViewSetup(url, attachment.previewUrl, attachment.description)
     }
 
     override fun onToolbarVisibilityChange(visible: Boolean) {
@@ -139,4 +138,7 @@ class ViewVideoFragment : ViewMediaFragment() {
             handler.removeCallbacks(hideToolbar)
         }
     }
+
+    override fun onTransitionEnd() {
+    }
 }
diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt
index a5950f8d..1beab552 100644
--- a/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/pager/AvatarImagePagerAdapter.kt
@@ -3,12 +3,10 @@ package com.keylesspalace.tusky.pager
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
 import androidx.fragment.app.FragmentPagerAdapter
-
+import com.keylesspalace.tusky.SharedElementTransitionListener
 import com.keylesspalace.tusky.fragment.ViewMediaFragment
-import java.lang.IllegalStateException
-
-class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avatarUrl: String) : FragmentPagerAdapter(fragmentManager) {
 
+class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avatarUrl: String) : FragmentPagerAdapter(fragmentManager), SharedElementTransitionListener {
     override fun getItem(position: Int): Fragment {
         return if (position == 0) {
             ViewMediaFragment.newAvatarInstance(avatarUrl)
@@ -19,4 +17,6 @@ class AvatarImagePagerAdapter(fragmentManager: FragmentManager, private val avat
 
     override fun getCount() = 1
 
+    override fun onTransitionEnd() {
+    }
 }
diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt
index 794c0dad..55e5b25c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt
@@ -1,20 +1,26 @@
 package com.keylesspalace.tusky.pager
 
+import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
 import androidx.fragment.app.FragmentStatePagerAdapter
-
+import com.keylesspalace.tusky.SharedElementTransitionListener
 import com.keylesspalace.tusky.entity.Attachment
 import com.keylesspalace.tusky.fragment.ViewMediaFragment
-import java.lang.IllegalStateException
-
-import java.util.Locale
+import java.util.*
 
 class ImagePagerAdapter(
         fragmentManager: FragmentManager,
         private val attachments: List<Attachment>,
         private val initialPosition: Int
-) : FragmentStatePagerAdapter(fragmentManager) {
+) : FragmentStatePagerAdapter(fragmentManager), SharedElementTransitionListener {
+
+    private var primaryItem: ViewMediaFragment? = null
+
+    override fun setPrimaryItem(container: ViewGroup, position: Int, item: Any) {
+        super.setPrimaryItem(container, position, item)
+        this.primaryItem = item as ViewMediaFragment
+    }
 
     override fun getItem(position: Int): Fragment {
         return if (position >= 0 && position < attachments.size) {
@@ -31,4 +37,8 @@ class ImagePagerAdapter(
     override fun getPageTitle(position: Int): CharSequence {
         return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments.size)
     }
+
+    override fun onTransitionEnd() {
+        primaryItem?.onTransitionEnd()
+    }
 }
diff --git a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt
index b682c664..d32cf6b2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt
@@ -5,7 +5,6 @@ import com.google.gson.Gson
 import com.google.gson.reflect.TypeToken
 import com.keylesspalace.tusky.db.*
 import com.keylesspalace.tusky.entity.*
-import com.keylesspalace.tusky.entity.Status
 import com.keylesspalace.tusky.network.MastodonApi
 import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
 import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK