Migrate LiveData to Flow (#4337)

This commit is contained in:
Zongle Wang 2024-03-27 18:34:17 +08:00 committed by GitHub
parent a3d87de8ac
commit f029b7f84d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 326 additions and 274 deletions

View File

@ -30,7 +30,6 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -59,6 +58,7 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class EditProfileActivity : BaseActivity(), Injectable { class EditProfileActivity : BaseActivity(), Injectable {
@ -152,51 +152,54 @@ class EditProfileActivity : BaseActivity(), Injectable {
viewModel.obtainProfile() viewModel.obtainProfile()
viewModel.profileData.observe(this) { profileRes -> lifecycleScope.launch {
when (profileRes) { viewModel.profileData.collect { profileRes ->
is Success -> { if (profileRes == null) return@collect
val me = profileRes.data when (profileRes) {
if (me != null) { is Success -> {
binding.displayNameEditText.setText(me.displayName) val me = profileRes.data
binding.noteEditText.setText(me.source?.note) if (me != null) {
binding.lockedCheckBox.isChecked = me.locked binding.displayNameEditText.setText(me.displayName)
binding.noteEditText.setText(me.source?.note)
binding.lockedCheckBox.isChecked = me.locked
accountFieldEditAdapter.setFields(me.source?.fields.orEmpty()) accountFieldEditAdapter.setFields(me.source?.fields.orEmpty())
binding.addFieldButton.isVisible = binding.addFieldButton.isVisible =
(me.source?.fields?.size ?: 0) < maxAccountFields (me.source?.fields?.size ?: 0) < maxAccountFields
if (viewModel.avatarData.value == null) { if (viewModel.avatarData.value == null) {
Glide.with(this) Glide.with(this@EditProfileActivity)
.load(me.avatar) .load(me.avatar)
.placeholder(R.drawable.avatar_default) .placeholder(R.drawable.avatar_default)
.transform( .transform(
FitCenter(), FitCenter(),
RoundedCorners( RoundedCorners(
resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp) resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)
)
) )
) .into(binding.avatarPreview)
.into(binding.avatarPreview) }
}
if (viewModel.headerData.value == null) { if (viewModel.headerData.value == null) {
Glide.with(this) Glide.with(this@EditProfileActivity)
.load(me.header) .load(me.header)
.into(binding.headerPreview) .into(binding.headerPreview)
}
} }
} }
is Error -> {
Snackbar.make(
binding.avatarButton,
R.string.error_generic,
Snackbar.LENGTH_LONG
)
.setAction(R.string.action_retry) {
viewModel.obtainProfile()
}
.show()
}
is Loading -> { }
} }
is Error -> {
Snackbar.make(
binding.avatarButton,
R.string.error_generic,
Snackbar.LENGTH_LONG
)
.setAction(R.string.action_retry) {
viewModel.obtainProfile()
}
.show()
}
is Loading -> { }
} }
} }
@ -215,18 +218,19 @@ class EditProfileActivity : BaseActivity(), Injectable {
observeImage(viewModel.avatarData, binding.avatarPreview, true) observeImage(viewModel.avatarData, binding.avatarPreview, true)
observeImage(viewModel.headerData, binding.headerPreview, false) observeImage(viewModel.headerData, binding.headerPreview, false)
viewModel.saveData.observe( lifecycleScope.launch {
this viewModel.saveData.collect {
) { if (it == null) return@collect
when (it) { when (it) {
is Success -> { is Success -> {
finish() finish()
} }
is Loading -> { is Loading -> {
binding.saveProgressBar.visibility = View.VISIBLE binding.saveProgressBar.visibility = View.VISIBLE
} }
is Error -> { is Error -> {
onSaveFailure(it.errorMessage) onSaveFailure(it.errorMessage)
}
} }
} }
} }
@ -269,30 +273,30 @@ class EditProfileActivity : BaseActivity(), Injectable {
} }
private fun observeImage( private fun observeImage(
liveData: LiveData<Uri>, flow: StateFlow<Uri?>,
imageView: ImageView, imageView: ImageView,
roundedCorners: Boolean roundedCorners: Boolean
) { ) {
liveData.observe( lifecycleScope.launch {
this flow.collect { imageUri ->
) { imageUri ->
// skipping all caches so we can always reuse the same uri // skipping all caches so we can always reuse the same uri
val glide = Glide.with(imageView) val glide = Glide.with(imageView)
.load(imageUri) .load(imageUri)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
if (roundedCorners) { if (roundedCorners) {
glide.transform( glide.transform(
FitCenter(), FitCenter(),
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)) RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
).into(imageView) ).into(imageView)
} else { } else {
glide.into(imageView) glide.into(imageView)
}
imageView.show()
} }
imageView.show()
} }
} }

View File

@ -48,6 +48,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.MarginPageTransformer import androidx.viewpager2.widget.MarginPageTransformer
@ -109,6 +110,7 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
import kotlinx.coroutines.launch
class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider, HasAndroidInjector, LinkListener { class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider, HasAndroidInjector, LinkListener {
@ -429,10 +431,32 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
* Subscribe to data loaded at the view model * Subscribe to data loaded at the view model
*/ */
private fun subscribeObservables() { private fun subscribeObservables() {
viewModel.accountData.observe(this) { lifecycleScope.launch {
when (it) { viewModel.accountData.collect {
is Success -> onAccountChanged(it.data) if (it == null) return@collect
is Error -> { when (it) {
is Success -> onAccountChanged(it.data)
is Error -> {
Snackbar.make(
binding.accountCoordinatorLayout,
R.string.error_generic,
Snackbar.LENGTH_LONG
)
.setAction(R.string.action_retry) { viewModel.refresh() }
.show()
}
is Loading -> { }
}
}
}
lifecycleScope.launch {
viewModel.relationshipData.collect {
val relation = it?.data
if (relation != null) {
onRelationshipChanged(relation)
}
if (it is Error) {
Snackbar.make( Snackbar.make(
binding.accountCoordinatorLayout, binding.accountCoordinatorLayout,
R.string.error_generic, R.string.error_generic,
@ -441,27 +465,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
.setAction(R.string.action_retry) { viewModel.refresh() } .setAction(R.string.action_retry) { viewModel.refresh() }
.show() .show()
} }
is Loading -> { }
} }
} }
viewModel.relationshipData.observe(this) { lifecycleScope.launch {
val relation = it?.data viewModel.noteSaved.collect {
if (relation != null) { binding.saveNoteInfo.visible(it, View.INVISIBLE)
onRelationshipChanged(relation)
} }
if (it is Error) {
Snackbar.make(
binding.accountCoordinatorLayout,
R.string.error_generic,
Snackbar.LENGTH_LONG
)
.setAction(R.string.action_retry) { viewModel.refresh() }
.show()
}
}
viewModel.noteSaved.observe(this) {
binding.saveNoteInfo.visible(it, View.INVISIBLE)
} }
// "Post failed" dialog should display in this activity // "Post failed" dialog should display in this activity
@ -478,10 +487,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
*/ */
private fun setupRefreshLayout() { private fun setupRefreshLayout() {
binding.swipeToRefreshLayout.setOnRefreshListener { onRefresh() } binding.swipeToRefreshLayout.setOnRefreshListener { onRefresh() }
viewModel.isRefreshing.observe( lifecycleScope.launch {
this viewModel.isRefreshing.collect { isRefreshing ->
) { isRefreshing -> binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true }
} }
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue) binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
} }

View File

@ -1,7 +1,6 @@
package com.keylesspalace.tusky.components.account package com.keylesspalace.tusky.components.account
import android.util.Log import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
@ -23,6 +22,9 @@ import com.keylesspalace.tusky.util.getDomain
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AccountViewModel @Inject constructor( class AccountViewModel @Inject constructor(
@ -31,12 +33,18 @@ class AccountViewModel @Inject constructor(
accountManager: AccountManager accountManager: AccountManager
) : ViewModel() { ) : ViewModel() {
val accountData = MutableLiveData<Resource<Account>>() private val _accountData = MutableStateFlow(null as Resource<Account>?)
val relationshipData = MutableLiveData<Resource<Relationship>>() val accountData: StateFlow<Resource<Account>?> = _accountData.asStateFlow()
val noteSaved = MutableLiveData<Boolean>() private val _relationshipData = MutableStateFlow(null as Resource<Relationship>?)
val relationshipData: StateFlow<Resource<Relationship>?> = _relationshipData.asStateFlow()
private val _noteSaved = MutableStateFlow(false)
val noteSaved: StateFlow<Boolean> = _noteSaved.asStateFlow()
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
val isRefreshing = MutableLiveData<Boolean>().apply { value = false }
private var isDataLoading = false private var isDataLoading = false
lateinit var accountId: String lateinit var accountId: String
@ -55,17 +63,17 @@ class AccountViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
eventHub.events.collect { event -> eventHub.events.collect { event ->
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) { if (event is ProfileEditedEvent && event.newProfileData.id == _accountData.value?.data?.id) {
accountData.postValue(Success(event.newProfileData)) _accountData.value = Success(event.newProfileData)
} }
} }
} }
} }
private fun obtainAccount(reload: Boolean = false) { private fun obtainAccount(reload: Boolean = false) {
if (accountData.value == null || reload) { if (_accountData.value == null || reload) {
isDataLoading = true isDataLoading = true
accountData.postValue(Loading()) _accountData.value = Loading()
viewModelScope.launch { viewModelScope.launch {
mastodonApi.account(accountId) mastodonApi.account(accountId)
@ -74,15 +82,15 @@ class AccountViewModel @Inject constructor(
domain = getDomain(account.url) domain = getDomain(account.url)
isFromOwnDomain = domain == activeAccount.domain isFromOwnDomain = domain == activeAccount.domain
accountData.postValue(Success(account)) _accountData.value = Success(account)
isDataLoading = false isDataLoading = false
isRefreshing.postValue(false) _isRefreshing.value = false
}, },
{ t -> { t ->
Log.w(TAG, "failed obtaining account", t) Log.w(TAG, "failed obtaining account", t)
accountData.postValue(Error(cause = t)) _accountData.value = Error(cause = t)
isDataLoading = false isDataLoading = false
isRefreshing.postValue(false) _isRefreshing.value = false
} }
) )
} }
@ -90,14 +98,14 @@ class AccountViewModel @Inject constructor(
} }
private fun obtainRelationship(reload: Boolean = false) { private fun obtainRelationship(reload: Boolean = false) {
if (relationshipData.value == null || reload) { if (_relationshipData.value == null || reload) {
relationshipData.postValue(Loading()) _relationshipData.value = Loading()
viewModelScope.launch { viewModelScope.launch {
mastodonApi.relationships(listOf(accountId)) mastodonApi.relationships(listOf(accountId))
.fold( .fold(
{ relationships -> { relationships ->
relationshipData.postValue( _relationshipData.value =
if (relationships.isNotEmpty()) { if (relationships.isNotEmpty()) {
Success( Success(
relationships[0] relationships[0]
@ -105,11 +113,10 @@ class AccountViewModel @Inject constructor(
} else { } else {
Error() Error()
} }
)
}, },
{ t -> { t ->
Log.w(TAG, "failed obtaining relationships", t) Log.w(TAG, "failed obtaining relationships", t)
relationshipData.postValue(Error(cause = t)) _relationshipData.value = Error(cause = t)
} }
) )
} }
@ -117,7 +124,7 @@ class AccountViewModel @Inject constructor(
} }
fun changeFollowState() { fun changeFollowState() {
val relationship = relationshipData.value?.data val relationship = _relationshipData.value?.data
if (relationship?.following == true || relationship?.requested == true) { if (relationship?.following == true || relationship?.requested == true) {
changeRelationship(RelationShipAction.UNFOLLOW) changeRelationship(RelationShipAction.UNFOLLOW)
} else { } else {
@ -126,7 +133,7 @@ class AccountViewModel @Inject constructor(
} }
fun changeBlockState() { fun changeBlockState() {
if (relationshipData.value?.data?.blocking == true) { if (_relationshipData.value?.data?.blocking == true) {
changeRelationship(RelationShipAction.UNBLOCK) changeRelationship(RelationShipAction.UNBLOCK)
} else { } else {
changeRelationship(RelationShipAction.BLOCK) changeRelationship(RelationShipAction.BLOCK)
@ -142,7 +149,7 @@ class AccountViewModel @Inject constructor(
} }
fun changeSubscribingState() { fun changeSubscribingState() {
val relationship = relationshipData.value?.data val relationship = _relationshipData.value?.data
if (relationship?.notifying == true || // Mastodon 3.3.0rc1 if (relationship?.notifying == true || // Mastodon 3.3.0rc1
relationship?.subscribing == true // Pleroma relationship?.subscribing == true // Pleroma
) { ) {
@ -156,9 +163,9 @@ class AccountViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
mastodonApi.blockDomain(instance).fold({ mastodonApi.blockDomain(instance).fold({
eventHub.dispatch(DomainMuteEvent(instance)) eventHub.dispatch(DomainMuteEvent(instance))
val relation = relationshipData.value?.data val relation = _relationshipData.value?.data
if (relation != null) { if (relation != null) {
relationshipData.postValue(Success(relation.copy(blockingDomain = true))) _relationshipData.value = Success(relation.copy(blockingDomain = true))
} }
}, { e -> }, { e ->
Log.e(TAG, "Error muting $instance", e) Log.e(TAG, "Error muting $instance", e)
@ -169,9 +176,9 @@ class AccountViewModel @Inject constructor(
fun unblockDomain(instance: String) { fun unblockDomain(instance: String) {
viewModelScope.launch { viewModelScope.launch {
mastodonApi.unblockDomain(instance).fold({ mastodonApi.unblockDomain(instance).fold({
val relation = relationshipData.value?.data val relation = _relationshipData.value?.data
if (relation != null) { if (relation != null) {
relationshipData.postValue(Success(relation.copy(blockingDomain = false))) _relationshipData.value = Success(relation.copy(blockingDomain = false))
} }
}, { e -> }, { e ->
Log.e(TAG, "Error unmuting $instance", e) Log.e(TAG, "Error unmuting $instance", e)
@ -180,7 +187,7 @@ class AccountViewModel @Inject constructor(
} }
fun changeShowReblogsState() { fun changeShowReblogsState() {
if (relationshipData.value?.data?.showingReblogs == true) { if (_relationshipData.value?.data?.showingReblogs == true) {
changeRelationship(RelationShipAction.FOLLOW, false) changeRelationship(RelationShipAction.FOLLOW, false)
} else { } else {
changeRelationship(RelationShipAction.FOLLOW, true) changeRelationship(RelationShipAction.FOLLOW, true)
@ -195,9 +202,9 @@ class AccountViewModel @Inject constructor(
parameter: Boolean? = null, parameter: Boolean? = null,
duration: Int? = null duration: Int? = null
) = viewModelScope.launch { ) = viewModelScope.launch {
val relation = relationshipData.value?.data val relation = _relationshipData.value?.data
val account = accountData.value?.data val account = _accountData.value?.data
val isMastodon = relationshipData.value?.data?.notifying != null val isMastodon = _relationshipData.value?.data?.notifying != null
if (relation != null && account != null) { if (relation != null && account != null) {
// optimistically post new state for faster response // optimistically post new state for faster response
@ -230,7 +237,7 @@ class AccountViewModel @Inject constructor(
} }
} }
} }
relationshipData.postValue(Loading(newRelation)) _relationshipData.value = Loading(newRelation)
} }
val relationshipCall = when (relationshipAction) { val relationshipCall = when (relationshipAction) {
@ -265,7 +272,7 @@ class AccountViewModel @Inject constructor(
relationshipCall.fold( relationshipCall.fold(
{ relationship -> { relationship ->
relationshipData.postValue(Success(relationship)) _relationshipData.value = Success(relationship)
when (relationshipAction) { when (relationshipAction) {
RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId)) RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId))
@ -276,22 +283,22 @@ class AccountViewModel @Inject constructor(
}, },
{ t -> { t ->
Log.w(TAG, "failed loading relationship", t) Log.w(TAG, "failed loading relationship", t)
relationshipData.postValue(Error(relation, cause = t)) _relationshipData.value = Error(relation, cause = t)
} }
) )
} }
fun noteChanged(newNote: String) { fun noteChanged(newNote: String) {
noteSaved.postValue(false) _noteSaved.value = false
noteUpdateJob?.cancel() noteUpdateJob?.cancel()
noteUpdateJob = viewModelScope.launch { noteUpdateJob = viewModelScope.launch {
delay(1500) delay(1500)
mastodonApi.updateAccountNote(accountId, newNote) mastodonApi.updateAccountNote(accountId, newNote)
.fold( .fold(
{ {
noteSaved.postValue(true) _noteSaved.value = true
delay(4000) delay(4000)
noteSaved.postValue(false) _noteSaved.value = false
}, },
{ t -> { t ->
Log.w(TAG, "Error updating note", t) Log.w(TAG, "Error updating note", t)

View File

@ -26,6 +26,7 @@ import android.view.View
import android.widget.PopupWindow import android.widget.PopupWindow
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -53,6 +54,7 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
class AnnouncementsActivity : class AnnouncementsActivity :
BottomSheetActivity(), BottomSheetActivity(),
@ -111,41 +113,46 @@ class AnnouncementsActivity :
binding.announcementsList.adapter = adapter binding.announcementsList.adapter = adapter
viewModel.announcements.observe(this) { lifecycleScope.launch {
when (it) { viewModel.announcements.collect {
is Success -> { if (it == null) return@collect
binding.progressBar.hide() when (it) {
binding.swipeRefreshLayout.isRefreshing = false is Success -> {
if (it.data.isNullOrEmpty()) { binding.progressBar.hide()
binding.errorMessageView.setup( binding.swipeRefreshLayout.isRefreshing = false
R.drawable.elephant_friend_empty, if (it.data.isNullOrEmpty()) {
R.string.no_announcements binding.errorMessageView.setup(
) R.drawable.elephant_friend_empty,
binding.errorMessageView.show() R.string.no_announcements
} else { )
binding.errorMessageView.show()
} else {
binding.errorMessageView.hide()
}
adapter.updateList(it.data ?: listOf())
}
is Loading -> {
binding.errorMessageView.hide() binding.errorMessageView.hide()
} }
adapter.updateList(it.data ?: listOf()) is Error -> {
} binding.progressBar.hide()
is Loading -> { binding.swipeRefreshLayout.isRefreshing = false
binding.errorMessageView.hide() binding.errorMessageView.setup(
} R.drawable.errorphant_error,
is Error -> { R.string.error_generic
binding.progressBar.hide() ) {
binding.swipeRefreshLayout.isRefreshing = false refreshAnnouncements()
binding.errorMessageView.setup( }
R.drawable.errorphant_error, binding.errorMessageView.show()
R.string.error_generic
) {
refreshAnnouncements()
} }
binding.errorMessageView.show()
} }
} }
} }
viewModel.emojis.observe(this) { lifecycleScope.launch {
picker.adapter = EmojiAdapter(it, this, animateEmojis) viewModel.emoji.collect {
picker.adapter = EmojiAdapter(it, this@AnnouncementsActivity, animateEmojis)
}
} }
viewModel.load() viewModel.load()

View File

@ -16,8 +16,6 @@
package com.keylesspalace.tusky.components.announcements package com.keylesspalace.tusky.components.announcements
import android.util.Log import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
@ -32,6 +30,9 @@ import com.keylesspalace.tusky.util.Loading
import com.keylesspalace.tusky.util.Resource import com.keylesspalace.tusky.util.Resource
import com.keylesspalace.tusky.util.Success import com.keylesspalace.tusky.util.Success
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AnnouncementsViewModel @Inject constructor( class AnnouncementsViewModel @Inject constructor(
@ -40,25 +41,25 @@ class AnnouncementsViewModel @Inject constructor(
private val eventHub: EventHub private val eventHub: EventHub
) : ViewModel() { ) : ViewModel() {
private val announcementsMutable = MutableLiveData<Resource<List<Announcement>>>() private val _announcements = MutableStateFlow(null as Resource<List<Announcement>>?)
val announcements: LiveData<Resource<List<Announcement>>> = announcementsMutable val announcements: StateFlow<Resource<List<Announcement>>?> = _announcements.asStateFlow()
private val emojisMutable = MutableLiveData<List<Emoji>>() private val _emoji = MutableStateFlow(emptyList<Emoji>())
val emojis: LiveData<List<Emoji>> = emojisMutable val emoji: StateFlow<List<Emoji>> = _emoji.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
emojisMutable.postValue(instanceInfoRepo.getEmojis()) _emoji.value = instanceInfoRepo.getEmojis()
} }
} }
fun load() { fun load() {
viewModelScope.launch { viewModelScope.launch {
announcementsMutable.postValue(Loading()) _announcements.value = Loading()
mastodonApi.listAnnouncements() mastodonApi.listAnnouncements()
.fold( .fold(
{ {
announcementsMutable.postValue(Success(it)) _announcements.value = Success(it)
it.filter { announcement -> !announcement.read } it.filter { announcement -> !announcement.read }
.forEach { announcement -> .forEach { announcement ->
mastodonApi.dismissAnnouncement(announcement.id) mastodonApi.dismissAnnouncement(announcement.id)
@ -79,7 +80,7 @@ class AnnouncementsViewModel @Inject constructor(
} }
}, },
{ {
announcementsMutable.postValue(Error(cause = it)) _announcements.value = Error(cause = it)
} }
) )
} }
@ -90,9 +91,9 @@ class AnnouncementsViewModel @Inject constructor(
mastodonApi.addAnnouncementReaction(announcementId, name) mastodonApi.addAnnouncementReaction(announcementId, name)
.fold( .fold(
{ {
announcementsMutable.postValue( _announcements.value =
Success( Success(
announcements.value!!.data!!.map { announcement -> announcements.value?.data?.map { announcement ->
if (announcement.id == announcementId) { if (announcement.id == announcementId) {
announcement.copy( announcement.copy(
reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) { reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
@ -109,7 +110,7 @@ class AnnouncementsViewModel @Inject constructor(
} else { } else {
listOf( listOf(
*announcement.reactions.toTypedArray(), *announcement.reactions.toTypedArray(),
emojis.value!!.find { emoji -> emoji.shortcode == name }!!.run { emoji.value.find { emoji -> emoji.shortcode == name }!!.run {
Announcement.Reaction( Announcement.Reaction(
name, name,
1, 1,
@ -126,7 +127,6 @@ class AnnouncementsViewModel @Inject constructor(
} }
} }
) )
)
}, },
{ {
Log.w(TAG, "Failed to add reaction to the announcement.", it) Log.w(TAG, "Failed to add reaction to the announcement.", it)
@ -140,7 +140,7 @@ class AnnouncementsViewModel @Inject constructor(
mastodonApi.removeAnnouncementReaction(announcementId, name) mastodonApi.removeAnnouncementReaction(announcementId, name)
.fold( .fold(
{ {
announcementsMutable.postValue( _announcements.value =
Success( Success(
announcements.value!!.data!!.map { announcement -> announcements.value!!.data!!.map { announcement ->
if (announcement.id == announcementId) { if (announcement.id == announcementId) {
@ -165,7 +165,6 @@ class AnnouncementsViewModel @Inject constructor(
} }
} }
) )
)
}, },
{ {
Log.w(TAG, "Failed to remove reaction from the announcement.", it) Log.w(TAG, "Failed to remove reaction from the announcement.", it)

View File

@ -19,6 +19,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter import com.keylesspalace.tusky.components.report.adapter.ReportPagerAdapter
@ -28,6 +29,7 @@ import com.keylesspalace.tusky.util.viewBinding
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
class ReportActivity : BottomSheetActivity(), HasAndroidInjector { class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
@ -82,8 +84,9 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
} }
private fun subscribeObservables() { private fun subscribeObservables() {
viewModel.navigation.observe(this) { screen -> lifecycleScope.launch {
if (screen != null) { viewModel.navigation.collect { screen ->
if (screen == null) return@collect
viewModel.navigated() viewModel.navigated()
when (screen) { when (screen) {
Screen.Statuses -> showStatusesPage() Screen.Statuses -> showStatusesPage()
@ -95,10 +98,12 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
} }
} }
viewModel.checkUrl.observe(this) { lifecycleScope.launch {
if (!it.isNullOrBlank()) { viewModel.checkUrl.collect {
viewModel.urlChecked() if (!it.isNullOrBlank()) {
viewUrl(it) viewModel.urlChecked()
viewUrl(it)
}
} }
} }
} }

View File

@ -15,8 +15,6 @@
package com.keylesspalace.tusky.components.report package com.keylesspalace.tusky.components.report
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.Pager import androidx.paging.Pager
@ -40,6 +38,9 @@ import com.keylesspalace.tusky.util.toViewData
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -49,20 +50,20 @@ class ReportViewModel @Inject constructor(
private val eventHub: EventHub private val eventHub: EventHub
) : ViewModel() { ) : ViewModel() {
private val navigationMutable = MutableLiveData<Screen?>() private val navigationMutable = MutableStateFlow(null as Screen?)
val navigation: LiveData<Screen?> = navigationMutable val navigation: StateFlow<Screen?> = navigationMutable.asStateFlow()
private val muteStateMutable = MutableLiveData<Resource<Boolean>>() private val muteStateMutable = MutableStateFlow(null as Resource<Boolean>?)
val muteState: LiveData<Resource<Boolean>> = muteStateMutable val muteState: StateFlow<Resource<Boolean>?> = muteStateMutable.asStateFlow()
private val blockStateMutable = MutableLiveData<Resource<Boolean>>() private val blockStateMutable = MutableStateFlow(null as Resource<Boolean>?)
val blockState: LiveData<Resource<Boolean>> = blockStateMutable val blockState: StateFlow<Resource<Boolean>?> = blockStateMutable.asStateFlow()
private val reportingStateMutable = MutableLiveData<Resource<Boolean>>() private val reportingStateMutable = MutableStateFlow(null as Resource<Boolean>?)
var reportingState: LiveData<Resource<Boolean>> = reportingStateMutable var reportingState: StateFlow<Resource<Boolean>?> = reportingStateMutable.asStateFlow()
private val checkUrlMutable = MutableLiveData<String?>() private val checkUrlMutable = MutableStateFlow(null as String?)
val checkUrl: LiveData<String?> = checkUrlMutable val checkUrl: StateFlow<String?> = checkUrlMutable.asStateFlow()
private val accountIdFlow = MutableSharedFlow<String>( private val accountIdFlow = MutableSharedFlow<String>(
replay = 1, replay = 1,

View File

@ -19,6 +19,7 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.report.ReportViewModel
import com.keylesspalace.tusky.components.report.Screen import com.keylesspalace.tusky.components.report.Screen
@ -30,6 +31,7 @@ import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable { class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
@ -47,37 +49,43 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
} }
private fun subscribeObservables() { private fun subscribeObservables() {
viewModel.muteState.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
if (it !is Loading) { viewModel.muteState.collect {
binding.buttonMute.show() if (it == null) return@collect
binding.progressMute.show() if (it !is Loading) {
} else { binding.buttonMute.show()
binding.buttonMute.hide() binding.progressMute.show()
binding.progressMute.hide() } else {
} binding.buttonMute.hide()
binding.progressMute.hide()
binding.buttonMute.setText(
when (it.data) {
true -> R.string.action_unmute
else -> R.string.action_mute
} }
)
binding.buttonMute.setText(
when (it.data) {
true -> R.string.action_unmute
else -> R.string.action_mute
}
)
}
} }
viewModel.blockState.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
if (it !is Loading) { viewModel.blockState.collect {
binding.buttonBlock.show() if (it == null) return@collect
binding.progressBlock.show() if (it !is Loading) {
} else { binding.buttonBlock.show()
binding.buttonBlock.hide() binding.progressBlock.show()
binding.progressBlock.hide() } else {
} binding.buttonBlock.hide()
binding.buttonBlock.setText( binding.progressBlock.hide()
when (it.data) {
true -> R.string.action_unblock
else -> R.string.action_block
} }
) binding.buttonBlock.setText(
when (it.data) {
true -> R.string.action_unblock
else -> R.string.action_block
}
)
}
} }
} }

View File

@ -20,6 +20,7 @@ import android.view.View
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.report.ReportViewModel
@ -35,6 +36,7 @@ import com.keylesspalace.tusky.util.show
import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.util.viewBinding
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable { class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
@ -79,11 +81,14 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
} }
private fun subscribeObservables() { private fun subscribeObservables() {
viewModel.reportingState.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
when (it) { viewModel.reportingState.collect {
is Success -> viewModel.navigateTo(Screen.Done) if (it == null) return@collect
is Loading -> showLoading() when (it) {
is Error -> showError(it.cause) is Success -> viewModel.navigateTo(Screen.Done)
is Loading -> showLoading()
is Error -> showError(it.cause)
}
} }
} }
} }

View File

@ -15,12 +15,12 @@
package com.keylesspalace.tusky.db package com.keylesspalace.tusky.db
import androidx.lifecycle.LiveData
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface DraftDao { interface DraftDao {
@ -32,7 +32,7 @@ interface DraftDao {
fun draftsPagingSource(accountId: Long): PagingSource<Int, DraftEntity> fun draftsPagingSource(accountId: Long): PagingSource<Int, DraftEntity>
@Query("SELECT COUNT(*) FROM DraftEntity WHERE accountId = :accountId AND failedToSendNew = 1") @Query("SELECT COUNT(*) FROM DraftEntity WHERE accountId = :accountId AND failedToSendNew = 1")
fun draftsNeedUserAlert(accountId: Long): LiveData<Int> fun draftsNeedUserAlert(accountId: Long): Flow<Int>
@Query( @Query(
"UPDATE DraftEntity SET failedToSendNew = 0 WHERE accountId = :accountId AND failedToSendNew = 1" "UPDATE DraftEntity SET failedToSendNew = 0 WHERE accountId = :accountId AND failedToSendNew = 1"

View File

@ -51,36 +51,37 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
// Assume a single MainActivity, AccountActivity or DraftsActivity never sees more then one user id in its lifetime. // Assume a single MainActivity, AccountActivity or DraftsActivity never sees more then one user id in its lifetime.
val activeAccountId = activeAccount.id val activeAccountId = activeAccount.id
// This LiveData will be automatically disposed when the activity is destroyed.
val draftsNeedUserAlert = draftDao.draftsNeedUserAlert(activeAccountId) val draftsNeedUserAlert = draftDao.draftsNeedUserAlert(activeAccountId)
// observe ensures that this gets called at the most appropriate moment wrt the context lifecycle— // observe ensures that this gets called at the most appropriate moment wrt the context lifecycle—
// at init, at next onResume, or immediately if the context is resumed already. // at init, at next onResume, or immediately if the context is resumed already.
if (showAlert) { coroutineScope.launch {
draftsNeedUserAlert.observe(context) { count -> if (showAlert) {
Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count") draftsNeedUserAlert.collect { count ->
if (count > 0) { Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count")
AlertDialog.Builder(context) if (count > 0) {
.setTitle(R.string.action_post_failed) AlertDialog.Builder(context)
.setMessage( .setTitle(R.string.action_post_failed)
context.resources.getQuantityString(R.plurals.action_post_failed_detail, count) .setMessage(
) context.resources.getQuantityString(R.plurals.action_post_failed_detail, count)
.setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int -> )
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts .setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int ->
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts
val intent = DraftsActivity.newIntent(context) val intent = DraftsActivity.newIntent(context)
context.startActivity(intent) context.startActivity(intent)
} }
.setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int -> .setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int ->
clearDraftsAlert(coroutineScope, activeAccountId) // User doesn't care clearDraftsAlert(coroutineScope, activeAccountId) // User doesn't care
} }
.show() .show()
}
}
} else {
draftsNeedUserAlert.collect {
Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts")
clearDraftsAlert(coroutineScope, activeAccountId)
} }
}
} else {
draftsNeedUserAlert.observe(context) {
Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts")
clearDraftsAlert(coroutineScope, activeAccountId)
} }
} }
} ?: run { } ?: run {

View File

@ -18,7 +18,6 @@ package com.keylesspalace.tusky.viewmodel
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import at.connyduck.calladapter.networkresult.fold import at.connyduck.calladapter.networkresult.fold
@ -40,6 +39,7 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.Flow 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.StateFlow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
@ -66,10 +66,17 @@ class EditProfileViewModel @Inject constructor(
private val instanceInfoRepo: InstanceInfoRepository private val instanceInfoRepo: InstanceInfoRepository
) : ViewModel() { ) : ViewModel() {
val profileData = MutableLiveData<Resource<Account>>() private val _profileData = MutableStateFlow(null as Resource<Account>?)
val avatarData = MutableLiveData<Uri>() val profileData: StateFlow<Resource<Account>?> = _profileData.asStateFlow()
val headerData = MutableLiveData<Uri>()
val saveData = MutableLiveData<Resource<Nothing>>() private val _avatarData = MutableStateFlow(null as Uri?)
val avatarData: StateFlow<Uri?> = _avatarData.asStateFlow()
private val _headerData = MutableStateFlow(null as Uri?)
val headerData: StateFlow<Uri?> = _headerData.asStateFlow()
private val _saveData = MutableStateFlow(null as Resource<Nothing>?)
val saveData: StateFlow<Resource<Nothing>?> = _saveData.asStateFlow()
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)
@ -80,16 +87,16 @@ class EditProfileViewModel @Inject constructor(
private var apiProfileAccount: Account? = null private var apiProfileAccount: Account? = null
fun obtainProfile() = viewModelScope.launch { fun obtainProfile() = viewModelScope.launch {
if (profileData.value == null || profileData.value is Error) { if (_profileData.value == null || _profileData.value is Error) {
profileData.postValue(Loading()) _profileData.value = Loading()
mastodonApi.accountVerifyCredentials().fold( mastodonApi.accountVerifyCredentials().fold(
{ profile -> { profile ->
apiProfileAccount = profile apiProfileAccount = profile
profileData.postValue(Success(profile)) _profileData.value = Success(profile)
}, },
{ {
profileData.postValue(Error()) _profileData.value = Error()
} }
) )
} }
@ -100,11 +107,11 @@ class EditProfileViewModel @Inject constructor(
fun getHeaderUri() = getCacheFileForName(HEADER_FILE_NAME).toUri() fun getHeaderUri() = getCacheFileForName(HEADER_FILE_NAME).toUri()
fun newAvatarPicked() { fun newAvatarPicked() {
avatarData.value = getAvatarUri() _avatarData.value = getAvatarUri()
} }
fun newHeaderPicked() { fun newHeaderPicked() {
headerData.value = getHeaderUri() _headerData.value = getHeaderUri()
} }
internal fun dataChanged(newProfileData: ProfileDataInUi) { internal fun dataChanged(newProfileData: ProfileDataInUi) {
@ -112,16 +119,16 @@ class EditProfileViewModel @Inject constructor(
} }
internal fun save(newProfileData: ProfileDataInUi) { internal fun save(newProfileData: ProfileDataInUi) {
if (saveData.value is Loading || profileData.value !is Success) { if (_saveData.value is Loading || _profileData.value !is Success) {
return return
} }
saveData.value = Loading() _saveData.value = Loading()
val diff = getProfileDiff(apiProfileAccount, newProfileData) val diff = getProfileDiff(apiProfileAccount, newProfileData)
if (!diff.hasChanges()) { if (!diff.hasChanges()) {
// if nothing has changed, there is no need to make an api call // if nothing has changed, there is no need to make an api call
saveData.value = Success() _saveData.value = Success()
return return
} }
@ -160,11 +167,11 @@ class EditProfileViewModel @Inject constructor(
diff.field4?.second?.toRequestBody(MultipartBody.FORM) diff.field4?.second?.toRequestBody(MultipartBody.FORM)
).fold( ).fold(
{ newAccountData -> { newAccountData ->
saveData.postValue(Success()) _saveData.value = Success()
eventHub.dispatch(ProfileEditedEvent(newAccountData)) eventHub.dispatch(ProfileEditedEvent(newAccountData))
}, },
{ throwable -> { throwable ->
saveData.postValue(Error(errorMessage = throwable.getServerErrorMessage())) _saveData.value = Error(errorMessage = throwable.getServerErrorMessage())
} }
) )
} }
@ -172,18 +179,18 @@ class EditProfileViewModel @Inject constructor(
// cache activity state for rotation change // cache activity state for rotation change
internal fun updateProfile(newProfileData: ProfileDataInUi) { internal fun updateProfile(newProfileData: ProfileDataInUi) {
if (profileData.value is Success) { if (_profileData.value is Success) {
val newProfileSource = profileData.value?.data?.source?.copy( val newProfileSource = _profileData.value?.data?.source?.copy(
note = newProfileData.note, note = newProfileData.note,
fields = newProfileData.fields fields = newProfileData.fields
) )
val newProfile = profileData.value?.data?.copy( val newProfile = _profileData.value?.data?.copy(
displayName = newProfileData.displayName, displayName = newProfileData.displayName,
locked = newProfileData.locked, locked = newProfileData.locked,
source = newProfileSource source = newProfileSource
) )
profileData.value = Success(newProfile) _profileData.value = Success(newProfile)
} }
} }
@ -209,13 +216,13 @@ class EditProfileViewModel @Inject constructor(
newProfileData.locked newProfileData.locked
} }
val avatarFile = if (avatarData.value != null) { val avatarFile = if (_avatarData.value != null) {
getCacheFileForName(AVATAR_FILE_NAME) getCacheFileForName(AVATAR_FILE_NAME)
} else { } else {
null null
} }
val headerFile = if (headerData.value != null) { val headerFile = if (_headerData.value != null) {
getCacheFileForName(HEADER_FILE_NAME) getCacheFileForName(HEADER_FILE_NAME)
} else { } else {
null null

View File

@ -75,7 +75,6 @@ androidx-emoji2-view-helper = { module = "androidx.emoji2:emoji2-views-helper",
androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "androidx-exifinterface" } androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "androidx-exifinterface" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
androidx-lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" } androidx-lifecycle-common-java8 = { module = "androidx.lifecycle:lifecycle-common-java8", version.ref = "androidx-lifecycle" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-reactivestreams-ktx = { module = "androidx.lifecycle:lifecycle-reactivestreams-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-reactivestreams-ktx = { module = "androidx.lifecycle:lifecycle-reactivestreams-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" }
@ -137,7 +136,7 @@ xmlwriter = { module = "org.pageseeder.xmlwriter:pso-xmlwriter", version.ref = "
androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx", "androidx-browser", "androidx-swiperefreshlayout", androidx = ["androidx-core-ktx", "androidx-appcompat", "androidx-fragment-ktx", "androidx-browser", "androidx-swiperefreshlayout",
"androidx-recyclerview", "androidx-exifinterface", "androidx-cardview", "androidx-preference-ktx", "androidx-sharetarget", "androidx-recyclerview", "androidx-exifinterface", "androidx-cardview", "androidx-preference-ktx", "androidx-sharetarget",
"androidx-emoji2-core", "androidx-emoji2-views-core", "androidx-emoji2-view-helper", "androidx-lifecycle-viewmodel-ktx", "androidx-emoji2-core", "androidx-emoji2-views-core", "androidx-emoji2-view-helper", "androidx-lifecycle-viewmodel-ktx",
"androidx-lifecycle-livedata-ktx", "androidx-lifecycle-common-java8", "androidx-lifecycle-reactivestreams-ktx", "androidx-lifecycle-common-java8", "androidx-lifecycle-reactivestreams-ktx",
"androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx", "androidx-constraintlayout", "androidx-paging-runtime-ktx", "androidx-viewpager2", "androidx-work-runtime-ktx",
"androidx-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-datasource-okhttp", "androidx-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-datasource-okhttp",
"androidx-media3-ui"] "androidx-media3-ui"]