Repository: tuskyapp/Tusky (language: kotlin)

Issue:
[friendica] Post length limited to 500 in recent Friendica RC
Using Tusky Test 23.0-e9195846 with Friendica 2023.09-rc on loma.ml, I noticed that all of a sudden I can't compose posts longer than 500 chars anymore. Current Tusky stable does however display the "correct" amount of chars allowed per post (around 20k on that instance). Any way to fix this?


Discussion hints:
My instinct is that it's a regression caused by [instance v2 support](https://github.com/tuskyapp/Tusky/pull/4062), in particular I notice that https://loma.ml/api/v2/instance fails to return a number of fields in `configuration` that I understood to be "mandatory" from the mastodon api docs

I'll investigate

Pull request: Fix deserialization of the response from friendica on api/v2/instance
Fixes #4100

Test command: ./gradlew :app:testBlueDebugUnitTest --tests "com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeActivityTest"

Fix patch:
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
index 4031181ed2..c781ff90e3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/instanceinfo/InstanceInfoRepository.kt
@@ -64,17 +64,17 @@ class InstanceInfoRepository @Inject constructor(
                 { instance ->
                     val instanceEntity = InstanceInfoEntity(
                         instance = instanceName,
-                        maximumTootCharacters = instance.configuration.statuses.maxCharacters,
-                        maxPollOptions = instance.configuration.polls.maxOptions,
-                        maxPollOptionLength = instance.configuration.polls.maxCharactersPerOption,
-                        minPollDuration = instance.configuration.polls.minExpirationSeconds,
-                        maxPollDuration = instance.configuration.polls.maxExpirationSeconds,
-                        charactersReservedPerUrl = instance.configuration.statuses.charactersReservedPerUrl,
+                        maximumTootCharacters = instance.configuration.statuses?.maxCharacters ?: DEFAULT_CHARACTER_LIMIT,
+                        maxPollOptions = instance.configuration.polls?.maxOptions ?: DEFAULT_MAX_OPTION_COUNT,
+                        maxPollOptionLength = instance.configuration.polls?.maxCharactersPerOption ?: DEFAULT_MAX_OPTION_LENGTH,
+                        minPollDuration = instance.configuration.polls?.minExpirationSeconds ?: DEFAULT_MIN_POLL_DURATION,
+                        maxPollDuration = instance.configuration.polls?.maxExpirationSeconds ?: DEFAULT_MAX_POLL_DURATION,
+                        charactersReservedPerUrl = instance.configuration.statuses?.charactersReservedPerUrl ?: DEFAULT_CHARACTERS_RESERVED_PER_URL,
                         version = instance.version,
-                        videoSizeLimit = instance.configuration.mediaAttachments.videoSizeLimitBytes.toInt(),
-                        imageSizeLimit = instance.configuration.mediaAttachments.imageSizeLimitBytes.toInt(),
-                        imageMatrixLimit = instance.configuration.mediaAttachments.imagePixelCountLimit.toInt(),
-                        maxMediaAttachments = instance.configuration.statuses.maxMediaAttachments,
+                        videoSizeLimit = instance.configuration.mediaAttachments?.videoSizeLimitBytes?.toInt() ?: DEFAULT_VIDEO_SIZE_LIMIT,
+                        imageSizeLimit = instance.configuration.mediaAttachments?.imageSizeLimitBytes?.toInt() ?: DEFAULT_IMAGE_SIZE_LIMIT,
+                        imageMatrixLimit = instance.configuration.mediaAttachments?.imagePixelCountLimit?.toInt() ?: DEFAULT_IMAGE_MATRIX_LIMIT,
+                        maxMediaAttachments = instance.configuration.statuses?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS,
                         maxFields = instance.pleroma?.metadata?.fieldLimits?.maxFields,
                         maxFieldNameLength = instance.pleroma?.metadata?.fieldLimits?.nameLength,
                         maxFieldValueLength = instance.pleroma?.metadata?.fieldLimits?.valueLength,
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
index bf5aa2804d..048367535e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
@@ -31,12 +31,12 @@ data class Instance(
         )
     }
     data class Configuration(
-        val urls: Urls,
-        val accounts: Accounts,
-        val statuses: Statuses,
-        @SerializedName("media_attachments") val mediaAttachments: MediaAttachments,
-        val polls: Polls,
-        val translation: Translation,
+        val urls: Urls?,
+        val accounts: Accounts?,
+        val statuses: Statuses?,
+        @SerializedName("media_attachments") val mediaAttachments: MediaAttachments?,
+        val polls: Polls?,
+        val translation: Translation?,
     ) {
         data class Urls(@SerializedName("streaming_api") val streamingApi: String)
         data class Accounts(@SerializedName("max_featured_tags") val maxFeaturedTags: Int)
@@ -46,7 +46,8 @@ data class Instance(
             @SerializedName("characters_reserved_per_url") val charactersReservedPerUrl: Int,
         )
         data class MediaAttachments(
-            @SerializedName("supported_mime_types") val supportedMimeTypes: List<String>,
+            // Warning: This is an array in mastodon and a dictionary in friendica
+            // @SerializedName("supported_mime_types") val supportedMimeTypes: List<String>,
             @SerializedName("image_size_limit") val imageSizeLimitBytes: Long,
             @SerializedName("image_matrix_limit") val imagePixelCountLimit: Long,
             @SerializedName("video_size_limit") val videoSizeLimitBytes: Long,


Test patch:
diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivity/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivity/ComposeActivityTest.kt
index ecff6d3be5..451b5c09a0 100644
--- a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivity/ComposeActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivity/ComposeActivityTest.kt
@@ -22,6 +22,7 @@ import android.os.Looper.getMainLooper
 import android.widget.EditText
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import at.connyduck.calladapter.networkresult.NetworkResult
+import com.google.gson.Gson
 import com.keylesspalace.tusky.R
 import com.keylesspalace.tusky.components.compose.ComposeActivity
 import com.keylesspalace.tusky.components.compose.ComposeViewModel
@@ -95,6 +96,7 @@ class ComposeActivityTest {
     private var instanceV1ResponseCallback: (() -> InstanceV1)? = null
     private var instanceResponseCallback: (() -> Instance)? = null
     private var composeOptions: ComposeActivity.ComposeOptions? = null
+    private val gson = Gson()
 
     @Before
     fun setupActivity() {
@@ -528,6 +530,15 @@ class ComposeActivityTest {
         assertEquals(language, activity.selectedLanguage)
     }
 
+    @Test
+    fun sampleFriendicaInstanceResponseIsDeserializable() {
+        // https://github.com/tuskyapp/Tusky/issues/4100
+        instanceResponseCallback = { getSampleFriendicaInstance() }
+        setupActivity()
+        shadowOf(getMainLooper()).idle()
+        assertEquals(friendicaMaximum, activity.maximumTootCharacters)
+    }
+
     private fun clickUp() {
         val menuItem = RoboMenuItem(android.R.id.home)
         activity.onOptionsItemSelected(menuItem)
@@ -560,7 +571,7 @@ class ComposeActivityTest {
                 InstanceInfoRepository.DEFAULT_MAX_MEDIA_ATTACHMENTS,
                 charactersReservedPerUrl ?: InstanceInfoRepository.DEFAULT_CHARACTERS_RESERVED_PER_URL
             ),
-            Instance.Configuration.MediaAttachments(emptyList(), 0, 0, 0, 0, 0),
+            Instance.Configuration.MediaAttachments(0, 0, 0, 0, 0),
             Instance.Configuration.Polls(0, 0, 0, 0),
             Instance.Configuration.Translation(false),
         )
@@ -591,4 +602,83 @@ class ComposeActivityTest {
             polls = null
         )
     }
+
+    private fun getSampleFriendicaInstance(): Instance {
+        return gson.fromJson(sampleFriendicaResponse, Instance::class.java)
+    }
+
+    companion object {
+        private const val friendicaMaximum = 200000
+
+        // https://github.com/tuskyapp/Tusky/issues/4100
+        private val sampleFriendicaResponse = """{
+                "domain": "loma.ml",
+                "title": "[ˈloma]",
+                "version": "2.8.0 (compatible; Friendica 2023.09-rc)",
+                "source_url": "https://git.friendi.ca/friendica/friendica",
+                "description": "loma.ml ist eine Friendica Community im Fediverse auf der vorwiegend DE \uD83C\uDDE9\uD83C\uDDEA gesprochen wird. \\r\\nServer in Germany/EU \uD83C\uDDE9\uD83C\uDDEA \uD83C\uDDEA\uD83C\uDDFA. Open to all with fun in new. \\r\\nServer in Deutschland. Offen für alle mit Spaß an Neuen.",
+                "usage": {
+                    "users": {
+                        "active_month": 125
+                    }
+                },
+                "thumbnail": {
+                    "url": "https://loma.ml/ad/friendica-banner.jpg"
+                },
+                "languages": [
+                    "de"
+                ],
+                "configuration": {
+                    "statuses": {
+                        "max_characters": $friendicaMaximum
+                    },
+                    "media_attachments": {
+                        "supported_mime_types": {
+                            "image/jpeg": "jpg",
+                            "image/jpg": "jpg",
+                            "image/png": "png",
+                            "image/gif": "gif"
+                        },
+                        "image_size_limit": 10485760
+                    }
+                },
+                "registrations": {
+                    "enabled": true,
+                    "approval_required": false
+                },
+                "contact": {
+                    "email": "anony@miz.ed",
+                    "account": {
+                        "id": "9632",
+                        "username": "webm",
+                        "acct": "webm",
+                        "display_name": "web m \uD83C\uDDEA\uD83C\uDDFA",
+                        "locked": false,
+                        "bot": false,
+                        "discoverable": true,
+                        "group": false,
+                        "created_at": "2018-05-21T11:24:55.000Z",
+                        "note": "\uD83C\uDDE9\uD83C\uDDEA Über diesen Account werden Änderungen oder geplante Beeinträchtigungen angekündigt. Wenn du einen Account auf Loma.ml besitzt, dann solltest du dich mit mir verbinden.\uD83C\uDDEA\uD83C\uDDFA Changes or planned impairments are announced via this account. If you have an account on Loma.ml, you should connect to me.\uD83C\uDD98 Fallbackaccount @webm@joinfriendica.de",
+                        "url": "https://loma.ml/profile/webm",
+                        "avatar": "https://loma.ml/photo/contact/320/373ebf56355ac895a09cb99264485383?ts=1686417730",
+                        "avatar_static": "https://loma.ml/photo/contact/320/373ebf56355ac895a09cb99264485383?ts=1686417730&static=1",
+                        "header": "https://loma.ml/photo/header/373ebf56355ac895a09cb99264485383?ts=1686417730",
+                        "header_static": "https://loma.ml/photo/header/373ebf56355ac895a09cb99264485383?ts=1686417730&static=1",
+                        "followers_count": 23,
+                        "following_count": 25,
+                        "statuses_count": 15,
+                        "last_status_at": "2023-09-19T00:00:00.000Z",
+                        "emojis": [],
+                        "fields": []
+                    }
+                },
+                "rules": [],
+                "friendica": {
+                    "version": "2023.09-rc",
+                    "codename": "Giant Rhubarb",
+                    "db_version": 1539
+                }
+            }
+        """.trimIndent()
+    }
 }