2022-08-15 11:00:18 +02:00
|
|
|
/* Copyright 2022 Tusky Contributors
|
|
|
|
*
|
|
|
|
* This file is a part of Tusky.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
|
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
|
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
|
|
* Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
|
|
|
|
package com.keylesspalace.tusky.components.viewthread
|
|
|
|
|
|
|
|
import android.util.Log
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
|
|
import androidx.lifecycle.viewModelScope
|
|
|
|
import at.connyduck.calladapter.networkresult.fold
|
|
|
|
import at.connyduck.calladapter.networkresult.getOrElse
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
import com.google.gson.Gson
|
2022-08-15 11:00:18 +02:00
|
|
|
import com.keylesspalace.tusky.appstore.BlockEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.BookmarkEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.EventHub
|
|
|
|
import com.keylesspalace.tusky.appstore.FavoriteEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.PinEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.ReblogEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.StatusComposedEvent
|
|
|
|
import com.keylesspalace.tusky.appstore.StatusDeletedEvent
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
import com.keylesspalace.tusky.components.timeline.toViewData
|
2022-08-15 11:00:18 +02:00
|
|
|
import com.keylesspalace.tusky.components.timeline.util.ifExpected
|
|
|
|
import com.keylesspalace.tusky.db.AccountManager
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
import com.keylesspalace.tusky.db.AppDatabase
|
2022-08-15 11:00:18 +02:00
|
|
|
import com.keylesspalace.tusky.entity.Filter
|
|
|
|
import com.keylesspalace.tusky.entity.Status
|
|
|
|
import com.keylesspalace.tusky.network.FilterModel
|
|
|
|
import com.keylesspalace.tusky.network.MastodonApi
|
|
|
|
import com.keylesspalace.tusky.usecase.TimelineCases
|
|
|
|
import com.keylesspalace.tusky.util.toViewData
|
|
|
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.channels.BufferOverflow
|
|
|
|
import kotlinx.coroutines.flow.Flow
|
|
|
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
import kotlinx.coroutines.flow.update
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.rx3.asFlow
|
|
|
|
import kotlinx.coroutines.rx3.await
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
class ViewThreadViewModel @Inject constructor(
|
|
|
|
private val api: MastodonApi,
|
|
|
|
private val filterModel: FilterModel,
|
|
|
|
private val timelineCases: TimelineCases,
|
|
|
|
eventHub: EventHub,
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
accountManager: AccountManager,
|
|
|
|
private val db: AppDatabase,
|
|
|
|
private val gson: Gson
|
2022-08-15 11:00:18 +02:00
|
|
|
) : ViewModel() {
|
|
|
|
|
|
|
|
private val _uiState: MutableStateFlow<ThreadUiState> = MutableStateFlow(ThreadUiState.Loading)
|
|
|
|
val uiState: Flow<ThreadUiState>
|
|
|
|
get() = _uiState
|
|
|
|
|
|
|
|
private val _errors = MutableSharedFlow<Throwable>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
|
|
|
val errors: Flow<Throwable>
|
|
|
|
get() = _errors
|
|
|
|
|
2023-01-12 19:41:07 +01:00
|
|
|
var isInitialLoad: Boolean = true
|
|
|
|
|
2022-08-15 11:00:18 +02:00
|
|
|
private val alwaysShowSensitiveMedia: Boolean
|
|
|
|
private val alwaysOpenSpoiler: Boolean
|
|
|
|
|
|
|
|
init {
|
|
|
|
val activeAccount = accountManager.activeAccount
|
|
|
|
alwaysShowSensitiveMedia = activeAccount?.alwaysShowSensitiveMedia ?: false
|
|
|
|
alwaysOpenSpoiler = activeAccount?.alwaysOpenSpoiler ?: false
|
|
|
|
|
|
|
|
viewModelScope.launch {
|
|
|
|
eventHub.events
|
|
|
|
.asFlow()
|
|
|
|
.collect { event ->
|
|
|
|
when (event) {
|
|
|
|
is FavoriteEvent -> handleFavEvent(event)
|
|
|
|
is ReblogEvent -> handleReblogEvent(event)
|
|
|
|
is BookmarkEvent -> handleBookmarkEvent(event)
|
|
|
|
is PinEvent -> handlePinEvent(event)
|
|
|
|
is BlockEvent -> removeAllByAccountId(event.accountId)
|
|
|
|
is StatusComposedEvent -> handleStatusComposedEvent(event)
|
|
|
|
is StatusDeletedEvent -> handleStatusDeletedEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadFilters()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun loadThread(id: String) {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
_uiState.value = ThreadUiState.Loading
|
|
|
|
|
2022-08-15 11:00:18 +02:00
|
|
|
viewModelScope.launch {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
Log.d(TAG, "Finding status with: $id")
|
2022-08-15 11:00:18 +02:00
|
|
|
val contextCall = async { api.statusContext(id) }
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val timelineStatus = db.timelineDao().getStatus(id)
|
|
|
|
|
|
|
|
var detailedStatus = if (timelineStatus != null) {
|
|
|
|
Log.d(TAG, "Loaded status from local timeline")
|
|
|
|
val viewData = timelineStatus.toViewData(
|
|
|
|
gson,
|
|
|
|
isDetailed = true
|
|
|
|
) as StatusViewData.Concrete
|
|
|
|
|
|
|
|
// Return the correct status, depending on which one matched. If you do not do
|
|
|
|
// this the status IDs will be different between the status that's displayed with
|
|
|
|
// ThreadUiState.LoadingThread and ThreadUiState.Success, even though the apparent
|
|
|
|
// status content is the same. Then the status flickers as it is drawn twice.
|
|
|
|
if (viewData.actionableId == id) {
|
|
|
|
viewData.actionable.toViewData(isDetailed = true)
|
|
|
|
} else {
|
|
|
|
viewData
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.d(TAG, "Loaded status from network")
|
|
|
|
val result = api.status(id).getOrElse { exception ->
|
|
|
|
_uiState.value = ThreadUiState.Error(exception)
|
|
|
|
return@launch
|
|
|
|
}
|
|
|
|
result.toViewData(isDetailed = true)
|
|
|
|
}
|
2022-08-15 11:00:18 +02:00
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
_uiState.value = ThreadUiState.LoadingThread(
|
|
|
|
statusViewDatum = detailedStatus,
|
|
|
|
revealButton = detailedStatus.getRevealButtonState()
|
|
|
|
)
|
2022-08-15 11:00:18 +02:00
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
// If the detailedStatus was loaded from the database it might be out-of-date
|
|
|
|
// compared to the remote one. Now the user has a working UI do a background fetch
|
|
|
|
// for the status. Ignore errors, the user still has a functioning UI if the fetch
|
|
|
|
// failed.
|
|
|
|
if (timelineStatus != null) {
|
|
|
|
val viewData = api.status(id).getOrNull()?.toViewData(isDetailed = true)
|
|
|
|
if (viewData != null) { detailedStatus = viewData }
|
2022-08-15 11:00:18 +02:00
|
|
|
}
|
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val contextResult = contextCall.await()
|
2022-08-15 11:00:18 +02:00
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
contextResult.fold({ statusContext ->
|
2022-08-15 11:00:18 +02:00
|
|
|
val ancestors = statusContext.ancestors.map { status -> status.toViewData() }.filter()
|
|
|
|
val descendants = statusContext.descendants.map { status -> status.toViewData() }.filter()
|
|
|
|
val statuses = ancestors + detailedStatus + descendants
|
|
|
|
|
|
|
|
_uiState.value = ThreadUiState.Success(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = statuses,
|
|
|
|
detailedStatusPosition = ancestors.size,
|
|
|
|
revealButton = statuses.getRevealButtonState()
|
2022-08-15 11:00:18 +02:00
|
|
|
)
|
|
|
|
}, { throwable ->
|
|
|
|
_errors.emit(throwable)
|
|
|
|
_uiState.value = ThreadUiState.Success(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = listOf(detailedStatus),
|
|
|
|
detailedStatusPosition = 0,
|
2022-08-15 11:00:18 +02:00
|
|
|
revealButton = RevealButtonState.NO_BUTTON,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun retry(id: String) {
|
|
|
|
_uiState.value = ThreadUiState.Loading
|
|
|
|
loadThread(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun refresh(id: String) {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
_uiState.value = ThreadUiState.Refreshing
|
2022-08-15 11:00:18 +02:00
|
|
|
loadThread(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun detailedStatus(): StatusViewData.Concrete? {
|
2023-01-15 15:18:07 +01:00
|
|
|
return when (val uiState = _uiState.value) {
|
|
|
|
is ThreadUiState.Success -> uiState.statusViewData.find { status ->
|
|
|
|
status.isDetailed
|
|
|
|
}
|
|
|
|
is ThreadUiState.LoadingThread -> uiState.statusViewDatum
|
|
|
|
else -> null
|
2022-08-15 11:00:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
|
|
|
try {
|
|
|
|
timelineCases.reblog(status.actionableId, reblog).await()
|
|
|
|
} catch (t: Exception) {
|
|
|
|
ifExpected(t) {
|
|
|
|
Log.d(TAG, "Failed to reblog status " + status.actionableId, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun favorite(favorite: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
|
|
|
try {
|
|
|
|
timelineCases.favourite(status.actionableId, favorite).await()
|
|
|
|
} catch (t: Exception) {
|
|
|
|
ifExpected(t) {
|
|
|
|
Log.d(TAG, "Failed to favourite status " + status.actionableId, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun bookmark(bookmark: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
|
|
|
try {
|
|
|
|
timelineCases.bookmark(status.actionableId, bookmark).await()
|
|
|
|
} catch (t: Exception) {
|
|
|
|
ifExpected(t) {
|
2023-01-13 20:03:47 +01:00
|
|
|
Log.d(TAG, "Failed to bookmark status " + status.actionableId, t)
|
2022-08-15 11:00:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun voteInPoll(choices: List<Int>, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
|
|
|
val poll = status.status.actionableStatus.poll ?: run {
|
|
|
|
Log.w(TAG, "No poll on status ${status.id}")
|
|
|
|
return@launch
|
|
|
|
}
|
|
|
|
|
|
|
|
val votedPoll = poll.votedCopy(choices)
|
|
|
|
updateStatus(status.id) { status ->
|
|
|
|
status.copy(poll = votedPoll)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
timelineCases.voteInPoll(status.actionableId, poll.id, choices).await()
|
|
|
|
} catch (t: Exception) {
|
|
|
|
ifExpected(t) {
|
|
|
|
Log.d(TAG, "Failed to vote in poll: " + status.actionableId, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun removeStatus(statusToRemove: StatusViewData.Concrete) {
|
|
|
|
updateSuccess { uiState ->
|
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.filterNot { status -> status == statusToRemove }
|
2022-08-15 11:00:18 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun changeExpanded(expanded: Boolean, status: StatusViewData.Concrete) {
|
|
|
|
updateSuccess { uiState ->
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val statuses = uiState.statusViewData.map { viewData ->
|
2022-08-15 11:00:18 +02:00
|
|
|
if (viewData.id == status.id) {
|
|
|
|
viewData.copy(isExpanded = expanded)
|
|
|
|
} else {
|
|
|
|
viewData
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = statuses,
|
2022-08-15 11:00:18 +02:00
|
|
|
revealButton = statuses.getRevealButtonState()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun changeContentShowing(isShowing: Boolean, status: StatusViewData.Concrete) {
|
|
|
|
updateStatusViewData(status.id) { viewData ->
|
|
|
|
viewData.copy(isShowingContent = isShowing)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData.Concrete) {
|
|
|
|
updateStatusViewData(status.id) { viewData ->
|
|
|
|
viewData.copy(isCollapsed = isCollapsed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleFavEvent(event: FavoriteEvent) {
|
|
|
|
updateStatus(event.statusId) { status ->
|
|
|
|
status.copy(favourited = event.favourite)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleReblogEvent(event: ReblogEvent) {
|
|
|
|
updateStatus(event.statusId) { status ->
|
|
|
|
status.copy(reblogged = event.reblog)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleBookmarkEvent(event: BookmarkEvent) {
|
|
|
|
updateStatus(event.statusId) { status ->
|
|
|
|
status.copy(bookmarked = event.bookmark)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handlePinEvent(event: PinEvent) {
|
|
|
|
updateStatus(event.statusId) { status ->
|
|
|
|
status.copy(pinned = event.pinned)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun removeAllByAccountId(accountId: String) {
|
|
|
|
updateSuccess { uiState ->
|
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.filter { viewData ->
|
2022-12-05 14:44:52 +01:00
|
|
|
viewData.status.account.id != accountId
|
2022-08-15 11:00:18 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleStatusComposedEvent(event: StatusComposedEvent) {
|
|
|
|
val eventStatus = event.status
|
|
|
|
updateSuccess { uiState ->
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val statuses = uiState.statusViewData
|
2022-08-15 11:00:18 +02:00
|
|
|
val detailedIndex = statuses.indexOfFirst { status -> status.isDetailed }
|
|
|
|
val repliedIndex = statuses.indexOfFirst { status -> eventStatus.inReplyToId == status.id }
|
|
|
|
if (detailedIndex != -1 && repliedIndex >= detailedIndex) {
|
|
|
|
// there is a new reply to the detailed status or below -> display it
|
|
|
|
val newStatuses = statuses.subList(0, repliedIndex + 1) +
|
|
|
|
eventStatus.toViewData() +
|
|
|
|
statuses.subList(repliedIndex + 1, statuses.size)
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
uiState.copy(statusViewData = newStatuses)
|
2022-08-15 11:00:18 +02:00
|
|
|
} else {
|
|
|
|
uiState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleStatusDeletedEvent(event: StatusDeletedEvent) {
|
|
|
|
updateSuccess { uiState ->
|
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.filter { status ->
|
2022-08-15 11:00:18 +02:00
|
|
|
status.id != event.statusId
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun toggleRevealButton() {
|
|
|
|
updateSuccess { uiState ->
|
|
|
|
when (uiState.revealButton) {
|
|
|
|
RevealButtonState.HIDE -> uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.map { viewData ->
|
2022-08-15 11:00:18 +02:00
|
|
|
viewData.copy(isExpanded = false)
|
|
|
|
},
|
|
|
|
revealButton = RevealButtonState.REVEAL
|
|
|
|
)
|
|
|
|
RevealButtonState.REVEAL -> uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.map { viewData ->
|
2022-08-15 11:00:18 +02:00
|
|
|
viewData.copy(isExpanded = true)
|
|
|
|
},
|
|
|
|
revealButton = RevealButtonState.HIDE
|
|
|
|
)
|
|
|
|
else -> uiState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
private fun StatusViewData.Concrete.getRevealButtonState(): RevealButtonState {
|
|
|
|
val hasWarnings = status.spoilerText.isNotEmpty()
|
2022-08-15 11:00:18 +02:00
|
|
|
|
|
|
|
return if (hasWarnings) {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
if (isExpanded) {
|
2022-08-15 11:00:18 +02:00
|
|
|
RevealButtonState.HIDE
|
|
|
|
} else {
|
|
|
|
RevealButtonState.REVEAL
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
RevealButtonState.NO_BUTTON
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
/**
|
|
|
|
* Get the reveal button state based on the state of all the statuses in the list.
|
|
|
|
*
|
|
|
|
* - If any status sets it to REVEAL, use REVEAL
|
|
|
|
* - If no status sets it to REVEAL, but at least one uses HIDE, use HIDE
|
|
|
|
* - Otherwise use NO_BUTTON
|
|
|
|
*/
|
|
|
|
private fun List<StatusViewData.Concrete>.getRevealButtonState(): RevealButtonState {
|
|
|
|
var seenHide = false
|
|
|
|
|
|
|
|
forEach {
|
|
|
|
when (val state = it.getRevealButtonState()) {
|
|
|
|
RevealButtonState.NO_BUTTON -> return@forEach
|
|
|
|
RevealButtonState.REVEAL -> return state
|
|
|
|
RevealButtonState.HIDE -> seenHide = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seenHide) {
|
|
|
|
return RevealButtonState.HIDE
|
|
|
|
}
|
|
|
|
|
|
|
|
return RevealButtonState.NO_BUTTON
|
|
|
|
}
|
|
|
|
|
2022-08-15 11:00:18 +02:00
|
|
|
private fun loadFilters() {
|
|
|
|
viewModelScope.launch {
|
2022-12-07 19:30:53 +01:00
|
|
|
val filters = api.getFilters().getOrElse {
|
|
|
|
Log.w(TAG, "Failed to fetch filters", it)
|
2022-08-15 11:00:18 +02:00
|
|
|
return@launch
|
|
|
|
}
|
2022-12-07 19:30:53 +01:00
|
|
|
|
2022-08-15 11:00:18 +02:00
|
|
|
filterModel.initWithFilters(
|
|
|
|
filters.filter { filter ->
|
|
|
|
filter.context.contains(Filter.THREAD)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
updateSuccess { uiState ->
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val statuses = uiState.statusViewData.filter()
|
2022-08-15 11:00:18 +02:00
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = statuses,
|
2022-08-15 11:00:18 +02:00
|
|
|
revealButton = statuses.getRevealButtonState()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun List<StatusViewData.Concrete>.filter(): List<StatusViewData.Concrete> {
|
|
|
|
return filter { status ->
|
|
|
|
status.isDetailed || !filterModel.shouldFilterStatus(status.status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:09:18 +01:00
|
|
|
private fun Status.toViewData(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
isDetailed: Boolean = false
|
2023-01-02 14:09:18 +01:00
|
|
|
): StatusViewData.Concrete {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val oldStatus = (_uiState.value as? ThreadUiState.Success)?.statusViewData?.find { it.id == this.id }
|
2022-08-15 11:00:18 +02:00
|
|
|
return toViewData(
|
2022-11-18 21:03:51 +01:00
|
|
|
isShowingContent = oldStatus?.isShowingContent ?: (alwaysShowSensitiveMedia || !actionableStatus.sensitive),
|
|
|
|
isExpanded = oldStatus?.isExpanded ?: alwaysOpenSpoiler,
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
isCollapsed = oldStatus?.isCollapsed ?: !isDetailed,
|
|
|
|
isDetailed = oldStatus?.isDetailed ?: isDetailed
|
2022-08-15 11:00:18 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
private inline fun updateSuccess(updater: (ThreadUiState.Success) -> ThreadUiState.Success) {
|
|
|
|
_uiState.update { uiState ->
|
|
|
|
if (uiState is ThreadUiState.Success) {
|
|
|
|
updater(uiState)
|
|
|
|
} else {
|
|
|
|
uiState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateStatusViewData(statusId: String, updater: (StatusViewData.Concrete) -> StatusViewData.Concrete) {
|
|
|
|
updateSuccess { uiState ->
|
|
|
|
uiState.copy(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
statusViewData = uiState.statusViewData.map { viewData ->
|
2022-08-15 11:00:18 +02:00
|
|
|
if (viewData.id == statusId) {
|
|
|
|
updater(viewData)
|
|
|
|
} else {
|
|
|
|
viewData
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateStatus(statusId: String, updater: (Status) -> Status) {
|
|
|
|
updateStatusViewData(statusId) { viewData ->
|
|
|
|
viewData.copy(
|
|
|
|
status = updater(viewData.status)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
private const val TAG = "ViewThreadViewModel"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sealed interface ThreadUiState {
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
/** The initial load of the detailed status for this thread */
|
2022-08-15 11:00:18 +02:00
|
|
|
object Loading : ThreadUiState
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
|
|
|
|
/** Loading the detailed status has completed, now loading ancestors/descendants */
|
|
|
|
data class LoadingThread(
|
|
|
|
val statusViewDatum: StatusViewData.Concrete?,
|
|
|
|
val revealButton: RevealButtonState
|
|
|
|
) : ThreadUiState
|
|
|
|
|
|
|
|
/** An error occurred at any point */
|
2022-08-15 11:00:18 +02:00
|
|
|
class Error(val throwable: Throwable) : ThreadUiState
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
|
|
|
|
/** Successfully loaded the full thread */
|
2022-08-15 11:00:18 +02:00
|
|
|
data class Success(
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val statusViewData: List<StatusViewData.Concrete>,
|
2022-08-15 11:00:18 +02:00
|
|
|
val revealButton: RevealButtonState,
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
val detailedStatusPosition: Int
|
2022-08-15 11:00:18 +02:00
|
|
|
) : ThreadUiState
|
Improve the actual and perceived speed of thread loading (#3118)
* Improve the actual and perceived speed of thread loading
To improve the actual speed, note that if the user has opened a thread from
their home timeline then the initial status is cached in the database. Other
statuses in the same thread may be cached as well.
So try and load the initial status from the database, falling back to the
network if it's not present (e.g., the user has opened a thread from the
local or federated timelines, or a search).
Introduce a new loading state to deal with this case.
In typical cases this allows the UI to display the initial status immediately
with no need to show a progress indicator.
To improve the perceived speed, delay showing the initial loading circular
progress indicators by 500ms. If loading the initial status completes within
that time no spinner is shown and the user will perceive the action as
close-to-immediate
(https://www.nngroup.com/articles/response-times-3-important-limits/).
Additionally, introduce an extra indeterminate progress indicator.
The new indicator is linear, anchored to the bottom of the screen, and shows
progress loading ancestor/descendant statuses. Like the other indicator is
also delayed 500ms from when ancestor/descendant status information is
fetched, and if the fetch completes in that time it will not be shown.
* Mark `getStatus` as suspend so it doesn't run on the main thread
* Save an allocation, use an isDetailed parameter to TimelineStatusWithAccount.toViewData
Rename Status.toViewData's "detailed" parameter to "isDetailed" for
consistency with other uses.
* Ensure suspend functions run to completion when testing
* Delay-load the status from the network even if it's cached
This speeds up the UI while ensuring it will eventually contain accurate data
from the remote.
* Load the network status before updating the list
Avoids excess animations if the network copy has changes
* Fix UI flicker when loading reblogged statuses
* Lint
* Fixup tests
2023-01-09 21:31:31 +01:00
|
|
|
|
|
|
|
/** Refreshing the thread with a swipe */
|
|
|
|
object Refreshing : ThreadUiState
|
2022-08-15 11:00:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
enum class RevealButtonState {
|
|
|
|
NO_BUTTON, REVEAL, HIDE
|
|
|
|
}
|