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:
parent
9805a985b2
commit
ce01e6de22
7 changed files with 65 additions and 56 deletions
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue