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, getString(R.string.compose_active_account_description,
activeAccount.getFullName())); activeAccount.getFullName()));
mastodonApi.getInstance().enqueue(new Callback<Instance>() { mastodonApi.getInstance()
@Override .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
public void onResponse(@NonNull Call<Instance> call, @NonNull Response<Instance> response) { .subscribe(this::onFetchInstanceSuccess, this::onFetchInstanceFailure);
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.getCustomEmojis().enqueue(new Callback<List<Emoji>>() { mastodonApi.getCustomEmojis().enqueue(new Callback<List<Emoji>>() {
@Override @Override
@ -1851,6 +1838,19 @@ public final class ComposeActivity
(mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.equals("text/plain"))); (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 { public static final class QueuedMedia {
Type type; Type type;
ProgressImageView preview; 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.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
import com.mikepenz.google_material_typeface_library.GoogleMaterial 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.avatarData, avatarPreview, avatarProgressBar, true)
observeImage(viewModel.headerData, headerPreview, headerProgressBar, false) observeImage(viewModel.headerData, headerPreview, headerProgressBar, false)

View file

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

View file

@ -358,7 +358,7 @@ public interface MastodonApi {
Call<List<Emoji>> getCustomEmojis(); Call<List<Emoji>> getCustomEmojis();
@GET("api/v1/instance") @GET("api/v1/instance")
Call<Instance> getInstance(); Single<Instance> getInstance();
@GET("/api/v1/conversations") @GET("/api/v1/conversations")
Call<List<Conversation>> getConversations(@Nullable @Query("max_id") String maxId, @Query("limit") int limit); 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.EventHub
import com.keylesspalace.tusky.appstore.ProfileEditedEvent import com.keylesspalace.tusky.appstore.ProfileEditedEvent
import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.StringField import com.keylesspalace.tusky.entity.StringField
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.* import com.keylesspalace.tusky.util.*
@ -64,6 +65,7 @@ class EditProfileViewModel @Inject constructor(
val avatarData = MutableLiveData<Resource<Bitmap>>() val avatarData = MutableLiveData<Resource<Bitmap>>()
val headerData = MutableLiveData<Resource<Bitmap>>() val headerData = MutableLiveData<Resource<Bitmap>>()
val saveData = MutableLiveData<Resource<Nothing>>() val saveData = MutableLiveData<Resource<Nothing>>()
val instanceData = MutableLiveData<Resource<Instance>>()
private var oldProfileData: Account? = null private var oldProfileData: Account? = null
@ -267,5 +269,20 @@ class EditProfileViewModel @Inject constructor(
disposeables.dispose() 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 <com.google.android.material.textfield.TextInputLayout
style="@style/TuskyTextInput" style="@style/TuskyTextInput"
android:id="@+id/noteEditTextLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"

View file

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