Personal account notes (#1978)

* add personal notes to AccountActivity

* use RxJava instead of plain okhttp calls

* make AccountViewModel rx aware

* hide note input until data is loaded
This commit is contained in:
Konrad Pozniak 2020-11-17 20:10:54 +01:00 committed by GitHub
parent 56219ddcc7
commit ce973ea7e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 213 deletions

View File

@ -23,6 +23,7 @@ import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -131,6 +132,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
if (viewModel.isSelf) { if (viewModel.isSelf) {
updateButtons() updateButtons()
saveNoteInfo.hide()
} else {
saveNoteInfo.visibility = View.INVISIBLE
} }
} }
@ -336,8 +340,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
viewModel.accountFieldData.observe(this, { viewModel.accountFieldData.observe(this, {
accountFieldAdapter.fields = it accountFieldAdapter.fields = it
accountFieldAdapter.notifyDataSetChanged() accountFieldAdapter.notifyDataSetChanged()
}) })
viewModel.noteSaved.observe(this) {
saveNoteInfo.visible(it, View.INVISIBLE)
}
} }
/** /**
@ -532,9 +538,22 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
accountFollowsYouTextView.visible(relation.followedBy) accountFollowsYouTextView.visible(relation.followedBy)
accountNoteTextInputLayout.visible(relation.note != null)
accountNoteTextInputLayout.editText?.setText(relation.note)
// add the listener late to avoid it firing on the first change
accountNoteTextInputLayout.editText?.removeTextChangedListener(noteWatcher)
accountNoteTextInputLayout.editText?.addTextChangedListener(noteWatcher)
updateButtons() updateButtons()
} }
private val noteWatcher = object: DefaultTextWatcher() {
override fun afterTextChanged(s: Editable) {
viewModel.noteChanged(s.toString())
}
}
private fun updateFollowButton() { private fun updateFollowButton() {
if (viewModel.isSelf) { if (viewModel.isSelf) {
accountFollowButton.setText(R.string.action_edit_own_profile) accountFollowButton.setText(R.string.action_edit_own_profile)

View File

@ -100,7 +100,7 @@ class ReportViewModel @Inject constructor(
val ids = listOf(accountId) val ids = listOf(accountId)
muteStateMutable.value = Loading() muteStateMutable.value = Loading()
blockStateMutable.value = Loading() blockStateMutable.value = Loading()
mastodonApi.relationshipsObservable(ids) mastodonApi.relationships(ids)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
@ -129,9 +129,9 @@ class ReportViewModel @Inject constructor(
fun toggleMute() { fun toggleMute() {
val alreadyMuted = muteStateMutable.value?.data == true val alreadyMuted = muteStateMutable.value?.data == true
if (alreadyMuted) { if (alreadyMuted) {
mastodonApi.unmuteAccountObservable(accountId) mastodonApi.unmuteAccount(accountId)
} else { } else {
mastodonApi.muteAccountObservable(accountId) mastodonApi.muteAccount(accountId)
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -154,9 +154,9 @@ class ReportViewModel @Inject constructor(
fun toggleBlock() { fun toggleBlock() {
val alreadyBlocked = blockStateMutable.value?.data == true val alreadyBlocked = blockStateMutable.value?.data == true
if (alreadyBlocked) { if (alreadyBlocked) {
mastodonApi.unblockAccountObservable(accountId) mastodonApi.unblockAccount(accountId)
} else { } else {
mastodonApi.blockAccountObservable(accountId) mastodonApi.blockAccount(accountId)
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())

View File

@ -26,5 +26,6 @@ data class Relationship (
@SerializedName("muting_notifications") val mutingNotifications: Boolean, @SerializedName("muting_notifications") val mutingNotifications: Boolean,
val requested: Boolean, val requested: Boolean,
@SerializedName("showing_reblogs") val showingReblogs: Boolean, @SerializedName("showing_reblogs") val showingReblogs: Boolean,
@SerializedName("domain_blocking") val blockingDomain: Boolean @SerializedName("domain_blocking") val blockingDomain: Boolean,
val note: String? // nullable for backward compatibility / feature detection
) )

View File

@ -52,7 +52,6 @@ import java.io.IOException
import java.util.HashMap import java.util.HashMap
import javax.inject.Inject import javax.inject.Inject
class AccountListFragment : BaseFragment(), AccountActionListener, Injectable { class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
@Inject @Inject
@ -116,27 +115,17 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
} }
override fun onMute(mute: Boolean, id: String, position: Int, notifications: Boolean) { override fun onMute(mute: Boolean, id: String, position: Int, notifications: Boolean) {
val callback = object : Callback<Relationship> { if (!mute) {
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
if (response.isSuccessful) {
onMuteSuccess(mute, id, position, notifications)
} else {
onMuteFailure(mute, id, notifications)
}
}
override fun onFailure(call: Call<Relationship>, t: Throwable) {
onMuteFailure(mute, id, notifications)
}
}
val call = if (!mute) {
api.unmuteAccount(id) api.unmuteAccount(id)
} else { } else {
api.muteAccount(id, notifications) api.muteAccount(id, notifications)
} }
callList.add(call) .autoDispose(from(this))
call.enqueue(callback) .subscribe({
onMuteSuccess(mute, id, position, notifications)
}, {
onMuteFailure(mute, id, notifications)
})
} }
private fun onMuteSuccess(muted: Boolean, id: String, position: Int, notifications: Boolean) { private fun onMuteSuccess(muted: Boolean, id: String, position: Int, notifications: Boolean) {
@ -171,27 +160,17 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
} }
override fun onBlock(block: Boolean, id: String, position: Int) { override fun onBlock(block: Boolean, id: String, position: Int) {
val cb = object : Callback<Relationship> { if (!block) {
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
if (response.isSuccessful) {
onBlockSuccess(block, id, position)
} else {
onBlockFailure(block, id)
}
}
override fun onFailure(call: Call<Relationship>, t: Throwable) {
onBlockFailure(block, id)
}
}
val call = if (!block) {
api.unblockAccount(id) api.unblockAccount(id)
} else { } else {
api.blockAccount(id) api.blockAccount(id)
} }
callList.add(call) .autoDispose(from(this))
call.enqueue(cb) .subscribe({
onBlockSuccess(block, id, position)
}, {
onBlockFailure(block, id)
})
} }
private fun onBlockSuccess(blocked: Boolean, id: String, position: Int) { private fun onBlockSuccess(blocked: Boolean, id: String, position: Int) {
@ -350,29 +329,16 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
} }
private fun fetchRelationships(ids: List<String>) { private fun fetchRelationships(ids: List<String>) {
val callback = object : Callback<List<Relationship>> { api.relationships(ids)
override fun onResponse(call: Call<List<Relationship>>, response: Response<List<Relationship>>) { .autoDispose(from(this))
val body = response.body() .subscribe(::onFetchRelationshipsSuccess) {
if (response.isSuccessful && body != null) {
onFetchRelationshipsSuccess(body)
} else {
onFetchRelationshipsFailure(ids) onFetchRelationshipsFailure(ids)
} }
}
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
onFetchRelationshipsFailure(ids)
}
}
val call = api.relationships(ids)
callList.add(call)
call.enqueue(callback)
} }
private fun onFetchRelationshipsSuccess(relationships: List<Relationship>) { private fun onFetchRelationshipsSuccess(relationships: List<Relationship>) {
val mutesAdapter = adapter as MutesAdapter val mutesAdapter = adapter as MutesAdapter
var mutingNotificationsMap = HashMap<String, Boolean>() val mutingNotificationsMap = HashMap<String, Boolean>()
relationships.map { mutingNotificationsMap.put(it.id, it.mutingNotifications) } relationships.map { mutingNotificationsMap.put(it.id, it.mutingNotifications) }
mutesAdapter.updateMutingNotificationsMap(mutingNotificationsMap) mutesAdapter.updateMutingNotificationsMap(mutingNotificationsMap)
} }

View File

@ -269,7 +269,7 @@ interface MastodonApi {
@GET("api/v1/accounts/{id}") @GET("api/v1/accounts/{id}")
fun account( fun account(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Account> ): Single<Account>
/** /**
* Method to fetch statuses for the specified account. * Method to fetch statuses for the specified account.
@ -308,44 +308,44 @@ interface MastodonApi {
fun followAccount( fun followAccount(
@Path("id") accountId: String, @Path("id") accountId: String,
@Field("reblogs") showReblogs: Boolean @Field("reblogs") showReblogs: Boolean
): Call<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unfollow") @POST("api/v1/accounts/{id}/unfollow")
fun unfollowAccount( fun unfollowAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/block") @POST("api/v1/accounts/{id}/block")
fun blockAccount( fun blockAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unblock") @POST("api/v1/accounts/{id}/unblock")
fun unblockAccount( fun unblockAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Single<Relationship>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/accounts/{id}/mute") @POST("api/v1/accounts/{id}/mute")
fun muteAccount( fun muteAccount(
@Path("id") accountId: String, @Path("id") accountId: String,
@Field("notifications") notifications: Boolean @Field("notifications") notifications: Boolean? = null
): Call<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unmute") @POST("api/v1/accounts/{id}/unmute")
fun unmuteAccount( fun unmuteAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Single<Relationship>
@GET("api/v1/accounts/relationships") @GET("api/v1/accounts/relationships")
fun relationships( fun relationships(
@Query("id[]") accountIds: List<String> @Query("id[]") accountIds: List<String>
): Call<List<Relationship>> ): Single<List<Relationship>>
@GET("api/v1/accounts/{id}/identity_proofs") @GET("api/v1/accounts/{id}/identity_proofs")
fun identityProofs( fun identityProofs(
@Path("id") accountId: String @Path("id") accountId: String
): Call<List<IdentityProof>> ): Single<List<IdentityProof>>
@GET("api/v1/blocks") @GET("api/v1/blocks")
fun blocks( fun blocks(
@ -513,31 +513,6 @@ interface MastodonApi {
@Field("choices[]") choices: List<Int> @Field("choices[]") choices: List<Int>
): Single<Poll> ): Single<Poll>
@POST("api/v1/accounts/{id}/block")
fun blockAccountObservable(
@Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/accounts/{id}/unblock")
fun unblockAccountObservable(
@Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/accounts/{id}/mute")
fun muteAccountObservable(
@Path("id") accountId: String
): Single<Relationship>
@POST("api/v1/accounts/{id}/unmute")
fun unmuteAccountObservable(
@Path("id") accountId: String
): Single<Relationship>
@GET("api/v1/accounts/relationships")
fun relationshipsObservable(
@Query("id[]") accountIds: List<String>
): Single<List<Relationship>>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/reports") @POST("api/v1/reports")
fun reportObservable( fun reportObservable(
@ -571,4 +546,11 @@ interface MastodonApi {
@Query("following") following: Boolean? = null @Query("following") following: Boolean? = null
): Single<SearchResult> ): Single<SearchResult>
@FormUrlEncoded
@POST("api/v1/accounts/{id}/note")
fun updateAccountNote(
@Path("id") accountId: String,
@Field("comment") note: String
): Single<Relationship>
} }

View File

@ -15,17 +15,14 @@
package com.keylesspalace.tusky.network package com.keylesspalace.tusky.network
import android.util.Log
import com.keylesspalace.tusky.appstore.* import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.entity.DeletedStatus import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.Status
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.addTo
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.lang.IllegalStateException import java.lang.IllegalStateException
/** /**
@ -108,24 +105,23 @@ class TimelineCasesImpl(
} }
override fun mute(id: String, notifications: Boolean) { override fun mute(id: String, notifications: Boolean) {
val call = mastodonApi.muteAccount(id, notifications) mastodonApi.muteAccount(id, notifications)
call.enqueue(object : Callback<Relationship> { .subscribe({
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {} eventHub.dispatch(MuteEvent(id))
}, { t ->
override fun onFailure(call: Call<Relationship>, t: Throwable) {} Log.w("Failed to mute account", t)
}) })
eventHub.dispatch(MuteEvent(id)) .addTo(cancelDisposable)
} }
override fun block(id: String) { override fun block(id: String) {
val call = mastodonApi.blockAccount(id) mastodonApi.blockAccount(id)
call.enqueue(object : Callback<Relationship> { .subscribe({
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {} eventHub.dispatch(BlockEvent(id))
}, { t ->
override fun onFailure(call: Call<Relationship>, t: Throwable) {} Log.w("Failed to block account", t)
}) })
eventHub.dispatch(BlockEvent(id)) .addTo(cancelDisposable)
} }
override fun delete(id: String): Single<DeletedStatus> { override fun delete(id: String): Single<DeletedStatus> {

View File

@ -2,7 +2,6 @@ package com.keylesspalace.tusky.viewmodel
import android.util.Log import android.util.Log
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.keylesspalace.tusky.appstore.* import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
@ -11,21 +10,25 @@ import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.entity.Relationship import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.util.*
import io.reactivex.Single
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class AccountViewModel @Inject constructor( class AccountViewModel @Inject constructor(
private val mastodonApi: MastodonApi, private val mastodonApi: MastodonApi,
private val eventHub: EventHub, private val eventHub: EventHub,
private val accountManager: AccountManager private val accountManager: AccountManager
) : ViewModel() { ) : RxAwareViewModel() {
val accountData = MutableLiveData<Resource<Account>>() val accountData = MutableLiveData<Resource<Account>>()
val relationshipData = MutableLiveData<Resource<Relationship>>() val relationshipData = MutableLiveData<Resource<Relationship>>()
val noteSaved = MutableLiveData<Boolean>()
private val identityProofData = MutableLiveData<List<IdentityProof>>() private val identityProofData = MutableLiveData<List<IdentityProof>>()
val accountFieldData = combineOptionalLiveData(accountData, identityProofData) { accountRes, identityProofs -> val accountFieldData = combineOptionalLiveData(accountData, identityProofData) { accountRes, identityProofs ->
@ -33,47 +36,40 @@ class AccountViewModel @Inject constructor(
.plus(accountRes?.data?.fields.orEmpty().map { Either.Right(it) }) .plus(accountRes?.data?.fields.orEmpty().map { Either.Right(it) })
} }
private val callList: MutableList<Call<*>> = mutableListOf()
private val disposable: Disposable = eventHub.events
.subscribe { event ->
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) {
accountData.postValue(Success(event.newProfileData))
}
}
val isRefreshing = MutableLiveData<Boolean>().apply { value = false } val isRefreshing = MutableLiveData<Boolean>().apply { value = false }
private var isDataLoading = false private var isDataLoading = false
lateinit var accountId: String lateinit var accountId: String
var isSelf = false var isSelf = false
private var noteDisposable: Disposable? = null
init {
eventHub.events
.subscribe { event ->
if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) {
accountData.postValue(Success(event.newProfileData))
}
}.autoDispose()
}
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.postValue(Loading())
val call = mastodonApi.account(accountId) mastodonApi.account(accountId)
call.enqueue(object : Callback<Account> { .subscribe({ account ->
override fun onResponse(call: Call<Account>, accountData.postValue(Success(account))
response: Response<Account>) { isDataLoading = false
if (response.isSuccessful) { isRefreshing.postValue(false)
accountData.postValue(Success(response.body())) }, {t ->
} else { Log.w(TAG, "failed obtaining account", t)
accountData.postValue(Error()) accountData.postValue(Error())
} isDataLoading = false
isDataLoading = false isRefreshing.postValue(false)
isRefreshing.postValue(false) })
} .autoDispose()
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.w(TAG, "failed obtaining account", t)
accountData.postValue(Error())
isDataLoading = false
isRefreshing.postValue(false)
}
})
callList.add(call)
} }
} }
@ -82,51 +78,27 @@ class AccountViewModel @Inject constructor(
relationshipData.postValue(Loading()) relationshipData.postValue(Loading())
val ids = listOf(accountId) mastodonApi.relationships(listOf(accountId))
val call = mastodonApi.relationships(ids) .subscribe({ relationships ->
call.enqueue(object : Callback<List<Relationship>> { relationshipData.postValue(Success(relationships[0]))
override fun onResponse(call: Call<List<Relationship>>, }, { t ->
response: Response<List<Relationship>>) { Log.w(TAG, "failed obtaining relationships", t)
val relationships = response.body()
if (response.isSuccessful && relationships != null && relationships.getOrNull(0) != null) {
val relationship = relationships[0]
relationshipData.postValue(Success(relationship))
} else {
relationshipData.postValue(Error()) relationshipData.postValue(Error())
} })
} .autoDispose()
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
Log.w(TAG, "failed obtaining relationships", t)
relationshipData.postValue(Error())
}
})
callList.add(call)
} }
} }
private fun obtainIdentityProof(reload: Boolean = false) { private fun obtainIdentityProof(reload: Boolean = false) {
if (identityProofData.value == null || reload) { if (identityProofData.value == null || reload) {
val call = mastodonApi.identityProofs(accountId) mastodonApi.identityProofs(accountId)
call.enqueue(object : Callback<List<IdentityProof>> { .subscribe({ proofs ->
override fun onResponse(call: Call<List<IdentityProof>>,
response: Response<List<IdentityProof>>) {
val proofs = response.body()
if (response.isSuccessful && proofs != null ) {
identityProofData.postValue(proofs) identityProofData.postValue(proofs)
} else { }, { t ->
identityProofData.postValue(emptyList()) Log.w(TAG, "failed obtaining identity proofs", t)
} })
} .autoDispose()
override fun onFailure(call: Call<List<IdentityProof>>, t: Throwable) {
Log.w(TAG, "failed obtaining identity proofs", t)
}
})
callList.add(call)
} }
} }
@ -229,11 +201,15 @@ class AccountViewModel @Inject constructor(
relationshipData.postValue(Loading(newRelation)) relationshipData.postValue(Loading(newRelation))
} }
val callback = object : Callback<Relationship> { when (relationshipAction) {
override fun onResponse(call: Call<Relationship>, RelationShipAction.FOLLOW -> mastodonApi.followAccount(accountId, parameter ?: true)
response: Response<Relationship>) { RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId)
val relationship = response.body() RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId)
if (response.isSuccessful && relationship != null) { RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId)
RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true)
RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId)
}.subscribe(
{ relationship ->
relationshipData.postValue(Success(relationship)) relationshipData.postValue(Success(relationship))
when (relationshipAction) { when (relationshipAction) {
@ -243,37 +219,35 @@ class AccountViewModel @Inject constructor(
else -> { else -> {
} }
} }
},
} else { {
relationshipData.postValue(Error(relation)) relationshipData.postValue(Error(relation))
} }
)
.autoDispose()
}
} fun noteChanged(newNote: String) {
noteSaved.postValue(false)
override fun onFailure(call: Call<Relationship>, t: Throwable) { noteDisposable?.dispose()
relationshipData.postValue(Error(relation)) noteDisposable = Single.timer(1500, TimeUnit.MILLISECONDS)
} .flatMap {
} mastodonApi.updateAccountNote(accountId, newNote)
}
val call = when (relationshipAction) { .doOnSuccess {
RelationShipAction.FOLLOW -> mastodonApi.followAccount(accountId, parameter ?: true) noteSaved.postValue(true)
RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId) }
RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId) .delay(4, TimeUnit.SECONDS)
RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId) .subscribe({
RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true) noteSaved.postValue(false)
RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId) }, {
} Log.e(TAG, "Error updating note", it)
})
call.enqueue(callback)
callList.add(call)
} }
override fun onCleared() { override fun onCleared() {
callList.forEach { super.onCleared()
it.cancel() noteDisposable?.dispose()
}
disposable.dispose()
} }
fun refresh() { fun refresh() {

View File

@ -168,16 +168,43 @@
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="accountFollowsYouTextView,accountBadgeTextView" /> app:constraint_referenced_ids="accountFollowsYouTextView,accountBadgeTextView" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/accountNoteTextInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/labelBarrier"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/account_note_hint" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/saveNoteInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_note_saved"
android:textColor="@color/tusky_blue"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accountNoteTextInputLayout" />
<androidx.emoji.widget.EmojiTextView <androidx.emoji.widget.EmojiTextView
android:id="@+id/accountNoteTextView" android:id="@+id/accountNoteTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hyphenationFrequency="full" android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
android:paddingTop="10dp" android:paddingTop="2dp"
android:textColor="?android:textColorTertiary" android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium" android:textSize="?attr/status_text_medium"
app:layout_constraintTop_toBottomOf="@id/labelBarrier" app:layout_constraintTop_toBottomOf="@id/saveNoteInfo"
tools:text="This is a test description. Descriptions can be quite looooong." /> tools:text="This is a test description. Descriptions can be quite looooong." />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -244,6 +271,7 @@
android:text="@string/title_statuses" android:text="@string/title_statuses"
android:textColor="@color/account_tab_font_color" android:textColor="@color/account_tab_font_color"
android:textSize="?attr/status_text_medium" /> android:textSize="?attr/status_text_medium" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@ -573,5 +573,7 @@
<string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string> <string name="pref_title_show_cards_in_timelines">Show link previews in timelines</string>
<string name="pref_title_confirm_reblogs">Show confirmation dialog before boosting</string> <string name="pref_title_confirm_reblogs">Show confirmation dialog before boosting</string>
<string name="pref_title_hide_top_toolbar">Hide the title of the top toolbar</string> <string name="pref_title_hide_top_toolbar">Hide the title of the top toolbar</string>
<string name="account_note_hint">Your private note about this account</string>
<string name="account_note_saved">Saved!</string>
</resources> </resources>