Migrate LiveData to Flow (#4337)
This commit is contained in:
parent
a3d87de8ac
commit
f029b7f84d
13 changed files with 326 additions and 274 deletions
|
@ -30,7 +30,6 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.sizeDp
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EditProfileActivity : BaseActivity(), Injectable {
|
||||
|
@ -152,7 +152,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
viewModel.obtainProfile()
|
||||
|
||||
viewModel.profileData.observe(this) { profileRes ->
|
||||
lifecycleScope.launch {
|
||||
viewModel.profileData.collect { profileRes ->
|
||||
if (profileRes == null) return@collect
|
||||
when (profileRes) {
|
||||
is Success -> {
|
||||
val me = profileRes.data
|
||||
|
@ -166,7 +168,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
(me.source?.fields?.size ?: 0) < maxAccountFields
|
||||
|
||||
if (viewModel.avatarData.value == null) {
|
||||
Glide.with(this)
|
||||
Glide.with(this@EditProfileActivity)
|
||||
.load(me.avatar)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
.transform(
|
||||
|
@ -179,7 +181,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
if (viewModel.headerData.value == null) {
|
||||
Glide.with(this)
|
||||
Glide.with(this@EditProfileActivity)
|
||||
.load(me.header)
|
||||
.into(binding.headerPreview)
|
||||
}
|
||||
|
@ -199,6 +201,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
is Loading -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.instanceData.collect { instanceInfo ->
|
||||
|
@ -215,9 +218,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
observeImage(viewModel.avatarData, binding.avatarPreview, true)
|
||||
observeImage(viewModel.headerData, binding.headerPreview, false)
|
||||
|
||||
viewModel.saveData.observe(
|
||||
this
|
||||
) {
|
||||
lifecycleScope.launch {
|
||||
viewModel.saveData.collect {
|
||||
if (it == null) return@collect
|
||||
when (it) {
|
||||
is Success -> {
|
||||
finish()
|
||||
|
@ -230,6 +233,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.displayNameEditText.doAfterTextChanged {
|
||||
viewModel.dataChanged(currentProfileData)
|
||||
|
@ -269,13 +273,12 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
private fun observeImage(
|
||||
liveData: LiveData<Uri>,
|
||||
flow: StateFlow<Uri?>,
|
||||
imageView: ImageView,
|
||||
roundedCorners: Boolean
|
||||
) {
|
||||
liveData.observe(
|
||||
this
|
||||
) { imageUri ->
|
||||
lifecycleScope.launch {
|
||||
flow.collect { imageUri ->
|
||||
|
||||
// skipping all caches so we can always reuse the same uri
|
||||
val glide = Glide.with(imageView)
|
||||
|
@ -295,6 +298,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
imageView.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun pickMedia(pickType: PickType) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
|
|
|
@ -48,6 +48,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.WindowInsetsCompat.Type.systemBars
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.viewpager2.widget.MarginPageTransformer
|
||||
|
@ -109,6 +110,7 @@ import java.text.SimpleDateFormat
|
|||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider, HasAndroidInjector, LinkListener {
|
||||
|
||||
|
@ -429,7 +431,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
|
|||
* Subscribe to data loaded at the view model
|
||||
*/
|
||||
private fun subscribeObservables() {
|
||||
viewModel.accountData.observe(this) {
|
||||
lifecycleScope.launch {
|
||||
viewModel.accountData.collect {
|
||||
if (it == null) return@collect
|
||||
when (it) {
|
||||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
|
@ -444,7 +448,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
|
|||
is Loading -> { }
|
||||
}
|
||||
}
|
||||
viewModel.relationshipData.observe(this) {
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.relationshipData.collect {
|
||||
val relation = it?.data
|
||||
if (relation != null) {
|
||||
onRelationshipChanged(relation)
|
||||
|
@ -460,9 +466,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
|
|||
.show()
|
||||
}
|
||||
}
|
||||
viewModel.noteSaved.observe(this) {
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
viewModel.noteSaved.collect {
|
||||
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
// "Post failed" dialog should display in this activity
|
||||
draftsAlert.observeInContext(this, true)
|
||||
|
@ -478,11 +487,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvide
|
|||
*/
|
||||
private fun setupRefreshLayout() {
|
||||
binding.swipeToRefreshLayout.setOnRefreshListener { onRefresh() }
|
||||
viewModel.isRefreshing.observe(
|
||||
this
|
||||
) { isRefreshing ->
|
||||
lifecycleScope.launch {
|
||||
viewModel.isRefreshing.collect { isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
}
|
||||
}
|
||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.keylesspalace.tusky.components.account
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
|
@ -23,6 +22,9 @@ import com.keylesspalace.tusky.util.getDomain
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AccountViewModel @Inject constructor(
|
||||
|
@ -31,12 +33,18 @@ class AccountViewModel @Inject constructor(
|
|||
accountManager: AccountManager
|
||||
) : ViewModel() {
|
||||
|
||||
val accountData = MutableLiveData<Resource<Account>>()
|
||||
val relationshipData = MutableLiveData<Resource<Relationship>>()
|
||||
private val _accountData = MutableStateFlow(null as Resource<Account>?)
|
||||
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
|
||||
|
||||
lateinit var accountId: String
|
||||
|
@ -55,17 +63,17 @@ class AccountViewModel @Inject constructor(
|
|||
init {
|
||||
viewModelScope.launch {
|
||||
eventHub.events.collect { event ->
|
||||
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) {
|
||||
accountData.postValue(Success(event.newProfileData))
|
||||
if (event is ProfileEditedEvent && event.newProfileData.id == _accountData.value?.data?.id) {
|
||||
_accountData.value = Success(event.newProfileData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun obtainAccount(reload: Boolean = false) {
|
||||
if (accountData.value == null || reload) {
|
||||
if (_accountData.value == null || reload) {
|
||||
isDataLoading = true
|
||||
accountData.postValue(Loading())
|
||||
_accountData.value = Loading()
|
||||
|
||||
viewModelScope.launch {
|
||||
mastodonApi.account(accountId)
|
||||
|
@ -74,15 +82,15 @@ class AccountViewModel @Inject constructor(
|
|||
domain = getDomain(account.url)
|
||||
isFromOwnDomain = domain == activeAccount.domain
|
||||
|
||||
accountData.postValue(Success(account))
|
||||
_accountData.value = Success(account)
|
||||
isDataLoading = false
|
||||
isRefreshing.postValue(false)
|
||||
_isRefreshing.value = false
|
||||
},
|
||||
{ t ->
|
||||
Log.w(TAG, "failed obtaining account", t)
|
||||
accountData.postValue(Error(cause = t))
|
||||
_accountData.value = Error(cause = t)
|
||||
isDataLoading = false
|
||||
isRefreshing.postValue(false)
|
||||
_isRefreshing.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -90,14 +98,14 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun obtainRelationship(reload: Boolean = false) {
|
||||
if (relationshipData.value == null || reload) {
|
||||
relationshipData.postValue(Loading())
|
||||
if (_relationshipData.value == null || reload) {
|
||||
_relationshipData.value = Loading()
|
||||
|
||||
viewModelScope.launch {
|
||||
mastodonApi.relationships(listOf(accountId))
|
||||
.fold(
|
||||
{ relationships ->
|
||||
relationshipData.postValue(
|
||||
_relationshipData.value =
|
||||
if (relationships.isNotEmpty()) {
|
||||
Success(
|
||||
relationships[0]
|
||||
|
@ -105,11 +113,10 @@ class AccountViewModel @Inject constructor(
|
|||
} else {
|
||||
Error()
|
||||
}
|
||||
)
|
||||
},
|
||||
{ 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() {
|
||||
val relationship = relationshipData.value?.data
|
||||
val relationship = _relationshipData.value?.data
|
||||
if (relationship?.following == true || relationship?.requested == true) {
|
||||
changeRelationship(RelationShipAction.UNFOLLOW)
|
||||
} else {
|
||||
|
@ -126,7 +133,7 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun changeBlockState() {
|
||||
if (relationshipData.value?.data?.blocking == true) {
|
||||
if (_relationshipData.value?.data?.blocking == true) {
|
||||
changeRelationship(RelationShipAction.UNBLOCK)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.BLOCK)
|
||||
|
@ -142,7 +149,7 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun changeSubscribingState() {
|
||||
val relationship = relationshipData.value?.data
|
||||
val relationship = _relationshipData.value?.data
|
||||
if (relationship?.notifying == true || // Mastodon 3.3.0rc1
|
||||
relationship?.subscribing == true // Pleroma
|
||||
) {
|
||||
|
@ -156,9 +163,9 @@ class AccountViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
mastodonApi.blockDomain(instance).fold({
|
||||
eventHub.dispatch(DomainMuteEvent(instance))
|
||||
val relation = relationshipData.value?.data
|
||||
val relation = _relationshipData.value?.data
|
||||
if (relation != null) {
|
||||
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
|
||||
_relationshipData.value = Success(relation.copy(blockingDomain = true))
|
||||
}
|
||||
}, { e ->
|
||||
Log.e(TAG, "Error muting $instance", e)
|
||||
|
@ -169,9 +176,9 @@ class AccountViewModel @Inject constructor(
|
|||
fun unblockDomain(instance: String) {
|
||||
viewModelScope.launch {
|
||||
mastodonApi.unblockDomain(instance).fold({
|
||||
val relation = relationshipData.value?.data
|
||||
val relation = _relationshipData.value?.data
|
||||
if (relation != null) {
|
||||
relationshipData.postValue(Success(relation.copy(blockingDomain = false)))
|
||||
_relationshipData.value = Success(relation.copy(blockingDomain = false))
|
||||
}
|
||||
}, { e ->
|
||||
Log.e(TAG, "Error unmuting $instance", e)
|
||||
|
@ -180,7 +187,7 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun changeShowReblogsState() {
|
||||
if (relationshipData.value?.data?.showingReblogs == true) {
|
||||
if (_relationshipData.value?.data?.showingReblogs == true) {
|
||||
changeRelationship(RelationShipAction.FOLLOW, false)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.FOLLOW, true)
|
||||
|
@ -195,9 +202,9 @@ class AccountViewModel @Inject constructor(
|
|||
parameter: Boolean? = null,
|
||||
duration: Int? = null
|
||||
) = viewModelScope.launch {
|
||||
val relation = relationshipData.value?.data
|
||||
val account = accountData.value?.data
|
||||
val isMastodon = relationshipData.value?.data?.notifying != null
|
||||
val relation = _relationshipData.value?.data
|
||||
val account = _accountData.value?.data
|
||||
val isMastodon = _relationshipData.value?.data?.notifying != null
|
||||
|
||||
if (relation != null && account != null) {
|
||||
// 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) {
|
||||
|
@ -265,7 +272,7 @@ class AccountViewModel @Inject constructor(
|
|||
|
||||
relationshipCall.fold(
|
||||
{ relationship ->
|
||||
relationshipData.postValue(Success(relationship))
|
||||
_relationshipData.value = Success(relationship)
|
||||
|
||||
when (relationshipAction) {
|
||||
RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId))
|
||||
|
@ -276,22 +283,22 @@ class AccountViewModel @Inject constructor(
|
|||
},
|
||||
{ t ->
|
||||
Log.w(TAG, "failed loading relationship", t)
|
||||
relationshipData.postValue(Error(relation, cause = t))
|
||||
_relationshipData.value = Error(relation, cause = t)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun noteChanged(newNote: String) {
|
||||
noteSaved.postValue(false)
|
||||
_noteSaved.value = false
|
||||
noteUpdateJob?.cancel()
|
||||
noteUpdateJob = viewModelScope.launch {
|
||||
delay(1500)
|
||||
mastodonApi.updateAccountNote(accountId, newNote)
|
||||
.fold(
|
||||
{
|
||||
noteSaved.postValue(true)
|
||||
_noteSaved.value = true
|
||||
delay(4000)
|
||||
noteSaved.postValue(false)
|
||||
_noteSaved.value = false
|
||||
},
|
||||
{ t ->
|
||||
Log.w(TAG, "Error updating note", t)
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.view.View
|
|||
import android.widget.PopupWindow
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
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.sizeDp
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AnnouncementsActivity :
|
||||
BottomSheetActivity(),
|
||||
|
@ -111,7 +113,9 @@ class AnnouncementsActivity :
|
|||
|
||||
binding.announcementsList.adapter = adapter
|
||||
|
||||
viewModel.announcements.observe(this) {
|
||||
lifecycleScope.launch {
|
||||
viewModel.announcements.collect {
|
||||
if (it == null) return@collect
|
||||
when (it) {
|
||||
is Success -> {
|
||||
binding.progressBar.hide()
|
||||
|
@ -143,9 +147,12 @@ class AnnouncementsActivity :
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.emojis.observe(this) {
|
||||
picker.adapter = EmojiAdapter(it, this, animateEmojis)
|
||||
lifecycleScope.launch {
|
||||
viewModel.emoji.collect {
|
||||
picker.adapter = EmojiAdapter(it, this@AnnouncementsActivity, animateEmojis)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.load()
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
package com.keylesspalace.tusky.components.announcements
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.Success
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AnnouncementsViewModel @Inject constructor(
|
||||
|
@ -40,25 +41,25 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
private val eventHub: EventHub
|
||||
) : ViewModel() {
|
||||
|
||||
private val announcementsMutable = MutableLiveData<Resource<List<Announcement>>>()
|
||||
val announcements: LiveData<Resource<List<Announcement>>> = announcementsMutable
|
||||
private val _announcements = MutableStateFlow(null as Resource<List<Announcement>>?)
|
||||
val announcements: StateFlow<Resource<List<Announcement>>?> = _announcements.asStateFlow()
|
||||
|
||||
private val emojisMutable = MutableLiveData<List<Emoji>>()
|
||||
val emojis: LiveData<List<Emoji>> = emojisMutable
|
||||
private val _emoji = MutableStateFlow(emptyList<Emoji>())
|
||||
val emoji: StateFlow<List<Emoji>> = _emoji.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
emojisMutable.postValue(instanceInfoRepo.getEmojis())
|
||||
_emoji.value = instanceInfoRepo.getEmojis()
|
||||
}
|
||||
}
|
||||
|
||||
fun load() {
|
||||
viewModelScope.launch {
|
||||
announcementsMutable.postValue(Loading())
|
||||
_announcements.value = Loading()
|
||||
mastodonApi.listAnnouncements()
|
||||
.fold(
|
||||
{
|
||||
announcementsMutable.postValue(Success(it))
|
||||
_announcements.value = Success(it)
|
||||
it.filter { announcement -> !announcement.read }
|
||||
.forEach { announcement ->
|
||||
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)
|
||||
.fold(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
_announcements.value =
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
announcements.value?.data?.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
announcement.copy(
|
||||
reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) {
|
||||
|
@ -109,7 +110,7 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
} else {
|
||||
listOf(
|
||||
*announcement.reactions.toTypedArray(),
|
||||
emojis.value!!.find { emoji -> emoji.shortcode == name }!!.run {
|
||||
emoji.value.find { emoji -> emoji.shortcode == name }!!.run {
|
||||
Announcement.Reaction(
|
||||
name,
|
||||
1,
|
||||
|
@ -126,7 +127,6 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to add reaction to the announcement.", it)
|
||||
|
@ -140,7 +140,7 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
mastodonApi.removeAnnouncementReaction(announcementId, name)
|
||||
.fold(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
_announcements.value =
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
if (announcement.id == announcementId) {
|
||||
|
@ -165,7 +165,6 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to remove reaction from the announcement.", it)
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
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.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
|
||||
|
@ -82,8 +84,9 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
viewModel.navigation.observe(this) { screen ->
|
||||
if (screen != null) {
|
||||
lifecycleScope.launch {
|
||||
viewModel.navigation.collect { screen ->
|
||||
if (screen == null) return@collect
|
||||
viewModel.navigated()
|
||||
when (screen) {
|
||||
Screen.Statuses -> showStatusesPage()
|
||||
|
@ -95,13 +98,15 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.checkUrl.observe(this) {
|
||||
lifecycleScope.launch {
|
||||
viewModel.checkUrl.collect {
|
||||
if (!it.isNullOrBlank()) {
|
||||
viewModel.urlChecked()
|
||||
viewUrl(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPreviousScreen() {
|
||||
when (binding.wizard.currentItem) {
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.report
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
|
@ -40,6 +38,9 @@ import com.keylesspalace.tusky.util.toViewData
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
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.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -49,20 +50,20 @@ class ReportViewModel @Inject constructor(
|
|||
private val eventHub: EventHub
|
||||
) : ViewModel() {
|
||||
|
||||
private val navigationMutable = MutableLiveData<Screen?>()
|
||||
val navigation: LiveData<Screen?> = navigationMutable
|
||||
private val navigationMutable = MutableStateFlow(null as Screen?)
|
||||
val navigation: StateFlow<Screen?> = navigationMutable.asStateFlow()
|
||||
|
||||
private val muteStateMutable = MutableLiveData<Resource<Boolean>>()
|
||||
val muteState: LiveData<Resource<Boolean>> = muteStateMutable
|
||||
private val muteStateMutable = MutableStateFlow(null as Resource<Boolean>?)
|
||||
val muteState: StateFlow<Resource<Boolean>?> = muteStateMutable.asStateFlow()
|
||||
|
||||
private val blockStateMutable = MutableLiveData<Resource<Boolean>>()
|
||||
val blockState: LiveData<Resource<Boolean>> = blockStateMutable
|
||||
private val blockStateMutable = MutableStateFlow(null as Resource<Boolean>?)
|
||||
val blockState: StateFlow<Resource<Boolean>?> = blockStateMutable.asStateFlow()
|
||||
|
||||
private val reportingStateMutable = MutableLiveData<Resource<Boolean>>()
|
||||
var reportingState: LiveData<Resource<Boolean>> = reportingStateMutable
|
||||
private val reportingStateMutable = MutableStateFlow(null as Resource<Boolean>?)
|
||||
var reportingState: StateFlow<Resource<Boolean>?> = reportingStateMutable.asStateFlow()
|
||||
|
||||
private val checkUrlMutable = MutableLiveData<String?>()
|
||||
val checkUrl: LiveData<String?> = checkUrlMutable
|
||||
private val checkUrlMutable = MutableStateFlow(null as String?)
|
||||
val checkUrl: StateFlow<String?> = checkUrlMutable.asStateFlow()
|
||||
|
||||
private val accountIdFlow = MutableSharedFlow<String>(
|
||||
replay = 1,
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
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.viewBinding
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||
|
||||
|
@ -47,7 +49,9 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
viewModel.muteState.observe(viewLifecycleOwner) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.muteState.collect {
|
||||
if (it == null) return@collect
|
||||
if (it !is Loading) {
|
||||
binding.buttonMute.show()
|
||||
binding.progressMute.show()
|
||||
|
@ -63,8 +67,11 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.blockState.observe(viewLifecycleOwner) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.blockState.collect {
|
||||
if (it == null) return@collect
|
||||
if (it !is Loading) {
|
||||
binding.buttonBlock.show()
|
||||
binding.progressBlock.show()
|
||||
|
@ -80,6 +87,7 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClicks() {
|
||||
binding.buttonDone.setOnClickListener {
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.view.View
|
|||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.report.ReportViewModel
|
||||
|
@ -35,6 +36,7 @@ import com.keylesspalace.tusky.util.show
|
|||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||
|
||||
|
@ -79,7 +81,9 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
viewModel.reportingState.observe(viewLifecycleOwner) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.reportingState.collect {
|
||||
if (it == null) return@collect
|
||||
when (it) {
|
||||
is Success -> viewModel.navigateTo(Screen.Done)
|
||||
is Loading -> showLoading()
|
||||
|
@ -87,6 +91,7 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError(error: Throwable?) {
|
||||
binding.editNote.isEnabled = true
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface DraftDao {
|
||||
|
@ -32,7 +32,7 @@ interface DraftDao {
|
|||
fun draftsPagingSource(accountId: Long): PagingSource<Int, DraftEntity>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM DraftEntity WHERE accountId = :accountId AND failedToSendNew = 1")
|
||||
fun draftsNeedUserAlert(accountId: Long): LiveData<Int>
|
||||
fun draftsNeedUserAlert(accountId: Long): Flow<Int>
|
||||
|
||||
@Query(
|
||||
"UPDATE DraftEntity SET failedToSendNew = 0 WHERE accountId = :accountId AND failedToSendNew = 1"
|
||||
|
|
|
@ -51,13 +51,13 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
|
|||
// Assume a single MainActivity, AccountActivity or DraftsActivity never sees more then one user id in its lifetime.
|
||||
val activeAccountId = activeAccount.id
|
||||
|
||||
// This LiveData will be automatically disposed when the activity is destroyed.
|
||||
val draftsNeedUserAlert = draftDao.draftsNeedUserAlert(activeAccountId)
|
||||
|
||||
// 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.
|
||||
coroutineScope.launch {
|
||||
if (showAlert) {
|
||||
draftsNeedUserAlert.observe(context) { count ->
|
||||
draftsNeedUserAlert.collect { count ->
|
||||
Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count")
|
||||
if (count > 0) {
|
||||
AlertDialog.Builder(context)
|
||||
|
@ -78,11 +78,12 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
draftsNeedUserAlert.observe(context) {
|
||||
draftsNeedUserAlert.collect {
|
||||
Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts")
|
||||
clearDraftsAlert(coroutineScope, activeAccountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
Log.w(TAG, "Attempted to observe drafts, but there is no active account")
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package com.keylesspalace.tusky.viewmodel
|
|||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
|
@ -40,6 +39,7 @@ import javax.inject.Inject
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
|
@ -66,10 +66,17 @@ class EditProfileViewModel @Inject constructor(
|
|||
private val instanceInfoRepo: InstanceInfoRepository
|
||||
) : ViewModel() {
|
||||
|
||||
val profileData = MutableLiveData<Resource<Account>>()
|
||||
val avatarData = MutableLiveData<Uri>()
|
||||
val headerData = MutableLiveData<Uri>()
|
||||
val saveData = MutableLiveData<Resource<Nothing>>()
|
||||
private val _profileData = MutableStateFlow(null as Resource<Account>?)
|
||||
val profileData: StateFlow<Resource<Account>?> = _profileData.asStateFlow()
|
||||
|
||||
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()
|
||||
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
@ -80,16 +87,16 @@ class EditProfileViewModel @Inject constructor(
|
|||
private var apiProfileAccount: Account? = null
|
||||
|
||||
fun obtainProfile() = viewModelScope.launch {
|
||||
if (profileData.value == null || profileData.value is Error) {
|
||||
profileData.postValue(Loading())
|
||||
if (_profileData.value == null || _profileData.value is Error) {
|
||||
_profileData.value = Loading()
|
||||
|
||||
mastodonApi.accountVerifyCredentials().fold(
|
||||
{ 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 newAvatarPicked() {
|
||||
avatarData.value = getAvatarUri()
|
||||
_avatarData.value = getAvatarUri()
|
||||
}
|
||||
|
||||
fun newHeaderPicked() {
|
||||
headerData.value = getHeaderUri()
|
||||
_headerData.value = getHeaderUri()
|
||||
}
|
||||
|
||||
internal fun dataChanged(newProfileData: ProfileDataInUi) {
|
||||
|
@ -112,16 +119,16 @@ class EditProfileViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
internal fun save(newProfileData: ProfileDataInUi) {
|
||||
if (saveData.value is Loading || profileData.value !is Success) {
|
||||
if (_saveData.value is Loading || _profileData.value !is Success) {
|
||||
return
|
||||
}
|
||||
|
||||
saveData.value = Loading()
|
||||
_saveData.value = Loading()
|
||||
|
||||
val diff = getProfileDiff(apiProfileAccount, newProfileData)
|
||||
if (!diff.hasChanges()) {
|
||||
// if nothing has changed, there is no need to make an api call
|
||||
saveData.value = Success()
|
||||
_saveData.value = Success()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -160,11 +167,11 @@ class EditProfileViewModel @Inject constructor(
|
|||
diff.field4?.second?.toRequestBody(MultipartBody.FORM)
|
||||
).fold(
|
||||
{ newAccountData ->
|
||||
saveData.postValue(Success())
|
||||
_saveData.value = Success()
|
||||
eventHub.dispatch(ProfileEditedEvent(newAccountData))
|
||||
},
|
||||
{ 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
|
||||
internal fun updateProfile(newProfileData: ProfileDataInUi) {
|
||||
if (profileData.value is Success) {
|
||||
val newProfileSource = profileData.value?.data?.source?.copy(
|
||||
if (_profileData.value is Success) {
|
||||
val newProfileSource = _profileData.value?.data?.source?.copy(
|
||||
note = newProfileData.note,
|
||||
fields = newProfileData.fields
|
||||
)
|
||||
val newProfile = profileData.value?.data?.copy(
|
||||
val newProfile = _profileData.value?.data?.copy(
|
||||
displayName = newProfileData.displayName,
|
||||
locked = newProfileData.locked,
|
||||
source = newProfileSource
|
||||
)
|
||||
|
||||
profileData.value = Success(newProfile)
|
||||
_profileData.value = Success(newProfile)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,13 +216,13 @@ class EditProfileViewModel @Inject constructor(
|
|||
newProfileData.locked
|
||||
}
|
||||
|
||||
val avatarFile = if (avatarData.value != null) {
|
||||
val avatarFile = if (_avatarData.value != null) {
|
||||
getCacheFileForName(AVATAR_FILE_NAME)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val headerFile = if (headerData.value != null) {
|
||||
val headerFile = if (_headerData.value != null) {
|
||||
getCacheFileForName(HEADER_FILE_NAME)
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -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-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-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-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
|
||||
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-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-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-core-splashscreen", "androidx-activity", "androidx-media3-exoplayer", "androidx-media3-datasource-okhttp",
|
||||
"androidx-media3-ui"]
|
||||
|
|
Loading…
Reference in a new issue