Don't use mutable state flows in UI (#4336)
This commit is contained in:
parent
3274bd2660
commit
a3d87de8ac
|
@ -1040,7 +1040,7 @@ class ComposeActivity :
|
||||||
|
|
||||||
override fun onVisibilityChanged(visibility: Status.Visibility) {
|
override fun onVisibilityChanged(visibility: Status.Visibility) {
|
||||||
composeOptionsBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
composeOptionsBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
viewModel.statusVisibility.value = visibility
|
viewModel.changeStatusVisibility(visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
|
@ -45,7 +45,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
@ -86,16 +88,25 @@ class ComposeViewModel @Inject constructor(
|
||||||
val emoji: SharedFlow<List<Emoji>> = instanceInfoRepo::getEmojis.asFlow()
|
val emoji: SharedFlow<List<Emoji>> = instanceInfoRepo::getEmojis.asFlow()
|
||||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
val markMediaAsSensitive: MutableStateFlow<Boolean> =
|
private val _markMediaAsSensitive =
|
||||||
MutableStateFlow(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
MutableStateFlow(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
||||||
|
val markMediaAsSensitive: StateFlow<Boolean> = _markMediaAsSensitive.asStateFlow()
|
||||||
|
|
||||||
val statusVisibility: MutableStateFlow<Status.Visibility> =
|
private val _statusVisibility = MutableStateFlow(Status.Visibility.UNKNOWN)
|
||||||
MutableStateFlow(Status.Visibility.UNKNOWN)
|
val statusVisibility: StateFlow<Status.Visibility> = _statusVisibility.asStateFlow()
|
||||||
val showContentWarning: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
val poll: MutableStateFlow<NewPoll?> = MutableStateFlow(null)
|
private val _showContentWarning = MutableStateFlow(false)
|
||||||
val scheduledAt: MutableStateFlow<String?> = MutableStateFlow(null)
|
val showContentWarning: StateFlow<Boolean> = _showContentWarning.asStateFlow()
|
||||||
|
|
||||||
|
private val _poll = MutableStateFlow(null as NewPoll?)
|
||||||
|
val poll: StateFlow<NewPoll?> = _poll.asStateFlow()
|
||||||
|
|
||||||
|
private val _scheduledAt = MutableStateFlow(null as String?)
|
||||||
|
val scheduledAt: StateFlow<String?> = _scheduledAt.asStateFlow()
|
||||||
|
|
||||||
|
private val _media = MutableStateFlow(emptyList<QueuedMedia>())
|
||||||
|
val media: StateFlow<List<QueuedMedia>> = _media.asStateFlow()
|
||||||
|
|
||||||
val media: MutableStateFlow<List<QueuedMedia>> = MutableStateFlow(emptyList())
|
|
||||||
val uploadError =
|
val uploadError =
|
||||||
MutableSharedFlow<Throwable>(
|
MutableSharedFlow<Throwable>(
|
||||||
replay = 0,
|
replay = 0,
|
||||||
|
@ -103,7 +114,8 @@ class ComposeViewModel @Inject constructor(
|
||||||
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||||
)
|
)
|
||||||
|
|
||||||
val closeConfirmation = MutableStateFlow(ConfirmationKind.NONE)
|
private val _closeConfirmation = MutableStateFlow(ConfirmationKind.NONE)
|
||||||
|
val closeConfirmation: StateFlow<ConfirmationKind> = _closeConfirmation.asStateFlow()
|
||||||
|
|
||||||
private lateinit var composeKind: ComposeKind
|
private lateinit var composeKind: ComposeKind
|
||||||
|
|
||||||
|
@ -121,7 +133,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri, instanceInfo.first())
|
val (type, uri, size) = mediaUploader.prepareMedia(mediaUri, instanceInfo.first())
|
||||||
val mediaItems = media.value
|
val mediaItems = _media.value
|
||||||
if (type != QueuedMedia.Type.IMAGE &&
|
if (type != QueuedMedia.Type.IMAGE &&
|
||||||
mediaItems.isNotEmpty() &&
|
mediaItems.isNotEmpty() &&
|
||||||
mediaItems[0].type == QueuedMedia.Type.IMAGE
|
mediaItems[0].type == QueuedMedia.Type.IMAGE
|
||||||
|
@ -146,7 +158,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
): QueuedMedia {
|
): QueuedMedia {
|
||||||
var stashMediaItem: QueuedMedia? = null
|
var stashMediaItem: QueuedMedia? = null
|
||||||
|
|
||||||
media.update { mediaList ->
|
_media.update { mediaList ->
|
||||||
val mediaItem = QueuedMedia(
|
val mediaItem = QueuedMedia(
|
||||||
localId = mediaUploader.getNewLocalMediaId(),
|
localId = mediaUploader.getNewLocalMediaId(),
|
||||||
uri = uri,
|
uri = uri,
|
||||||
|
@ -173,7 +185,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
mediaUploader
|
mediaUploader
|
||||||
.uploadMedia(mediaItem, instanceInfo.first())
|
.uploadMedia(mediaItem, instanceInfo.first())
|
||||||
.collect { event ->
|
.collect { event ->
|
||||||
val item = media.value.find { it.localId == mediaItem.localId }
|
val item = _media.value.find { it.localId == mediaItem.localId }
|
||||||
?: return@collect
|
?: return@collect
|
||||||
val newMediaItem = when (event) {
|
val newMediaItem = when (event) {
|
||||||
is UploadEvent.ProgressEvent ->
|
is UploadEvent.ProgressEvent ->
|
||||||
|
@ -189,12 +201,12 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
is UploadEvent.ErrorEvent -> {
|
is UploadEvent.ErrorEvent -> {
|
||||||
media.update { mediaList -> mediaList.filter { it.localId != mediaItem.localId } }
|
_media.update { mediaList -> mediaList.filter { it.localId != mediaItem.localId } }
|
||||||
uploadError.emit(event.error)
|
uploadError.emit(event.error)
|
||||||
return@collect
|
return@collect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
media.update { mediaList ->
|
_media.update { mediaList ->
|
||||||
mediaList.map { mediaItem ->
|
mediaList.map { mediaItem ->
|
||||||
if (mediaItem.localId == newMediaItem.localId) {
|
if (mediaItem.localId == newMediaItem.localId) {
|
||||||
newMediaItem
|
newMediaItem
|
||||||
|
@ -209,6 +221,10 @@ class ComposeViewModel @Inject constructor(
|
||||||
return mediaItem
|
return mediaItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeStatusVisibility(visibility: Status.Visibility) {
|
||||||
|
_statusVisibility.value = visibility
|
||||||
|
}
|
||||||
|
|
||||||
private fun addUploadedMedia(
|
private fun addUploadedMedia(
|
||||||
id: String,
|
id: String,
|
||||||
type: QueuedMedia.Type,
|
type: QueuedMedia.Type,
|
||||||
|
@ -216,7 +232,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
description: String?,
|
description: String?,
|
||||||
focus: Attachment.Focus?
|
focus: Attachment.Focus?
|
||||||
) {
|
) {
|
||||||
media.update { mediaList ->
|
_media.update { mediaList ->
|
||||||
val mediaItem = QueuedMedia(
|
val mediaItem = QueuedMedia(
|
||||||
localId = mediaUploader.getNewLocalMediaId(),
|
localId = mediaUploader.getNewLocalMediaId(),
|
||||||
uri = uri,
|
uri = uri,
|
||||||
|
@ -234,12 +250,12 @@ class ComposeViewModel @Inject constructor(
|
||||||
|
|
||||||
fun removeMediaFromQueue(item: QueuedMedia) {
|
fun removeMediaFromQueue(item: QueuedMedia) {
|
||||||
mediaUploader.cancelUploadScope(item.localId)
|
mediaUploader.cancelUploadScope(item.localId)
|
||||||
media.update { mediaList -> mediaList.filter { it.localId != item.localId } }
|
_media.update { mediaList -> mediaList.filter { it.localId != item.localId } }
|
||||||
updateCloseConfirmation()
|
updateCloseConfirmation()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleMarkSensitive() {
|
fun toggleMarkSensitive() {
|
||||||
this.markMediaAsSensitive.value = this.markMediaAsSensitive.value != true
|
this._markMediaAsSensitive.value = this._markMediaAsSensitive.value != true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateContent(newContent: String?) {
|
fun updateContent(newContent: String?) {
|
||||||
|
@ -253,12 +269,12 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCloseConfirmation() {
|
private fun updateCloseConfirmation() {
|
||||||
val contentWarning = if (showContentWarning.value) {
|
val contentWarning = if (_showContentWarning.value) {
|
||||||
currentContentWarning
|
currentContentWarning
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
this.closeConfirmation.value = if (didChange(currentContent, contentWarning)) {
|
this._closeConfirmation.value = if (didChange(currentContent, contentWarning)) {
|
||||||
when (composeKind) {
|
when (composeKind) {
|
||||||
ComposeKind.NEW -> if (isEmpty(currentContent, contentWarning)) {
|
ComposeKind.NEW -> if (isEmpty(currentContent, contentWarning)) {
|
||||||
ConfirmationKind.NONE
|
ConfirmationKind.NONE
|
||||||
|
@ -281,19 +297,19 @@ class ComposeViewModel @Inject constructor(
|
||||||
private fun didChange(content: String?, contentWarning: String?): Boolean {
|
private fun didChange(content: String?, contentWarning: String?): Boolean {
|
||||||
val textChanged = content.orEmpty() != startingText.orEmpty()
|
val textChanged = content.orEmpty() != startingText.orEmpty()
|
||||||
val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning
|
val contentWarningChanged = contentWarning.orEmpty() != startingContentWarning
|
||||||
val mediaChanged = media.value.isNotEmpty()
|
val mediaChanged = _media.value.isNotEmpty()
|
||||||
val pollChanged = poll.value != null
|
val pollChanged = _poll.value != null
|
||||||
val didScheduledTimeChange = hasScheduledTimeChanged
|
val didScheduledTimeChange = hasScheduledTimeChanged
|
||||||
|
|
||||||
return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged || didScheduledTimeChange
|
return modifiedInitialState || textChanged || contentWarningChanged || mediaChanged || pollChanged || didScheduledTimeChange
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEmpty(content: String?, contentWarning: String?): Boolean {
|
private fun isEmpty(content: String?, contentWarning: String?): Boolean {
|
||||||
return !modifiedInitialState && (content.isNullOrBlank() && contentWarning.isNullOrBlank() && media.value.isEmpty() && poll.value == null)
|
return !modifiedInitialState && (content.isNullOrBlank() && contentWarning.isNullOrBlank() && _media.value.isEmpty() && _poll.value == null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contentWarningChanged(value: Boolean) {
|
fun contentWarningChanged(value: Boolean) {
|
||||||
showContentWarning.value = value
|
_showContentWarning.value = value
|
||||||
contentWarningStateChanged = true
|
contentWarningStateChanged = true
|
||||||
updateCloseConfirmation()
|
updateCloseConfirmation()
|
||||||
}
|
}
|
||||||
|
@ -307,12 +323,12 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopUploads() {
|
fun stopUploads() {
|
||||||
mediaUploader.cancelUploadScope(*media.value.map { it.localId }.toIntArray())
|
mediaUploader.cancelUploadScope(*_media.value.map { it.localId }.toIntArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldShowSaveDraftDialog(): Boolean {
|
fun shouldShowSaveDraftDialog(): Boolean {
|
||||||
// if any of the media files need to be downloaded first it could take a while, so show a loading dialog
|
// if any of the media files need to be downloaded first it could take a while, so show a loading dialog
|
||||||
return media.value.any { mediaValue ->
|
return _media.value.any { mediaValue ->
|
||||||
mediaValue.uri.scheme == "https"
|
mediaValue.uri.scheme == "https"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +337,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
val mediaUris: MutableList<String> = mutableListOf()
|
val mediaUris: MutableList<String> = mutableListOf()
|
||||||
val mediaDescriptions: MutableList<String?> = mutableListOf()
|
val mediaDescriptions: MutableList<String?> = mutableListOf()
|
||||||
val mediaFocus: MutableList<Attachment.Focus?> = mutableListOf()
|
val mediaFocus: MutableList<Attachment.Focus?> = mutableListOf()
|
||||||
for (item in media.value) {
|
for (item in _media.value) {
|
||||||
mediaUris.add(item.uri.toString())
|
mediaUris.add(item.uri.toString())
|
||||||
mediaDescriptions.add(item.description)
|
mediaDescriptions.add(item.description)
|
||||||
mediaFocus.add(item.focus)
|
mediaFocus.add(item.focus)
|
||||||
|
@ -333,15 +349,15 @@ class ComposeViewModel @Inject constructor(
|
||||||
inReplyToId = inReplyToId,
|
inReplyToId = inReplyToId,
|
||||||
content = content,
|
content = content,
|
||||||
contentWarning = contentWarning,
|
contentWarning = contentWarning,
|
||||||
sensitive = markMediaAsSensitive.value,
|
sensitive = _markMediaAsSensitive.value,
|
||||||
visibility = statusVisibility.value,
|
visibility = _statusVisibility.value,
|
||||||
mediaUris = mediaUris,
|
mediaUris = mediaUris,
|
||||||
mediaDescriptions = mediaDescriptions,
|
mediaDescriptions = mediaDescriptions,
|
||||||
mediaFocus = mediaFocus,
|
mediaFocus = mediaFocus,
|
||||||
poll = poll.value,
|
poll = _poll.value,
|
||||||
failedToSend = false,
|
failedToSend = false,
|
||||||
failedToSendAlert = false,
|
failedToSendAlert = false,
|
||||||
scheduledAt = scheduledAt.value,
|
scheduledAt = _scheduledAt.value,
|
||||||
language = postLanguage,
|
language = postLanguage,
|
||||||
statusId = originalStatusId
|
statusId = originalStatusId
|
||||||
)
|
)
|
||||||
|
@ -356,7 +372,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
api.deleteScheduledStatus(scheduledTootId!!)
|
api.deleteScheduledStatus(scheduledTootId!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
val attachedMedia = media.value.map { item ->
|
val attachedMedia = _media.value.map { item ->
|
||||||
MediaToSend(
|
MediaToSend(
|
||||||
localId = item.localId,
|
localId = item.localId,
|
||||||
id = item.id,
|
id = item.id,
|
||||||
|
@ -369,12 +385,12 @@ class ComposeViewModel @Inject constructor(
|
||||||
val tootToSend = StatusToSend(
|
val tootToSend = StatusToSend(
|
||||||
text = content,
|
text = content,
|
||||||
warningText = spoilerText,
|
warningText = spoilerText,
|
||||||
visibility = statusVisibility.value.serverString(),
|
visibility = _statusVisibility.value.serverString(),
|
||||||
sensitive = attachedMedia.isNotEmpty() && (markMediaAsSensitive.value || showContentWarning.value),
|
sensitive = attachedMedia.isNotEmpty() && (_markMediaAsSensitive.value || _showContentWarning.value),
|
||||||
media = attachedMedia,
|
media = attachedMedia,
|
||||||
scheduledAt = scheduledAt.value,
|
scheduledAt = _scheduledAt.value,
|
||||||
inReplyToId = inReplyToId,
|
inReplyToId = inReplyToId,
|
||||||
poll = poll.value,
|
poll = _poll.value,
|
||||||
replyingStatusContent = null,
|
replyingStatusContent = null,
|
||||||
replyingStatusAuthorUsername = null,
|
replyingStatusAuthorUsername = null,
|
||||||
accountId = accountId,
|
accountId = accountId,
|
||||||
|
@ -389,7 +405,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMediaItem(localId: Int, mutator: (QueuedMedia) -> QueuedMedia) {
|
private fun updateMediaItem(localId: Int, mutator: (QueuedMedia) -> QueuedMedia) {
|
||||||
media.update { mediaList ->
|
_media.update { mediaList ->
|
||||||
mediaList.map { mediaItem ->
|
mediaList.map { mediaItem ->
|
||||||
if (mediaItem.localId == localId) {
|
if (mediaItem.localId == localId) {
|
||||||
mutator(mediaItem)
|
mutator(mediaItem)
|
||||||
|
@ -478,7 +494,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
startingContentWarning = contentWarning
|
startingContentWarning = contentWarning
|
||||||
}
|
}
|
||||||
if (!contentWarningStateChanged) {
|
if (!contentWarningStateChanged) {
|
||||||
showContentWarning.value = !contentWarning.isNullOrBlank()
|
_showContentWarning.value = !contentWarning.isNullOrBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
// recreate media list
|
// recreate media list
|
||||||
|
@ -512,7 +528,7 @@ class ComposeViewModel @Inject constructor(
|
||||||
if (tootVisibility.num != Status.Visibility.UNKNOWN.num) {
|
if (tootVisibility.num != Status.Visibility.UNKNOWN.num) {
|
||||||
startingVisibility = tootVisibility
|
startingVisibility = tootVisibility
|
||||||
}
|
}
|
||||||
statusVisibility.value = startingVisibility
|
_statusVisibility.value = startingVisibility
|
||||||
val mentionedUsernames = composeOptions?.mentionedUsernames
|
val mentionedUsernames = composeOptions?.mentionedUsernames
|
||||||
if (mentionedUsernames != null) {
|
if (mentionedUsernames != null) {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
|
@ -524,13 +540,13 @@ class ComposeViewModel @Inject constructor(
|
||||||
startingText = builder.toString()
|
startingText = builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduledAt.value = composeOptions?.scheduledAt
|
_scheduledAt.value = composeOptions?.scheduledAt
|
||||||
|
|
||||||
composeOptions?.sensitive?.let { markMediaAsSensitive.value = it }
|
composeOptions?.sensitive?.let { _markMediaAsSensitive.value = it }
|
||||||
|
|
||||||
val poll = composeOptions?.poll
|
val poll = composeOptions?.poll
|
||||||
if (poll != null && composeOptions.mediaAttachments.isNullOrEmpty()) {
|
if (poll != null && composeOptions.mediaAttachments.isNullOrEmpty()) {
|
||||||
this.poll.value = poll
|
this._poll.value = poll
|
||||||
}
|
}
|
||||||
replyingStatusContent = composeOptions?.replyingStatusContent
|
replyingStatusContent = composeOptions?.replyingStatusContent
|
||||||
replyingStatusAuthor = composeOptions?.replyingStatusAuthor
|
replyingStatusAuthor = composeOptions?.replyingStatusAuthor
|
||||||
|
@ -541,16 +557,16 @@ class ComposeViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePoll(newPoll: NewPoll?) {
|
fun updatePoll(newPoll: NewPoll?) {
|
||||||
poll.value = newPoll
|
_poll.value = newPoll
|
||||||
updateCloseConfirmation()
|
updateCloseConfirmation()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateScheduledAt(newScheduledAt: String?) {
|
fun updateScheduledAt(newScheduledAt: String?) {
|
||||||
if (newScheduledAt != scheduledAt.value) {
|
if (newScheduledAt != _scheduledAt.value) {
|
||||||
hasScheduledTimeChanged = true
|
hasScheduledTimeChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduledAt.value = newScheduledAt
|
_scheduledAt.value = newScheduledAt
|
||||||
}
|
}
|
||||||
|
|
||||||
val editing: Boolean
|
val editing: Boolean
|
||||||
|
|
|
@ -11,79 +11,91 @@ import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.isHttpNotFound
|
import com.keylesspalace.tusky.util.isHttpNotFound
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub: EventHub) : ViewModel() {
|
class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub: EventHub) : ViewModel() {
|
||||||
private var originalFilter: Filter? = null
|
private var originalFilter: Filter? = null
|
||||||
val title = MutableStateFlow("")
|
|
||||||
val keywords = MutableStateFlow(listOf<FilterKeyword>())
|
private val _title = MutableStateFlow("")
|
||||||
val action = MutableStateFlow(Filter.Action.WARN)
|
val title: StateFlow<String> = _title.asStateFlow()
|
||||||
val duration = MutableStateFlow(0)
|
|
||||||
val contexts = MutableStateFlow(listOf<Filter.Kind>())
|
private val _keywords = MutableStateFlow(listOf<FilterKeyword>())
|
||||||
|
val keywords: StateFlow<List<FilterKeyword>> = _keywords.asStateFlow()
|
||||||
|
|
||||||
|
private val _action = MutableStateFlow(Filter.Action.WARN)
|
||||||
|
val action: StateFlow<Filter.Action> = _action.asStateFlow()
|
||||||
|
|
||||||
|
private val _duration = MutableStateFlow(0)
|
||||||
|
val duration: StateFlow<Int> = _duration.asStateFlow()
|
||||||
|
|
||||||
|
private val _contexts = MutableStateFlow(listOf<Filter.Kind>())
|
||||||
|
val contexts: StateFlow<List<Filter.Kind>> = _contexts.asStateFlow()
|
||||||
|
|
||||||
fun load(filter: Filter) {
|
fun load(filter: Filter) {
|
||||||
originalFilter = filter
|
originalFilter = filter
|
||||||
title.value = filter.title
|
_title.value = filter.title
|
||||||
keywords.value = filter.keywords
|
_keywords.value = filter.keywords
|
||||||
action.value = filter.action
|
_action.value = filter.action
|
||||||
duration.value = if (filter.expiresAt == null) {
|
_duration.value = if (filter.expiresAt == null) {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
contexts.value = filter.kinds
|
_contexts.value = filter.kinds
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addKeyword(keyword: FilterKeyword) {
|
fun addKeyword(keyword: FilterKeyword) {
|
||||||
keywords.value += keyword
|
_keywords.value += keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteKeyword(keyword: FilterKeyword) {
|
fun deleteKeyword(keyword: FilterKeyword) {
|
||||||
keywords.value = keywords.value.filterNot { it == keyword }
|
_keywords.value = _keywords.value.filterNot { it == keyword }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun modifyKeyword(original: FilterKeyword, updated: FilterKeyword) {
|
fun modifyKeyword(original: FilterKeyword, updated: FilterKeyword) {
|
||||||
val index = keywords.value.indexOf(original)
|
val index = _keywords.value.indexOf(original)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
keywords.value = keywords.value.toMutableList().apply {
|
_keywords.value = _keywords.value.toMutableList().apply {
|
||||||
set(index, updated)
|
set(index, updated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTitle(title: String) {
|
fun setTitle(title: String) {
|
||||||
this.title.value = title
|
this._title.value = title
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDuration(index: Int) {
|
fun setDuration(index: Int) {
|
||||||
duration.value = index
|
_duration.value = index
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAction(action: Filter.Action) {
|
fun setAction(action: Filter.Action) {
|
||||||
this.action.value = action
|
this._action.value = action
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addContext(context: Filter.Kind) {
|
fun addContext(context: Filter.Kind) {
|
||||||
if (!contexts.value.contains(context)) {
|
if (!_contexts.value.contains(context)) {
|
||||||
contexts.value += context
|
_contexts.value += context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeContext(context: Filter.Kind) {
|
fun removeContext(context: Filter.Kind) {
|
||||||
contexts.value = contexts.value.filter { it != context }
|
_contexts.value = _contexts.value.filter { it != context }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validate(): Boolean {
|
fun validate(): Boolean {
|
||||||
return title.value.isNotBlank() &&
|
return _title.value.isNotBlank() &&
|
||||||
keywords.value.isNotEmpty() &&
|
_keywords.value.isNotEmpty() &&
|
||||||
contexts.value.isNotEmpty()
|
_contexts.value.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveChanges(context: Context): Boolean {
|
suspend fun saveChanges(context: Context): Boolean {
|
||||||
val contexts = contexts.value.map { it.kind }
|
val contexts = _contexts.value.map { it.kind }
|
||||||
val title = title.value
|
val title = _title.value
|
||||||
val durationIndex = duration.value
|
val durationIndex = _duration.value
|
||||||
val action = action.value.action
|
val action = _action.value.action
|
||||||
|
|
||||||
return withContext(viewModelScope.coroutineContext) {
|
return withContext(viewModelScope.coroutineContext) {
|
||||||
originalFilter?.let { filter ->
|
originalFilter?.let { filter ->
|
||||||
|
@ -108,7 +120,7 @@ class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub
|
||||||
).fold(
|
).fold(
|
||||||
{ newFilter ->
|
{ newFilter ->
|
||||||
// This is _terrible_, but the all-in-one update filter api Just Doesn't Work
|
// This is _terrible_, but the all-in-one update filter api Just Doesn't Work
|
||||||
return keywords.value.map { keyword ->
|
return _keywords.value.map { keyword ->
|
||||||
api.addFilterKeyword(
|
api.addFilterKeyword(
|
||||||
filterId = newFilter.id,
|
filterId = newFilter.id,
|
||||||
keyword = keyword.keyword,
|
keyword = keyword.keyword,
|
||||||
|
@ -144,7 +156,7 @@ class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub
|
||||||
).fold(
|
).fold(
|
||||||
{
|
{
|
||||||
// This is _terrible_, but the all-in-one update filter api Just Doesn't Work
|
// This is _terrible_, but the all-in-one update filter api Just Doesn't Work
|
||||||
val results = keywords.value.map { keyword ->
|
val results = _keywords.value.map { keyword ->
|
||||||
if (keyword.id.isEmpty()) {
|
if (keyword.id.isEmpty()) {
|
||||||
api.addFilterKeyword(filterId = originalFilter.id, keyword = keyword.keyword, wholeWord = keyword.wholeWord)
|
api.addFilterKeyword(filterId = originalFilter.id, keyword = keyword.keyword, wholeWord = keyword.wholeWord)
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,7 +164,7 @@ class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub
|
||||||
}
|
}
|
||||||
} + originalFilter.keywords.filter { keyword ->
|
} + originalFilter.keywords.filter { keyword ->
|
||||||
// Deleted keywords
|
// Deleted keywords
|
||||||
keywords.value.none { it.id == keyword.id }
|
_keywords.value.none { it.id == keyword.id }
|
||||||
}.map { api.deleteFilterKeyword(it.id) }
|
}.map { api.deleteFilterKeyword(it.id) }
|
||||||
|
|
||||||
return results.none { it.isFailure }
|
return results.none { it.isFailure }
|
||||||
|
@ -170,13 +182,13 @@ class EditFilterViewModel @Inject constructor(val api: MastodonApi, val eventHub
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createFilterV1(context: List<String>, expiresInSeconds: Int?): Boolean {
|
private suspend fun createFilterV1(context: List<String>, expiresInSeconds: Int?): Boolean {
|
||||||
return keywords.value.map { keyword ->
|
return _keywords.value.map { keyword ->
|
||||||
api.createFilterV1(keyword.keyword, context, false, keyword.wholeWord, expiresInSeconds)
|
api.createFilterV1(keyword.keyword, context, false, keyword.wholeWord, expiresInSeconds)
|
||||||
}.none { it.isFailure }
|
}.none { it.isFailure }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateFilterV1(context: List<String>, expiresInSeconds: Int?): Boolean {
|
private suspend fun updateFilterV1(context: List<String>, expiresInSeconds: Int?): Boolean {
|
||||||
val results = keywords.value.map { keyword ->
|
val results = _keywords.value.map { keyword ->
|
||||||
if (originalFilter == null) {
|
if (originalFilter == null) {
|
||||||
api.createFilterV1(
|
api.createFilterV1(
|
||||||
phrase = keyword.keyword,
|
phrase = keyword.keyword,
|
||||||
|
|
|
@ -23,13 +23,15 @@ import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.isHttpNotFound
|
import com.keylesspalace.tusky.util.isHttpNotFound
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LoginWebViewViewModel @Inject constructor(
|
class LoginWebViewViewModel @Inject constructor(
|
||||||
private val api: MastodonApi
|
private val api: MastodonApi
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val instanceRules: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
|
private val _instanceRules = MutableStateFlow(emptyList<String>())
|
||||||
|
val instanceRules = _instanceRules.asStateFlow()
|
||||||
|
|
||||||
private var domain: String? = null
|
private var domain: String? = null
|
||||||
|
|
||||||
|
@ -39,13 +41,13 @@ class LoginWebViewViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
api.getInstance().fold(
|
api.getInstance().fold(
|
||||||
{ instance ->
|
{ instance ->
|
||||||
instanceRules.value = instance.rules.orEmpty().map { rule -> rule.text }
|
_instanceRules.value = instance.rules.orEmpty().map { rule -> rule.text }
|
||||||
},
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
if (throwable.isHttpNotFound()) {
|
if (throwable.isHttpNotFound()) {
|
||||||
api.getInstanceV1(domain).fold(
|
api.getInstanceV1(domain).fold(
|
||||||
{ instance ->
|
{ instance ->
|
||||||
instanceRules.value = instance.rules?.map { rule -> rule.text }.orEmpty()
|
_instanceRules.value = instance.rules?.map { rule -> rule.text }.orEmpty()
|
||||||
},
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
Log.w(
|
Log.w(
|
||||||
|
|
|
@ -51,6 +51,7 @@ import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -64,9 +65,8 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
private val gson: Gson
|
private val gson: Gson
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState: MutableStateFlow<ThreadUiState> = MutableStateFlow(ThreadUiState.Loading)
|
private val _uiState = MutableStateFlow(ThreadUiState.Loading as ThreadUiState)
|
||||||
val uiState: Flow<ThreadUiState>
|
val uiState: Flow<ThreadUiState> = _uiState.asStateFlow()
|
||||||
get() = _uiState
|
|
||||||
|
|
||||||
private val _errors =
|
private val _errors =
|
||||||
MutableSharedFlow<Throwable>(
|
MutableSharedFlow<Throwable>(
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.pageseeder.xmlwriter.XMLStringWriter
|
||||||
|
|
||||||
class ViewEditsViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() {
|
class ViewEditsViewModel @Inject constructor(private val api: MastodonApi) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState: MutableStateFlow<EditsUiState> = MutableStateFlow(EditsUiState.Initial)
|
private val _uiState = MutableStateFlow(EditsUiState.Initial as EditsUiState)
|
||||||
val uiState: StateFlow<EditsUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<EditsUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
/** The API call to fetch edit history returned less than two items */
|
/** The API call to fetch edit history returned less than two items */
|
||||||
|
|
|
@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.shareIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
@ -73,7 +74,8 @@ class EditProfileViewModel @Inject constructor(
|
||||||
val instanceData: Flow<InstanceInfo> = instanceInfoRepo::getUpdatedInstanceInfoOrFallback.asFlow()
|
val instanceData: Flow<InstanceInfo> = instanceInfoRepo::getUpdatedInstanceInfoOrFallback.asFlow()
|
||||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
val isChanged = MutableStateFlow(false)
|
private val _isChanged = MutableStateFlow(false)
|
||||||
|
val isChanged = _isChanged.asStateFlow()
|
||||||
|
|
||||||
private var apiProfileAccount: Account? = null
|
private var apiProfileAccount: Account? = null
|
||||||
|
|
||||||
|
@ -106,7 +108,7 @@ class EditProfileViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun dataChanged(newProfileData: ProfileDataInUi) {
|
internal fun dataChanged(newProfileData: ProfileDataInUi) {
|
||||||
isChanged.value = getProfileDiff(apiProfileAccount, newProfileData).hasChanges()
|
_isChanged.value = getProfileDiff(apiProfileAccount, newProfileData).hasChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun save(newProfileData: ProfileDataInUi) {
|
internal fun save(newProfileData: ProfileDataInUi) {
|
||||||
|
|
Loading…
Reference in New Issue