Add support for instance property `max_bio_chars` (#1414)

* Migrate getInstance from Call to Single

* Add support for instance max_bio_chars.
Addresses #1329
This commit is contained in:
Levi Bard 2019-08-04 20:25:07 +02:00 committed by Konrad Pozniak
parent 9805a985b2
commit ce01e6de22
7 changed files with 65 additions and 56 deletions

View File

@ -313,22 +313,9 @@ public final class ComposeActivity
getString(R.string.compose_active_account_description,
activeAccount.getFullName()));
mastodonApi.getInstance().enqueue(new Callback<Instance>() {
@Override
public void onResponse(@NonNull Call<Instance> call, @NonNull Response<Instance> response) {
if (response.isSuccessful() && response.body().getMaxTootChars() != null) {
maximumTootCharacters = response.body().getMaxTootChars();
updateVisibleCharactersLeft();
cacheInstanceMetadata(activeAccount);
}
}
@Override
public void onFailure(@NonNull Call<Instance> call, @NonNull Throwable t) {
Log.w(TAG, "error loading instance data", t);
loadCachedInstanceMetadata(activeAccount);
}
});
mastodonApi.getInstance()
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(this::onFetchInstanceSuccess, this::onFetchInstanceFailure);
mastodonApi.getCustomEmojis().enqueue(new Callback<List<Emoji>>() {
@Override
@ -1851,6 +1838,19 @@ public final class ComposeActivity
(mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.equals("text/plain")));
}
private void onFetchInstanceSuccess(Instance instance) {
if (instance != null && instance.getMaxTootChars() != null) {
maximumTootCharacters = instance.getMaxTootChars();
updateVisibleCharactersLeft();
cacheInstanceMetadata(accountManager.getActiveAccount());
}
}
private void onFetchInstanceFailure(Throwable throwable) {
Log.w(TAG, "error loading instance data", throwable);
loadCachedInstanceMetadata(accountManager.getActiveAccount());
}
public static final class QueuedMedia {
Type type;
ProgressImageView preview;

View File

@ -41,6 +41,7 @@ import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
import com.mikepenz.google_material_typeface_library.GoogleMaterial
@ -164,6 +165,18 @@ class EditProfileActivity : BaseActivity(), Injectable {
}
})
viewModel.obtainInstance()
viewModel.instanceData.observe(this, Observer<Resource<Instance>> { result ->
when (result) {
is Success -> {
val instance = result.data
if (instance?.maxBioChars != null && instance.maxBioChars > 0) {
noteEditTextLayout.counterMaxLength = instance.maxBioChars
}
}
}
})
observeImage(viewModel.avatarData, avatarPreview, avatarProgressBar, true)
observeImage(viewModel.headerData, headerPreview, headerProgressBar, false)

View File

@ -28,7 +28,8 @@ data class Instance (
val thumbnail: String?,
val languages: List<String>,
@SerializedName("contact_account") val contactAccount: Account,
@SerializedName("max_toot_chars") val maxTootChars: Int?
@SerializedName("max_toot_chars") val maxTootChars: Int?,
@SerializedName("max_bio_chars") val maxBioChars: Int?
) {
override fun hashCode(): Int {
return uri.hashCode()

View File

@ -358,7 +358,7 @@ public interface MastodonApi {
Call<List<Emoji>> getCustomEmojis();
@GET("api/v1/instance")
Call<Instance> getInstance();
Single<Instance> getInstance();
@GET("/api/v1/conversations")
Call<List<Conversation>> getConversations(@Nullable @Query("max_id") String maxId, @Query("limit") int limit);

View File

@ -27,6 +27,7 @@ import com.keylesspalace.tusky.EditProfileActivity.Companion.HEADER_WIDTH
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.StringField
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.*
@ -64,6 +65,7 @@ class EditProfileViewModel @Inject constructor(
val avatarData = MutableLiveData<Resource<Bitmap>>()
val headerData = MutableLiveData<Resource<Bitmap>>()
val saveData = MutableLiveData<Resource<Nothing>>()
val instanceData = MutableLiveData<Resource<Instance>>()
private var oldProfileData: Account? = null
@ -267,5 +269,20 @@ class EditProfileViewModel @Inject constructor(
disposeables.dispose()
}
fun obtainInstance() {
if(instanceData.value == null || instanceData.value is Error) {
instanceData.postValue(Loading())
mastodonApi.instance.subscribe(
{instance ->
instanceData.postValue(Success(instance))
},
{
instanceData.postValue(Error())
})
.addTo(disposeables)
}
}
}

View File

@ -111,6 +111,7 @@
<com.google.android.material.textfield.TextInputLayout
style="@style/TuskyTextInput"
android:id="@+id/noteEditTextLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"

View File

@ -39,6 +39,8 @@ import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.robolectric.Robolectric
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.reactivex.Single
import io.reactivex.SingleObserver
import org.robolectric.annotation.Config
import org.robolectric.fakes.RoboMenuItem
import retrofit2.Call
@ -77,7 +79,7 @@ class ComposeActivityTest {
notificationVibration = true,
notificationLight = true
)
var instanceResponseCallback: ((Call<Instance>?, Callback<Instance>?)->Unit)? = null
var instanceResponseCallback: (()->Instance)? = null
@Before
fun setupActivity() {
@ -109,28 +111,14 @@ class ComposeActivityTest {
override fun enqueue(callback: Callback<List<Emoji>>?) {}
})
`when`(apiMock.instance).thenReturn(object: Call<Instance> {
override fun isExecuted(): Boolean {
return false
}
override fun clone(): Call<Instance> {
throw Error("not implemented")
}
override fun isCanceled(): Boolean {
throw Error("not implemented")
}
override fun cancel() {
throw Error("not implemented")
}
override fun execute(): Response<Instance> {
throw Error("not implemented")
}
override fun request(): Request {
throw Error("not implemented")
}
override fun enqueue(callback: Callback<Instance>?) {
instanceResponseCallback?.invoke(this, callback)
`when`(apiMock.instance).thenReturn(object: Single<Instance>() {
override fun subscribeActual(observer: SingleObserver<in Instance>) {
val instance = instanceResponseCallback?.invoke()
if (instance == null) {
observer.onError(Throwable())
} else {
observer.onSuccess(instance)
}
}
})
@ -181,7 +169,7 @@ class ComposeActivityTest {
@Test
fun whenMaximumTootCharsIsNull_defaultLimitIsUsed() {
instanceResponseCallback = getSuccessResponseCallbackWithMaximumTootCharacters(null)
instanceResponseCallback = { getInstanceWithMaximumTootCharacters(null) }
setupActivity()
assertEquals(ComposeActivity.STATUS_CHARACTER_LIMIT, activity.maximumTootCharacters)
}
@ -189,23 +177,11 @@ class ComposeActivityTest {
@Test
fun whenMaximumTootCharsIsPopulated_customLimitIsUsed() {
val customMaximum = 1000
instanceResponseCallback = getSuccessResponseCallbackWithMaximumTootCharacters(customMaximum)
instanceResponseCallback = { getInstanceWithMaximumTootCharacters(customMaximum) }
setupActivity()
assertEquals(customMaximum, activity.maximumTootCharacters)
}
@Test
fun whenInitialInstanceRequestFails_defaultValueIsUsed() {
instanceResponseCallback = {
call: Call<Instance>?, callback: Callback<Instance>? ->
if (call != null) {
callback?.onResponse(call, Response.error(400, ResponseBody.create(null, "")))
}
}
setupActivity()
assertEquals(ComposeActivity.STATUS_CHARACTER_LIMIT, activity.maximumTootCharacters)
}
@Test
fun whenTextContainsNoUrl_everyCharacterIsCounted() {
val content = "This is test content please ignore thx "
@ -281,7 +257,8 @@ class ComposeActivityTest {
emptyList(),
emptyList()
),
maximumTootCharacters
maximumTootCharacters,
null
)
}