[
  {
    "path": ".gitignore",
    "content": "# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n*.log\n\n# Android Studio generated files and folders\ncaptures/\n.externalNativeBuild/\n.cxx/\n*.apk\noutput.json\n\n# IntelliJ\n*.iml\n.idea/\nmisc.xml\ndeploymentTargetDropDown.xml\nrender.experimental.xml\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Android Profiling\n*.hprof\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Serhii Yaremych\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Imla - (Experimental) GPU-Accelerated Blurring for Android Jetpack Compose UI\n\n> ⚠️ **Disclaimer**:\n> This project is experimental and not intended for use in production applications.\n\n## Description\n\nImla (Ukrainian for \"Haze\", pronounced [ˈimlɑ] (eem-lah)) is an experimental project exploring\nGPU-accelerated view blurring on Android. It aims to implement efficient blurring effects using\nOpenGL, targeting devices from Android 6 (API 23) onwards.\n\nThe project serves as a playground for experimenting with GPU rendering and post-processing effects,\nwith the potential to evolve into a full-fledged library in the future.\n\n## Features\n\n- Gamma corrected blurring;\n- Adjustable blur radius;\n- Color tinting of blurred areas;\n- Blending with a noise mask for a frosted glass effect;\n- Setting blurring masks for gradient blur effects;\n- Supports Android 6 (API 23) onwards.\n\n## Demo\n\n<table>\n    <thead>\n        <tr>\n            <th colspan=2><b>Pixel 6</b></th>\n        </tr>\n    </thead>\n    <tbody>\n        <tr>\n            <td><img width=\"300\" alt=\"Gradient blur demo\" src=\"demo/gradient_blur.webp\"></td>\n            <td><img width=\"300\" alt=\"Blur demo\" src=\"demo/p6_blur_demo.webp\"></td>\n        </tr>\n        <tr>\n            <td><img width=\"300\" alt=\"Neat blur algorithm bug\" src=\"demo/blur_bug.webp\"></td>\n            <td><img width=\"300\" alt=\"Noise blend demo\" src=\"demo/p6_noise_blend_demo.webp\"></td>\n        </tr>\n        <tr>\n            <td><img width=\"300\" alt=\"Mosaic blur demo\" src=\"demo/mosaic_blur.webp\"></td>\n            <td><img width=\"300\" alt=\"Gamma corrected blur\" src=\"demo/gamma_corrected_blur.webp\"></td>\n        </tr>\n    </tbody>\n</table>\n\n\n| **Nexus 5**                                                                                                                                      |\n|--------------------------------------------------------------------------------------------------------------------------------------------------|\n| <img width=\"600\" alt=\"Live blur demo\" src=\"demo/nexus_5_demo.webp\">                                                                               |\n| <img width=\"600\" alt=\"Blur gamma correction side-by-side\" src=\"https://github.com/user-attachments/assets/85ad6c09-de0d-4bbd-89f5-a11a6aa8ac98\"> |\n| <img width=\"600\" alt=\"Mosaic blur\" src=\"https://github.com/user-attachments/assets/71d81431-a2cf-4aca-bb0d-d469ced53cee\">                        |\n\n## How It Works\n\nImla uses a combination of `GraphicsLayer` from Jetpack Compose and OpenGL ES 3.0 to achieve fast,\nGPU-accelerated blurring. The processing pipeline does multiple steps to achieve blurred effect:\n\n1. A specified view is rendered as a background texture using `Surface` and `SurfaceTexture`(\n   see [RenderableRootLayer.kt](imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/RenderableRootLayer.kt)).\n2. The rendered texture is copied to a post-processing framebuffer.\n3. The [BackdropBlur](imla/src/main/java/dev/serhiiyaremych/imla/ui/BackdropBlur.kt) composable\n   wraps\n   child composable elements that need a blurred background.\n4. The blurred texture is rendered as a SurfaceView background to the wrapped elements, creating the\n   illusion of a blurred backdrop.\n\nThe post-processing pipeline includes:\n\n1. Down-sampling the background\n   texture, [RenderableRootLayer.kt](imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/RenderableRootLayer.kt);\n2. Applying a two-pass blur algorithm with gamma\n   correction, [BlurEffect](imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/BlurEffect.kt);\n3. Blending with a noise texture for a frosted glass\n   effect, [NoiseEffect](imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/noise/NoiseEffect.kt);\n4. (Optional) Application of a mask for progressive or gradient blur\n   effects, [MaskEffect](imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/mask/MaskEffect.kt).\n\nImportantly, all blur color processing is performed in the linear color space, with appropriate\ngamma decoding and encoding applied to ensure colors blend naturally, preserving vibrancy and\ncontrast.\n\n## Rendering Abstraction\n\nThe project reuses the OpenGL abstractions from another experimental\nproject: [desugar-64/android-opengl-renderer](https://github.com/desugar-64/android-opengl-renderer).\nThis repo is a playground to learn graphics and OpenGL, including some convenient abstractions\nfor setting up OpenGL data structures and calling various OpenGL functions.\n\nThe current implementation uses a fully dynamic renderer, which pushes vertex data each frame. While\nthis approach offers flexibility, it introduces some performance overhead. Future iterations aim to\noptimize this aspect of the rendering pipeline.\n\n## Performance\n\nCurrent performance metrics for the blur effect on a Pixel 6 device:\n\n- `BlurEffect#applyEffect`: ~1.19ms\n- `RenderObject#onRender` : ~4.842ms\n\n| Trace                                                                                                  |\n|--------------------------------------------------------------------------------------------------------|\n| ![trace_blur_effect](https://github.com/user-attachments/assets/add113c5-4ccf-4ff4-a8c1-37fa404e8048)  |\n| ![trace_total_pass00](https://github.com/user-attachments/assets/78e8a4c5-43ec-4fc6-b0eb-89c6a77a1042) |\n| ![trace_total_pass01](https://github.com/user-attachments/assets/d97a629d-b683-4868-9229-c09331954a5d) |\n\nThese timings indicate that the blur effect and rendering process are relatively fast, but there's\nstill room for optimization.\n\n## Future Plans\n\n- [x] Implement Dual Kawase Blurring Filter for improved performance;\n- [ ] Optimize the rendering pipeline and OpenGL abstractions;\n- [ ] Address synchronization issues between the main thread and OpenGL thread.\n\n## Contributing\n\nThis project is open to suggestions and contributions. Feel free to open issues\nor submit pull requests on GitHub.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n\n## Development Updates\n\nFor project development updates and history, refer\nto [this Twitter thread](https://x.com/desugar_64/status/1787633739117277669).\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.jetbrains.kotlin.android)\n    alias(libs.plugins.compose.compiler)\n    alias(libs.plugins.jetbrains.kotlin.serialization)\n}\n\nandroid {\n    namespace = \"dev.serhiiyaremych.imla\"\n    compileSdk = 35\n\n    defaultConfig {\n        applicationId = \"dev.serhiiyaremych.imla\"\n        minSdk = 23\n        targetSdk = 35\n        versionCode = 1\n        versionName = \"1.0\"\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        vectorDrawables {\n            useSupportLibrary = true\n        }\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android.txt\"),\n                \"proguard-rules.pro\"\n            )\n            signingConfig = signingConfigs.getByName(\"debug\")\n        }\n        create(\"benchmark\") {\n            initWith(buildTypes.getByName(\"release\"))\n            matchingFallbacks += listOf(\"release\")\n            isDebuggable = false\n            isMinifyEnabled = false\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n\n        isCoreLibraryDesugaringEnabled = true\n    }\n    kotlinOptions {\n        jvmTarget = \"17\"\n        freeCompilerArgs += \"-Xcontext-receivers\"\n    }\n    buildFeatures {\n        compose = true\n        shaders = true\n\n        dataBinding = false\n        viewBinding = false\n        mlModelBinding = false\n        aidl = false\n        buildConfig = false\n    }\n    packaging {\n        resources {\n            excludes += \"/META-INF/{AL2.0,LGPL2.1}\"\n        }\n    }\n    lint {\n        abortOnError = true\n        warningsAsErrors = true\n    }\n}\n\ndependencies {\n    coreLibraryDesugaring(libs.desugar.jdk.libs)\n\n    implementation(libs.haze)\n    implementation(libs.androidx.runtime.tracing)\n    implementation(libs.androidx.tracing.ktx)\n\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.kotlinx.serialization.json)\n    implementation(libs.kotlinx.collections.immutable)\n\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.ui.tooling.preview)\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.material3)\n    implementation(libs.androidx.appcompat)\n    implementation(libs.material)\n    implementation(libs.androidx.material.icons.extended)\n    implementation(libs.coil.compose)\n\n    implementation(project(\":imla\"))\n\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n    debugImplementation(libs.androidx.ui.tooling)\n    debugImplementation(libs.androidx.ui.test.manifest)\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n-dontobfuscate"
  },
  {
    "path": "app/src/androidTest/java/dev/serhiiyaremych/imla/ExampleInstrumentedTest.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"dev.serhiiyaremych.imla.test\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n        android:name=\".DemoApp\"\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.Imla\"\n        tools:targetApi=\"31\">\n        <profileable\n            android:shell=\"true\"\n            tools:targetApi=\"29\" />\n\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/Theme.Imla\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/assets/loremipsum.json",
    "content": "[\n  {\n    \"id\": \"cc3a5683-b6bc-4272-8675-a904674f0261\",\n    \"userNickname\": \"@ponder\",\n    \"userFullName\": \"Perry McCray\",\n    \"userAvatar\": \"file:///android_asset/avatars/139.jpg\",\n    \"text\": \"⏸\\nSempersenectus dolor tibique euripidis pertinax eget elementum aliquam propriae morbi cubilia proin laudem aptent fugit.  Iuslibero neglegentur tota persecuti principes euismod mnesarchum suavitate sanctus quas interdum urbanitas graeco.⛏📹\\n\",\n    \"images\": [],\n    \"likes\": 484,\n    \"replyCount\": 460,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"2674562d-edbf-45fd-b43a-4a561bc4309e\",\n        \"userNickname\": \"@delica\",\n        \"userFullName\": \"Doyle Burris\",\n        \"userAvatar\": \"file:///android_asset/avatars/1.jpg\",\n        \"text\": \"adolescens  auctor  ☁️📚 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974121,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e4d7c77e-d1bb-4467-830e-ab4d5376a47e\",\n        \"userNickname\": \"@instru\",\n        \"userFullName\": \"Sherman Chapman\",\n        \"userAvatar\": \"file:///android_asset/avatars/130.jpg\",\n        \"text\": \"Volumuselementum ridiculus deterruisset sapien eleifend adipiscing dolores iisque fames vituperatoribus placerat perpetua adipiscing vituperatoribus audire simul eam sapien interdum.  Eirmodsed habemus latine parturient consectetur elitr montes pertinax inimicus sadipscing eirmod litora aptent.  Classsaperet harum at instructior venenatis tale maecenas aliquet aeque invenire cum dicta donec inceptos.  Theophrastusfeugiat blandit a tation ante sociis tale consectetuer.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574121,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"12043bea-5b2e-4fd2-9a56-e04867db6bed\",\n        \"userNickname\": \"@eam\",\n        \"userFullName\": \"Frederick Pearson\",\n        \"userAvatar\": \"file:///android_asset/avatars/69.jpg\",\n        \"text\": \"Verteremconubia ad tantas melius hac et quisque postea finibus himenaeos consul.  Eumeum honestatis.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174121,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636990374098,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"9f3e0565-d12e-4882-9e47-6d9bb441669f\",\n    \"userNickname\": \"@quot\",\n    \"userFullName\": \"Harriet Henderson\",\n    \"userAvatar\": \"file:///android_asset/avatars/85.jpg\",\n    \"text\": \"Fermentumdictumst corrumpit tamquam aeque felis taciti varius feugiat pellentesque appetere veritus nisi singulis sadipscing honestatis neque.  Egetsociis egestas fusce saepe urbanitas nominavi delectus similique qualisque patrioque ultricies eirmod.  Voluptariaaccumsan an tortor.\",\n    \"images\": [\n      \"file:///android_asset/post_images/2.jpg\",\n      \"file:///android_asset/post_images/4.jpg\",\n      \"file:///android_asset/post_images/51.jpg\",\n      \"file:///android_asset/post_images/33.jpg\",\n      \"file:///android_asset/post_images/19.jpg\"\n    ],\n    \"likes\": 460,\n    \"replyCount\": 9,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"823b4fe6-e3ce-42ce-9c52-245ee3f35444\",\n        \"userNickname\": \"@in\",\n        \"userFullName\": \"Hector Gaines\",\n        \"userAvatar\": \"file:///android_asset/avatars/56.jpg\",\n        \"text\": \"Posuerecondimentum primis sententiae adversarium fastidii tation per quisque tristique.  Natumurbanitas reprimique vis torquent bibendum interesset idque dictum solum.  Museget omittam morbi quidam nominavi tacimates tempus dicam mediocritatem veri.  Propriaelibris reformidans falli eam reque similique natoque ceteros.  Facilispopulo felis accumsan gubergren sociosqu eleifend falli inciderint viverra parturient vestibulum arcu imperdiet mnesarchum faucibus.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974121,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"6e9ed70c-41fc-45f1-ad0c-83e8aa85f9ae\",\n        \"userNickname\": \"@libris\",\n        \"userFullName\": \"Benjamin Navarro\",\n        \"userAvatar\": \"file:///android_asset/avatars/157.jpg\",\n        \"text\": \"mauris autem a\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174121,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"8e6a19b7-c44e-4a12-a7a5-1e35dad75c5d\",\n        \"userNickname\": \"@atqui\",\n        \"userFullName\": \"Mari Mann\",\n        \"userAvatar\": \"file:///android_asset/avatars/151.jpg\",\n        \"text\": \"🥞⏰\\nmontes constituto interpretaris luptatum \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574121,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3bfb1e9e-7378-45d2-9738-c571d1fd2a39\",\n        \"userNickname\": \"@magna\",\n        \"userFullName\": \"Darrel Trevino\",\n        \"userAvatar\": \"file:///android_asset/avatars/38.jpg\",\n        \"text\": \"🍯🗃 Repudiarepertinacia nisi vim iusto inani feugiat tamquam.  Mediocremnascetur condimentum evertitur falli penatibus harum mollis voluptaria unum unum referrentur.  Hassigniferumque reformidans ridiculus noster simul verear efficiantur omnesque partiendo reprehendunt magnis.  Promptadictumst porttitor maximus enim minim ultrices his usu deterruisset falli.💰➿\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"15b245db-e6f9-488d-bc46-29de834d264c\",\n        \"userNickname\": \"@advers\",\n        \"userFullName\": \"Trenton Hoover\",\n        \"userAvatar\": \"file:///android_asset/avatars/190.jpg\",\n        \"text\": \"volutpat veniam civibus liber\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974122,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636986774121,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"9ae51c3f-aaf0-4844-9470-01250502364c\",\n    \"userNickname\": \"@falli\",\n    \"userFullName\": \"Jami Cotton\",\n    \"userAvatar\": \"file:///android_asset/avatars/12.jpg\",\n    \"text\": \"🥨🌦 Atquiquaestio sem noster curabitur vero amet dictumst mattis molestie penatibus metus.  Tritanimeliore verterem.  Atquihabemus praesent lorem parturient.  Inaniquam curae nam curae dicant rutrum recteque justo alterum epicuri tempus cursus disputationi ex nihil errem possit movet porta.  Molestieleo accusata.🛰🍡🦃 \",\n    \"images\": [],\n    \"likes\": 720,\n    \"replyCount\": 368,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"a32f638a-d2d1-4839-b45c-a1f7beba6679\",\n        \"userNickname\": \"@ponder\",\n        \"userFullName\": \"Martha Chapman\",\n        \"userAvatar\": \"file:///android_asset/avatars/170.jpg\",\n        \"text\": \"🏺🏗↩️ Fastidiiluptatum suscipit ornare qui.  Adversariumarcu putent.  Evertiturtale theophrastus autem sit omnesque principes quem erroribus electram.  Propriaevim ceteros a gloriatur ornatus eum gubergren aliquet suscipiantur.  Dictasimperdiet euripidis torquent molestie delectus pharetra nam dicant repudiandae at consetetur docendi arcu libris salutatus libero.😙🥡 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1783107c-3ed1-4a4e-801b-b65ffd6b9956\",\n        \"userNickname\": \"@graece\",\n        \"userFullName\": \"Vaughn Langley\",\n        \"userAvatar\": \"file:///android_asset/avatars/119.jpg\",\n        \"text\": \"suas  pericula  🌃🔕 mediocritatem nisl  veniam  🕓 🐴💻  inceptos \\n💉🚘 ↔️🚗  docendi  🚖 habemus conubia  mazim  🍄 👇🕙  molestiae \\n🤦‍♂ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f9dcc7a2-7fec-499d-b690-d0f45b3775dd\",\n        \"userNickname\": \"@region\",\n        \"userFullName\": \"Thomas Simon\",\n        \"userAvatar\": \"file:///android_asset/avatars/112.jpg\",\n        \"text\": \"periculis electram sed ultricies\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"292d7635-d909-4072-b8ab-6d96e082936a\",\n        \"userNickname\": \"@gravid\",\n        \"userFullName\": \"Reynaldo Salas\",\n        \"userAvatar\": \"file:///android_asset/avatars/123.jpg\",\n        \"text\": \"🦒🤑👩‍🌾 dicant deseruisse iusto\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"914ed688-8ff6-4b7e-b2d9-288e7965cbee\",\n        \"userNickname\": \"@patrio\",\n        \"userFullName\": \"Ahmad Hines\",\n        \"userAvatar\": \"file:///android_asset/avatars/70.jpg\",\n        \"text\": \"Bibendumlatine ipsum accumsan corrumpit gloriatur hendrerit saperet platonem novum quas fugit mentitum aliquet iusto.  Volumuseam tation cras reformidans inimicus rutrum magnis.  Laudemluptatum venenatis litora tritani quem fugit ante ridiculus invidunt libero corrumpit.  Utamurverear debet tota reprimique eirmod quisque brute.  Ponderumvocibus quis conclusionemque dis.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f357b851-33e8-4738-ba05-ce9cd57ab26a\",\n        \"userNickname\": \"@nunc\",\n        \"userFullName\": \"Sheena Elliott\",\n        \"userAvatar\": \"file:///android_asset/avatars/40.jpg\",\n        \"text\": \"urna\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974122,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"04dc1a2a-5512-4e4f-9384-367a88e071aa\",\n        \"userNickname\": \"@mazim\",\n        \"userFullName\": \"Rickey Sweeney\",\n        \"userAvatar\": \"file:///android_asset/avatars/58.jpg\",\n        \"text\": \"voluptatibus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174122,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636939974122,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"16897794-47be-426d-8555-001eab8772bf\",\n    \"userNickname\": \"@pellen\",\n    \"userFullName\": \"Jerry Mathews\",\n    \"userAvatar\": \"file:///android_asset/avatars/142.jpg\",\n    \"text\": \"patrioque definitionem\",\n    \"images\": [],\n    \"likes\": 99,\n    \"replyCount\": 142,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636939974123,\n    \"reply\": {\n      \"id\": \"e9ad31da-ce0a-46fb-85fb-c9c23a8065e2\",\n      \"userNickname\": \"@porta\",\n      \"userFullName\": \"Jayson Hutchinson\",\n      \"userAvatar\": \"file:///android_asset/avatars/88.jpg\",\n      \"text\": \"Integergraecis vocibus iudicabit maluisset inceptos possit esse nominavi bibendum accumsan voluptaria.  Suavitategraece finibus feugait.  Gravidadeseruisse mei maximus potenti reprehendunt salutatus finibus laoreet inciderint.  Fuissetvulputate porttitor dolorem varius natoque.  Facilismediocritatem idque adolescens electram eos eius tibique nobis.\",\n      \"images\": [],\n      \"likes\": 313,\n      \"replyCount\": 665,\n      \"messages\": 9,\n      \"comments\": [\n        {\n          \"id\": \"78b23e1d-b4c3-4223-8082-f66427ff8d20\",\n          \"userNickname\": \"@vestib\",\n          \"userFullName\": \"Christina Simon\",\n          \"userAvatar\": \"file:///android_asset/avatars/141.jpg\",\n          \"text\": \"🏮\\nVerocursus est ante sit vocent cu docendi epicurei.  Doloreappareat fames verear.  Scriptalabores autem mus option discere idque intellegat eripuit rhoncus orci habitasse.🥪😦⛱ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"09c39f43-3555-4b81-bb3f-35f3b77da8c8\",\n          \"userNickname\": \"@dapibu\",\n          \"userFullName\": \"Mitchell Burns\",\n          \"userAvatar\": \"file:///android_asset/avatars/45.jpg\",\n          \"text\": \"Salutatusmollis dolores necessitatibus lectus aliquam penatibus a suas dolor repudiare.  Assueveritneque posuere duo eirmod hendrerit audire homero errem.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"371c4181-01f2-4cf8-92e1-b3c437dd3449\",\n          \"userNickname\": \"@inani\",\n          \"userFullName\": \"Enid Fisher\",\n          \"userAvatar\": \"file:///android_asset/avatars/150.jpg\",\n          \"text\": \"☃️💽🥧\\niudicabit adversarium ullamcorper patrioque evertitur\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"91687fa5-c783-45f4-8330-84aba0e5314c\",\n          \"userNickname\": \"@vehicu\",\n          \"userFullName\": \"Winfred Cherry\",\n          \"userAvatar\": \"file:///android_asset/avatars/171.jpg\",\n          \"text\": \"Contentionesultrices ligula donec legimus duis odio dicant populo aeque fabulas.  Nostrumultrices iisque.  Dicitfabulas definitionem vestibulum expetenda.  Legerevituperata magna nobis tractatos quas auctor error convallis inciderint comprehensam.  Indoctumpericula solum molestiae malesuada iisque suscipiantur eleifend gubergren minim hac evertitur accusata ad salutatus invenire cu convallis.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"78bc09ca-e6eb-4b7b-94e3-5249474bda5f\",\n          \"userNickname\": \"@hac\",\n          \"userFullName\": \"Nick Valentine\",\n          \"userAvatar\": \"file:///android_asset/avatars/178.jpg\",\n          \"text\": \"commune pharetra electram urbanitas quot\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"2c252856-0857-4461-9434-e712991a23fc\",\n          \"userNickname\": \"@incept\",\n          \"userFullName\": \"Jeff Dorsey\",\n          \"userAvatar\": \"file:///android_asset/avatars/91.jpg\",\n          \"text\": \"⬇️\\nDoctusturpis aliquet morbi antiopam curae contentiones.  Unumsagittis tortor urbanitas viderer efficitur tortor nulla an vix brute repudiare falli audire.  Ceterosjusto electram lorem singulis omittam noluisse consul habeo lacinia vulputate curae.  Dicatiusto augue eirmod eget doming percipit dissentiunt quem.  Cursuseuripidis vel phasellus malorum viris vivamus facilis praesent pulvinar rutrum saperet mutat sanctus.🔟🌖🍊\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636957974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"1e7c422a-ae5c-45c6-8c91-f7991c8010d0\",\n          \"userNickname\": \"@mucius\",\n          \"userFullName\": \"Alberta Mills\",\n          \"userAvatar\": \"file:///android_asset/avatars/169.jpg\",\n          \"text\": \"🍊\\nAmetmagnis movet unum.  Homeroiusto diam persequeris utamur aperiri efficitur mediocrem quod animal atqui bibendum felis.  Verimediocrem risus ferri.  Justopellentesque lorem tristique splendide natum libris cursus accumsan definitiones nonumes tortor venenatis.📓🥌🥐\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4b662f3e-deba-43f2-98a6-d639898a20e1\",\n          \"userNickname\": \"@amet\",\n          \"userFullName\": \"Frederic Gonzalez\",\n          \"userAvatar\": \"file:///android_asset/avatars/106.jpg\",\n          \"text\": \"habitasse\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974123,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"1a8fecfa-1dbb-43f3-a279-f8bf761b0da4\",\n          \"userNickname\": \"@eirmod\",\n          \"userFullName\": \"Charles Johnson\",\n          \"userAvatar\": \"file:///android_asset/avatars/26.jpg\",\n          \"text\": \"Laboresiaculis facilis salutatus graeci cetero assueverit indoctum voluptatibus enim rutrum vituperatoribus veritus a sea saperet dicit hendrerit.  Inviduntmaluisset oratio volutpat comprehensam platea pulvinar viverra nascetur adolescens reformidans.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636936374123,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636939974123,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"e4557916-05ba-4e67-8958-c988a5e74548\",\n    \"userNickname\": \"@compre\",\n    \"userFullName\": \"Steven Neal\",\n    \"userAvatar\": \"file:///android_asset/avatars/196.jpg\",\n    \"text\": \"tractatos has nihil tota torquent\",\n    \"images\": [],\n    \"likes\": 23,\n    \"replyCount\": 36,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974125,\n    \"reply\": {\n      \"id\": \"7b90b8c0-d096-4b8e-ac44-89c34ebb403b\",\n      \"userNickname\": \"@iudica\",\n      \"userFullName\": \"Milton Farmer\",\n      \"userAvatar\": \"file:///android_asset/avatars/67.jpg\",\n      \"text\": \"noluisse elementum tota nonumes\",\n      \"images\": [],\n      \"likes\": 94,\n      \"replyCount\": 89,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"0f8abba7-f901-4b8c-bc5b-1bca0b0b4075\",\n          \"userNickname\": \"@iisque\",\n          \"userFullName\": \"Ladonna Herring\",\n          \"userAvatar\": \"file:///android_asset/avatars/155.jpg\",\n          \"text\": \"dictas aperiri porta\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774125,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4ee55911-b0a1-4022-8240-4755a8ad1a4c\",\n          \"userNickname\": \"@his\",\n          \"userFullName\": \"Mauricio Hester\",\n          \"userAvatar\": \"file:///android_asset/avatars/156.jpg\",\n          \"text\": \"🥟 honestatis🌎📺🥙\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974125,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"bfc47d83-7452-4ac9-affe-e860a44db64a\",\n          \"userNickname\": \"@tempor\",\n          \"userFullName\": \"Elton Wood\",\n          \"userAvatar\": \"file:///android_asset/avatars/47.jpg\",\n          \"text\": \"Percipitdignissim tempor aliquam sociosqu fringilla orci appetere verterem netus.  Audireesse graece ferri solet adhuc.  Alteranoluisse scelerisque ac vituperata.  Vulputatefacilisi penatibus tritani quis suspendisse eloquentiam ludus tristique ridiculus indoctum orci.  Nisipharetra eleifend hac.  Voluptatumcurae legere aliquip.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774125,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637011974123,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": false,\n        \"totalVotes\": 1013,\n        \"positions\": [\n          {\n            \"text\": \"orci dictas\",\n            \"voted\": 71\n          },\n          {\n            \"text\": \"aliquip\",\n            \"voted\": 279\n          },\n          {\n            \"text\": \"conubia elementum nobis nostrum\",\n            \"voted\": 412\n          },\n          {\n            \"text\": \"ancillae adversarium liber feugait aptent\",\n            \"voted\": 75\n          },\n          {\n            \"text\": \"expetenda quis pro\",\n            \"voted\": 176\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"5739ca94-a595-4d4f-93c0-693fe402052c\",\n    \"userNickname\": \"@quem\",\n    \"userFullName\": \"Maggie Cotton\",\n    \"userAvatar\": \"file:///android_asset/avatars/19.jpg\",\n    \"text\": \"Pertinaxtincidunt nisl.  Promptafermentum ornatus atqui mus docendi decore tale facilis mentitum te risus regione sadipscing novum.\",\n    \"images\": [\n      \"file:///android_asset/post_images/53.jpg\",\n      \"file:///android_asset/post_images/31.jpg\",\n      \"file:///android_asset/post_images/95.jpg\",\n      \"file:///android_asset/post_images/86.jpg\"\n    ],\n    \"likes\": 62,\n    \"replyCount\": 642,\n    \"messages\": 1,\n    \"comments\": [\n      {\n        \"id\": \"58f8d963-5dde-420d-bc96-af2ec6330629\",\n        \"userNickname\": \"@suas\",\n        \"userFullName\": \"Wendell Luna\",\n        \"userAvatar\": \"file:///android_asset/avatars/61.jpg\",\n        \"text\": \"❇️🐼\\nMediocremiriure tempus corrumpit.  Dissentiuntpharetra dapibus affert accumsan moderatius.  Aperirilegere molestie molestiae convenire te propriae felis mei vestibulum deseruisse volutpat.  Persiusreprehendunt vocibus pharetra habitasse fusce est prodesset in ei.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974125,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637004774125,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"4aad95e1-e5d6-465f-9922-2ff11ecd58f9\",\n    \"userNickname\": \"@posuer\",\n    \"userFullName\": \"Freda Rosa\",\n    \"userAvatar\": \"file:///android_asset/avatars/65.jpg\",\n    \"text\": \"🥨📭  dolorem \\n👩‍👩‍👧 luctus  quot \\n🛣 disputationi  solum  🐌 📱🐟  inceptos \\n😎🚇 👕🤣  feugait  🚨👒 🥤🛵  posse \\n👨‍💻 putent  mandamus \\n🐒 ⬅️👭  gravida \\n🌍💙 \",\n    \"images\": [\n      \"file:///android_asset/post_images/21.jpg\",\n      \"file:///android_asset/post_images/65.jpg\",\n      \"file:///android_asset/post_images/73.jpg\"\n    ],\n    \"likes\": 321,\n    \"replyCount\": 563,\n    \"messages\": 4,\n    \"comments\": [\n      {\n        \"id\": \"27571184-2b66-4ee4-94ee-ab01f594f1a9\",\n        \"userNickname\": \"@dolore\",\n        \"userFullName\": \"Annette Savage\",\n        \"userAvatar\": \"file:///android_asset/avatars/181.jpg\",\n        \"text\": \"🍒 deseruisse🏣 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636975974125,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e036703a-2d47-43f8-b488-70da2d313c04\",\n        \"userNickname\": \"@nobis\",\n        \"userFullName\": \"Emmanuel Macias\",\n        \"userAvatar\": \"file:///android_asset/avatars/102.jpg\",\n        \"text\": \"Atefficiantur auctor falli animal id curae velit inceptos falli facilis litora pulvinar evertitur mi condimentum.  Sumovidisse persecuti rutrum salutatus vix aliquet dicunt pulvinar quidam delicata senectus idque fabellas suscipit feugait tacimates tritani leo.  Imperdietlaudem montes sapientem.  Convallisrhoncus meliore minim appareat sodales eruditi lorem lectus senserit neque pertinacia velit lorem ac vituperatoribus libris assueverit periculis potenti.  Epicureimaximus natoque lacus nisi ornatus interdum tantas voluptatibus mi solet facilis singulis dico interpretaris orci scelerisque.  Sociisarcu idque dico mediocrem pellentesque nisi curabitur magnis nonumes adolescens graeco gubergren graecis litora.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374125,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ce421f84-0dfc-4b08-a2b8-28e59ea0d1ed\",\n        \"userNickname\": \"@mollis\",\n        \"userFullName\": \"Leona Fleming\",\n        \"userAvatar\": \"file:///android_asset/avatars/80.jpg\",\n        \"text\": \"sociosqu per duo\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d7050a9b-afea-45b5-ad89-bc65332dd8b5\",\n        \"userNickname\": \"@indoct\",\n        \"userFullName\": \"Lakisha Wolfe\",\n        \"userAvatar\": \"file:///android_asset/avatars/134.jpg\",\n        \"text\": \"posidonium ancillae offendit agam diam\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174126,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636993974125,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"9e6f3399-c817-4304-84f8-c91f34a61021\",\n    \"userNickname\": \"@tracta\",\n    \"userFullName\": \"Wendell Odom\",\n    \"userAvatar\": \"file:///android_asset/avatars/129.jpg\",\n    \"text\": \"Eiusverterem finibus qui pri suavitate.  Ferritincidunt sadipscing et minim risus ea tempus corrumpit mattis maecenas simul phasellus tacimates sit.  Possecum tation partiendo justo invenire vulputate rhoncus.  Honestatiscubilia feugiat graecis vidisse putent latine omittantur gravida fermentum alia pulvinar gloriatur tristique inimicus aliquid sonet usu neglegentur volutpat.\",\n    \"images\": [],\n    \"likes\": 277,\n    \"replyCount\": 424,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"37ba58d7-5b82-4681-b77a-2ae98be6a492\",\n        \"userNickname\": \"@porro\",\n        \"userFullName\": \"Reynaldo Zimmerman\",\n        \"userAvatar\": \"file:///android_asset/avatars/20.jpg\",\n        \"text\": \"🃏🌮  movet \\n✝️🗄 📝👩‍🏫  civibus \\n🤑 ⛺️🐐  solet \\n®️ 🍢🌏  sapien  ⛓📘 proin solet  nihil \\n🎠 penatibus putent  postulant \\n🚠🍬 nihil euismod  facilisi  ⚓️ alienum  mauris \\n🌾 quis  pri  🈯️👂 falli  adipiscing  🥠 repudiandae  equidem  👗💵 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"795c7d1d-9430-475d-8226-c0792266dd4e\",\n        \"userNickname\": \"@lectus\",\n        \"userFullName\": \"Ericka Wood\",\n        \"userAvatar\": \"file:///android_asset/avatars/182.jpg\",\n        \"text\": \"postea ancillae\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f701bf7b-5660-48be-95e8-ce9d955d7e8c\",\n        \"userNickname\": \"@tritan\",\n        \"userFullName\": \"Raphael Raymond\",\n        \"userAvatar\": \"file:///android_asset/avatars/43.jpg\",\n        \"text\": \"error nisl iuvaret condimentum\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"0a75c41a-17df-4052-97c1-b20e2e52bd87\",\n        \"userNickname\": \"@facili\",\n        \"userFullName\": \"Eugenio Cervantes\",\n        \"userAvatar\": \"file:///android_asset/avatars/161.jpg\",\n        \"text\": \"🎊\\nvim🌾🍩6️⃣\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"82d7da6c-2b59-4b0c-b56d-ec4eba4954bb\",\n        \"userNickname\": \"@ornatu\",\n        \"userFullName\": \"Letha Gay\",\n        \"userAvatar\": \"file:///android_asset/avatars/189.jpg\",\n        \"text\": \"📧🚖  omnesque  📇 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"7c5e52b5-c599-4268-a648-71baed2ff5d9\",\n        \"userNickname\": \"@luptat\",\n        \"userFullName\": \"Seth David\",\n        \"userAvatar\": \"file:///android_asset/avatars/165.jpg\",\n        \"text\": \"Dicamfusce condimentum ubique movet theophrastus pulvinar mattis elaboraret melius voluptaria dico consectetuer.  Metusfermentum suavitate ancillae pharetra theophrastus vix congue eloquentiam reprehendunt dictumst suscipit.  Duisfacilisis mandamus salutatus malesuada maximus mediocrem harum ancillae habitasse expetenda feugait inceptos invenire eos legere quaeque audire etiam moderatius.  Communetractatos inani mea no maiestatis putent errem aeque dictas metus iisque scripserit duo.  Nibhgraeci eirmod persequeris qui consectetur disputationi vel.  Esthinc no an arcu vestibulum saperet vehicula praesent curabitur inciderint adolescens sale nobis partiendo viris accumsan noluisse diam gloriatur.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374126,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d41b3dde-2db2-40ec-ade5-098c8352e93c\",\n        \"userNickname\": \"@quot\",\n        \"userFullName\": \"Kennith Gamble\",\n        \"userAvatar\": \"file:///android_asset/avatars/104.jpg\",\n        \"text\": \"quot blandit  patrioque  🏕 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974126,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636939974126,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"3f3c3717-42ce-478a-9021-191c37dc1424\",\n    \"userNickname\": \"@donec\",\n    \"userFullName\": \"Etta Nicholson\",\n    \"userAvatar\": \"file:///android_asset/avatars/146.jpg\",\n    \"text\": \"Molestiaepraesent electram no hinc instructior posse dictumst volumus scripserit morbi dicunt errem eget scelerisque sollicitudin.  Utinamornatus suscipit nobis eleifend signiferumque suspendisse suas sit dicunt graece aliquet feugiat duo expetendis vocent possim nam.  Ipsumet sit pertinacia altera ultricies iusto comprehensam option aliquet vulputate duis nihil qui singulis ante.  Aperiripro quaerendum suscipiantur putent id maximus has movet orci voluptaria errem quisque donec mel ultrices expetenda tempus tation laoreet.  Elitpersequeris tortor utamur sodales conceptam constituam suas dapibus hendrerit scelerisque mei sociis dissentiunt primis voluptatibus aenean te ne accommodare.  Menandriatqui consectetuer risus sententiae nam adolescens eruditi falli inimicus pellentesque regione agam.\",\n    \"images\": [\n      \"file:///android_asset/post_images/54.jpg\",\n      \"file:///android_asset/post_images/38.jpg\"\n    ],\n    \"likes\": 804,\n    \"replyCount\": 294,\n    \"messages\": 1,\n    \"comments\": [\n      {\n        \"id\": \"8839a758-f063-4af8-ae19-5a9fbabfd9e5\",\n        \"userNickname\": \"@curae\",\n        \"userFullName\": \"Irene Pratt\",\n        \"userAvatar\": \"file:///android_asset/avatars/27.jpg\",\n        \"text\": \"Vitaehabeo partiendo nihil viderer.  Consectetuerdis ac aliquip euripidis equidem.  Populorepudiare conceptam pertinax decore saperet.  Oporteatoption mollis omittantur sem luptatum graecis alia utamur possit tractatos potenti porttitor utamur hinc efficiantur equidem.  Harumdolorum homero lacinia quot adhuc delectus tempor senectus dicant wisi dolor inimicus propriae cursus meliore.  Senserithomero torquent labores molestiae sonet eum tristique libris gloriatur impetus detraxit sea aliquam cu.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174127,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637001174126,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"20afd082-15b0-4b39-93d0-736e705dbbe5\",\n    \"userNickname\": \"@accums\",\n    \"userFullName\": \"Juliette Campos\",\n    \"userAvatar\": \"file:///android_asset/avatars/197.jpg\",\n    \"text\": \"🍼🤤  efficiantur \\n🌊 erat  conclusionemque \\n🦊 💿👯  libero \\n🌎🌁 👹♣️  efficiantur  🆎 🚲📇  faucibus  😟🥦 sea  pri  🦑 cetero hac  deserunt  ⚱️ expetendis  expetenda  💓🍢 malesuada  condimentum  🍵🕵 \",\n    \"images\": [],\n    \"likes\": 425,\n    \"replyCount\": 314,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"7a9bc732-7cc7-497b-b0b5-a291b5343c03\",\n        \"userNickname\": \"@iisque\",\n        \"userFullName\": \"Elena Roth\",\n        \"userAvatar\": \"file:///android_asset/avatars/94.jpg\",\n        \"text\": \"principes doctus porta magna viderer\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374127,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f4d44856-6b4b-4705-96df-f9bfb59fecc0\",\n        \"userNickname\": \"@alienu\",\n        \"userFullName\": \"Scott Silva\",\n        \"userAvatar\": \"file:///android_asset/avatars/137.jpg\",\n        \"text\": \"imperdiet nostra\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974127,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2fd05e40-e472-4c7a-b592-5967bd39b388\",\n        \"userNickname\": \"@vocent\",\n        \"userFullName\": \"Sofia Lowery\",\n        \"userAvatar\": \"file:///android_asset/avatars/101.jpg\",\n        \"text\": \"🌃🍵 veritus ludus🔭🆑\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174127,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636986774127,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"7d5e02a0-0763-416f-9aad-bdffc3a292d4\",\n    \"userNickname\": \"@errem\",\n    \"userFullName\": \"Jim Koch\",\n    \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n    \"text\": \"🥛👨‍✈ utamur \",\n    \"images\": [],\n    \"likes\": 83,\n    \"replyCount\": 90,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637001174127,\n    \"reply\": {\n      \"id\": \"7744a4f1-74e7-4533-988f-f21c21b328f1\",\n      \"userNickname\": \"@habita\",\n      \"userFullName\": \"Morton Bender\",\n      \"userAvatar\": \"file:///android_asset/avatars/62.jpg\",\n      \"text\": \"Principesrhoncus recteque saepe quaestio dapibus sadipscing delectus reprimique ultricies legimus sumo contentiones suspendisse ex meliore mediocritatem pri veniam.  Maecenasoffendit ut hac ne suas eos nisi class volumus egestas cetero definitionem integer eirmod.  Tacitipretium pretium no melius quod integer decore quaerendum nulla malorum mollis quas eu porttitor animal phasellus.\",\n      \"images\": [],\n      \"likes\": 225,\n      \"replyCount\": 48,\n      \"messages\": 5,\n      \"comments\": [\n        {\n          \"id\": \"948b1eeb-7680-4f5f-a58e-b7d885a2fd7e\",\n          \"userNickname\": \"@quo\",\n          \"userFullName\": \"Julianne Alexander\",\n          \"userAvatar\": \"file:///android_asset/avatars/6.jpg\",\n          \"text\": \"Doloresquod sumo eripuit no fermentum altera dapibus lorem necessitatibus scripserit putent posidonium mucius qui hinc consul debet fabellas.  Ametutamur vulputate molestiae suspendisse fabellas ferri auctor voluptatibus viris nonumy tota elit.  Librissumo homero.  Alialaudem nobis mnesarchum.  Nonumyexpetendis scelerisque quidam mauris eos agam regione habitasse dicat alterum sagittis sanctus.  Consetetursapientem dolor nam option vestibulum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636997574127,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"10566497-6065-469c-baf9-8af20b1cbc4b\",\n          \"userNickname\": \"@potent\",\n          \"userFullName\": \"Vance Aguilar\",\n          \"userAvatar\": \"file:///android_asset/avatars/111.jpg\",\n          \"text\": \"molestie\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374127,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"afddeddd-2280-43ac-b1ac-192a2a200c23\",\n          \"userNickname\": \"@tincid\",\n          \"userFullName\": \"Graham Gay\",\n          \"userAvatar\": \"file:///android_asset/avatars/89.jpg\",\n          \"text\": \"Numquamdiam cetero congue tation vim conclusionemque conclusionemque.  Dictasit veri necessitatibus mei montes tantas qui facilis tation quod.  Usumelius faucibus contentiones repudiare tortor per ex pro populo quod quaestio legere viderer tincidunt non pretium libero agam.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574127,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"cc6ff0fd-d4b1-4764-9f18-6fe81f8e8812\",\n          \"userNickname\": \"@concep\",\n          \"userFullName\": \"Wilbur Murray\",\n          \"userAvatar\": \"file:///android_asset/avatars/34.jpg\",\n          \"text\": \"Possitdiscere luctus veniam platea.  Donecconceptam persecuti quo vidisse ridens a dolor hac felis viderer pri error invenire.  Communediscere adipiscing conclusionemque.  Veroceteros alienum cursus class etiam deterruisset a animal nostrum per dolore porro ut.  Pulvinarqualisque mnesarchum pharetra curae finibus dictum convallis porta euripidis ornare doming cu volutpat vel sociis vitae.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636979574127,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a7053c74-239d-4458-94ca-3920d0f38a10\",\n          \"userNickname\": \"@ipsum\",\n          \"userFullName\": \"Tabatha Roman\",\n          \"userAvatar\": \"file:///android_asset/avatars/120.jpg\",\n          \"text\": \"commune vivamus  eos \\n💆 habemus  appetere  💞 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574127,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636993974127,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"7a9a593f-5554-44b0-b8bf-f1d7d5d92e75\",\n    \"userNickname\": \"@est\",\n    \"userFullName\": \"Jeremy Hodges\",\n    \"userAvatar\": \"file:///android_asset/avatars/191.jpg\",\n    \"text\": \"eleifend affert\",\n    \"images\": [],\n    \"likes\": 65,\n    \"replyCount\": 114,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636968774127,\n    \"reply\": {\n      \"id\": \"68171fb6-9c39-4a08-92be-2936012f67af\",\n      \"userNickname\": \"@inimic\",\n      \"userFullName\": \"Nelson Pena\",\n      \"userAvatar\": \"file:///android_asset/avatars/7.jpg\",\n      \"text\": \"condimentum definitionem  suscipit  🐯📲 simul te  singulis \\n🐞 nulla himenaeos  pulvinar \\n⛩ 🍒🛶  inceptos  🔐👨‍👩‍👧‍👧 turpis decore  epicuri \\n⛲️ ante parturient  aeque  🌠🐍 📞🤑  condimentum  💲 \",\n      \"images\": [\n        \"file:///android_asset/post_images/30.jpg\",\n        \"file:///android_asset/post_images/10.jpg\",\n        \"file:///android_asset/post_images/72.jpg\",\n        \"file:///android_asset/post_images/28.jpg\"\n      ],\n      \"likes\": 833,\n      \"replyCount\": 56,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"b5bb832a-2dd4-4c71-8270-ee7742ebcdb2\",\n          \"userNickname\": \"@patrio\",\n          \"userFullName\": \"Bobbie Snider\",\n          \"userAvatar\": \"file:///android_asset/avatars/13.jpg\",\n          \"text\": \"Tortorfabellas commune ubique adipisci moderatius nibh dolore facilis nisi inciderint expetendis pertinacia purus phasellus.  Extacimates facilis propriae.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374128,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"dc14136f-c011-44c4-8eb2-42406872f8ec\",\n          \"userNickname\": \"@volupt\",\n          \"userFullName\": \"Terrance Wilcox\",\n          \"userAvatar\": \"file:///android_asset/avatars/163.jpg\",\n          \"text\": \"⛪️💦 Eloquentiamurbanitas ponderum dolore iaculis.  Vocentridens penatibus orci dicta reprimique singulis luptatum senectus epicurei movet impetus invidunt egestas mucius solet intellegat himenaeos graeci vocibus.  Maluissetdapibus percipit epicuri.  Pretiumquem theophrastus repudiandae finibus homero veniam mel malorum sapientem singulis qualisque solet aptent vituperatoribus et alterum petentium eam.  Dictasei movet natoque principes repudiandae. \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636990374128,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636954374127,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"6bcf2fe3-82b6-4bda-9aa7-b68fb64fd6dd\",\n    \"userNickname\": \"@pellen\",\n    \"userFullName\": \"Cathryn Hardy\",\n    \"userAvatar\": \"file:///android_asset/avatars/124.jpg\",\n    \"text\": \"mediocrem\",\n    \"images\": [],\n    \"likes\": 29,\n    \"replyCount\": 144,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636993974128,\n    \"reply\": {\n      \"id\": \"74697a81-47e1-4bb0-bb17-66781d378261\",\n      \"userNickname\": \"@senten\",\n      \"userFullName\": \"Carly Berger\",\n      \"userAvatar\": \"file:///android_asset/avatars/17.jpg\",\n      \"text\": \"Veniamdelectus posse utinam gubergren nulla erroribus sociis dictas ius affert animal his inceptos detraxit commodo delectus mutat.  Eruditiepicuri vidisse scelerisque quaeque ornatus mei consetetur bibendum nonumes has.  Hendreritomittantur netus in eum suscipit eros quod vivendo cras taciti audire tacimates class epicurei quidam velit justo fuisset nobis.  Loremnatum sociosqu detraxit wisi nisl corrumpit est mea verterem sem.\",\n      \"images\": [\n        \"file:///android_asset/post_images/34.jpg\",\n        \"file:///android_asset/post_images/37.jpg\",\n        \"file:///android_asset/post_images/16.jpg\"\n      ],\n      \"likes\": 103,\n      \"replyCount\": 553,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"dc953522-a71f-43e1-acc0-3fcec1a5226f\",\n          \"userNickname\": \"@option\",\n          \"userFullName\": \"Sheryl Owen\",\n          \"userAvatar\": \"file:///android_asset/avatars/147.jpg\",\n          \"text\": \"nonumy fusce commune\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974128,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"5187b2b6-97c1-49f8-bc9f-9e5507ddca97\",\n          \"userNickname\": \"@melius\",\n          \"userFullName\": \"Deanna Horn\",\n          \"userAvatar\": \"file:///android_asset/avatars/158.jpg\",\n          \"text\": \"Mediocremmorbi graeci fabulas elementum electram civibus iuvaret suspendisse varius taciti accusata sollicitudin.  Vidissepertinax iriure sale.  Esseefficitur viverra natoque mollis ei nibh brute nulla urna odio oratio honestatis nulla euismod disputationi tristique adipiscing esse dolor.  Parturientsimilique urna.  Sollicitudindelicata consetetur arcu vocibus mazim repudiandae veritus torquent pericula ornatus egestas eum quaerendum nostrum imperdiet utroque postea vocent viris.  Porttitorlaoreet in has eirmod mediocritatem dolorum velit tempor dapibus et facilisis accusata blandit suavitate graece verterem.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774128,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"71b89ed9-c1a7-4c50-b3a3-702ed929d491\",\n          \"userNickname\": \"@petent\",\n          \"userFullName\": \"Stevie Weber\",\n          \"userAvatar\": \"file:///android_asset/avatars/63.jpg\",\n          \"text\": \"🐛⭐️ gubergren sanctus💨🚰🚇\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974128,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636979574128,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"0b88a0c7-be2a-47bb-b8fb-e7dcd4dfe57a\",\n    \"userNickname\": \"@doming\",\n    \"userFullName\": \"Cara Witt\",\n    \"userAvatar\": \"file:///android_asset/avatars/16.jpg\",\n    \"text\": \"mazim dictumst  persecuti  🎉 utinam  euripidis  🏩🏦 🥥🍸  aliquip  👫🏰 magna senserit  tortor  👨‍👩‍👧 🐙✈️  lectus \\n👎⛪️ 🐡🍾  vocent \\n🚬 🥤😡  novum  🚐 🏗📀  ei  🔧⛺️ ⚫️🌏  fabellas  🚬 🚒📲  praesent \\n🖲 \",\n    \"images\": [\n      \"file:///android_asset/post_images/71.jpg\",\n      \"file:///android_asset/post_images/62.jpg\",\n      \"file:///android_asset/post_images/27.jpg\"\n    ],\n    \"likes\": 494,\n    \"replyCount\": 427,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"1120008c-e5be-46cd-b2e3-8d473fc9875f\",\n        \"userNickname\": \"@effici\",\n        \"userFullName\": \"Rogelio Stokes\",\n        \"userAvatar\": \"file:///android_asset/avatars/187.jpg\",\n        \"text\": \"Similiqueadipiscing quis at singulis id ipsum saepe diam pro interesset mollis vidisse posuere harum regione cursus.  Ubiquegraeci primis eget liber.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"24a99d6d-d49b-4b79-9efb-8dfe5df4221e\",\n        \"userNickname\": \"@impetu\",\n        \"userFullName\": \"Myles Barnett\",\n        \"userAvatar\": \"file:///android_asset/avatars/11.jpg\",\n        \"text\": \"🖕🛃📁\\nparturient magnis iusto hac ea🐯 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"7fe159d3-194e-4ed3-acd0-82ce5dc9fb37\",\n        \"userNickname\": \"@partur\",\n        \"userFullName\": \"Barbra Griffith\",\n        \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n        \"text\": \"Torquentinstructior metus repudiare pertinacia gravida elaboraret leo.  Accumsanpotenti fuisset doming ridiculus fuisset conubia donec mel tation meliore noster pharetra tamquam omittam malorum dicta.  Veriutinam adhuc docendi iuvaret fugit rhoncus laoreet ceteros.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b33009bb-a39e-44c8-ab58-ff6d35f501fc\",\n        \"userNickname\": \"@nisi\",\n        \"userFullName\": \"Felipe Nolan\",\n        \"userAvatar\": \"file:///android_asset/avatars/48.jpg\",\n        \"text\": \"mi mattis  mauris  ⛓ 🕸🚀  veniam  🍵 est  elaboraret  🌤🥛 🌽🕹  brute \\n🌳🏛 ❄️🛅  eget  🍾 causae  metus \\n👩‍🚀⏬ doctus consul  ridens  🅾️ iaculis  phasellus \\n↔️ 🐴😗  dicant  🥜👫 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b6eb5d87-ebf1-44a3-9eec-9a8a553b2f8a\",\n        \"userNickname\": \"@defini\",\n        \"userFullName\": \"Sanford Kramer\",\n        \"userAvatar\": \"file:///android_asset/avatars/128.jpg\",\n        \"text\": \"Nostrainterpretaris purus sed.  Vimhabemus interpretaris eget reformidans consequat numquam mandamus tota doming felis sollicitudin utroque diam regione ut senserit nihil offendit.  Temporelementum morbi percipit deserunt facilisis maximus solum torquent.  Ceterosper morbi porttitor sumo delectus imperdiet dictum.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774129,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636975974128,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"500cee11-2bd5-42f6-9565-d72dee864a90\",\n    \"userNickname\": \"@unum\",\n    \"userFullName\": \"Rudy Strickland\",\n    \"userAvatar\": \"file:///android_asset/avatars/186.jpg\",\n    \"text\": \"vis primis dissentiunt\",\n    \"images\": [],\n    \"likes\": 53,\n    \"replyCount\": 97,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636975974129,\n    \"reply\": {\n      \"id\": \"958ecd1c-e8cc-4a6e-84c9-6fdaf0a263c7\",\n      \"userNickname\": \"@idque\",\n      \"userFullName\": \"Lorie Thornton\",\n      \"userAvatar\": \"file:///android_asset/avatars/2.jpg\",\n      \"text\": \"eu iriure\",\n      \"images\": [],\n      \"likes\": 156,\n      \"replyCount\": 280,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"3e2330a7-1998-444a-9d3e-a70e7f83bcda\",\n          \"userNickname\": \"@videre\",\n          \"userFullName\": \"Sheryl Weiss\",\n          \"userAvatar\": \"file:///android_asset/avatars/31.jpg\",\n          \"text\": \"amet contentiones possit necessitatibus\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374129,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c3562db9-9385-4951-bae1-dbd97bef7e93\",\n          \"userNickname\": \"@ac\",\n          \"userFullName\": \"Trevor Wilson\",\n          \"userAvatar\": \"file:///android_asset/avatars/82.jpg\",\n          \"text\": \"🍙🙍🌛 pharetra periculis🏬🎛 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174129,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"fc9f5574-7987-4d6b-b65a-45cc925e472b\",\n          \"userNickname\": \"@ludus\",\n          \"userFullName\": \"Lacy Sears\",\n          \"userAvatar\": \"file:///android_asset/avatars/136.jpg\",\n          \"text\": \"aliquam urbanitas volutpat sapientem\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174129,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6865f4eb-ccf1-4bac-9c06-287f981eeea8\",\n          \"userNickname\": \"@ne\",\n          \"userFullName\": \"Kimberly Delgado\",\n          \"userAvatar\": \"file:///android_asset/avatars/18.jpg\",\n          \"text\": \"🍽🏭  nostrum  🕵🏞 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174129,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636972374129,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": false,\n        \"totalVotes\": 944,\n        \"positions\": [\n          {\n            \"text\": \"mi fringilla autem saperet tritani\",\n            \"voted\": 360\n          },\n          {\n            \"text\": \"sententiae ultrices cum dui\",\n            \"voted\": 141\n          },\n          {\n            \"text\": \"hac congue iaculis sale aliquid\",\n            \"voted\": 443\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"cf01eb40-1782-4e6c-932d-70dbd1378951\",\n    \"userNickname\": \"@theoph\",\n    \"userFullName\": \"Dorthy Luna\",\n    \"userAvatar\": \"file:///android_asset/avatars/153.jpg\",\n    \"text\": \"Facilisipetentium sale pharetra consetetur sed.  Instructiorinterpretaris quisque repudiandae.\",\n    \"images\": [\n      \"file:///android_asset/post_images/6.jpg\",\n      \"file:///android_asset/post_images/81.jpg\"\n    ],\n    \"likes\": 551,\n    \"replyCount\": 5,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"61ce5dcf-932f-4e6a-a44f-8622b5a0864a\",\n        \"userNickname\": \"@felis\",\n        \"userFullName\": \"Genaro Hardin\",\n        \"userAvatar\": \"file:///android_asset/avatars/177.jpg\",\n        \"text\": \"vocent facilis\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"feb8cf42-ba17-4dd1-8941-98d0c213be76\",\n        \"userNickname\": \"@repudi\",\n        \"userFullName\": \"Deann David\",\n        \"userAvatar\": \"file:///android_asset/avatars/109.jpg\",\n        \"text\": \"🔁🚸  sumo \\n🚏 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174129,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"6662a1f1-f483-4bd1-b25f-660420844622\",\n        \"userNickname\": \"@sit\",\n        \"userFullName\": \"Alfredo Bradshaw\",\n        \"userAvatar\": \"file:///android_asset/avatars/41.jpg\",\n        \"text\": \"🔋⁉️  molestie  🉑⏮ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174129,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636990374129,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"11117103-7bca-4d82-a224-e8e2d3d62a5e\",\n    \"userNickname\": \"@pretiu\",\n    \"userFullName\": \"Keri Gray\",\n    \"userAvatar\": \"file:///android_asset/avatars/138.jpg\",\n    \"text\": \"torquent\",\n    \"images\": [],\n    \"likes\": 11,\n    \"replyCount\": 101,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636947174129,\n    \"reply\": {\n      \"id\": \"81a3bfcd-9f4d-46ea-9170-b8312f850574\",\n      \"userNickname\": \"@volupt\",\n      \"userFullName\": \"Sophia Walter\",\n      \"userAvatar\": \"file:///android_asset/avatars/172.jpg\",\n      \"text\": \"fabellas inciderint postea ac\",\n      \"images\": [],\n      \"likes\": 259,\n      \"replyCount\": 249,\n      \"messages\": 0,\n      \"comments\": [],\n      \"createdAt\": 1636932774129,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 740,\n        \"positions\": [\n          {\n            \"text\": \"eos\",\n            \"voted\": 8\n          },\n          {\n            \"text\": \"equidem alterum electram iudicabit\",\n            \"voted\": 245\n          },\n          {\n            \"text\": \"hac atomorum\",\n            \"voted\": 459\n          },\n          {\n            \"text\": \"maiorum quod aliquid habitasse partiendo\",\n            \"voted\": 28\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"87c62d13-257b-4a09-b21c-d4cdc05bcdb3\",\n    \"userNickname\": \"@no\",\n    \"userFullName\": \"Shawna Maynard\",\n    \"userAvatar\": \"file:///android_asset/avatars/60.jpg\",\n    \"text\": \"✅🌎☄\\nVispri vivendo.  Muciusvocent tale ceteros ullamcorper audire sociosqu elit pericula tota labores dicit quot utinam similique indoctum inciderint tincidunt.  Oporteatdefinitiones platonem inimicus homero pertinacia honestatis mei audire consectetuer augue labores detracto vituperata sale mollis patrioque voluptaria tale vitae.®️👘🌱\\n\",\n    \"images\": [],\n    \"likes\": 40,\n    \"replyCount\": 380,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"91453676-290c-4c5d-8922-0cd3fb28ffd2\",\n        \"userNickname\": \"@movet\",\n        \"userFullName\": \"Max Ford\",\n        \"userAvatar\": \"file:///android_asset/avatars/144.jpg\",\n        \"text\": \"Curabiturcum constituto error maiorum fringilla novum omnesque purus scripta molestiae molestie rhoncus his lobortis lobortis adolescens.  Propriaefugit convallis est salutatus.  Eammolestie repudiare elementum percipit habitant consectetuer sententiae rhoncus possim.  Petentiumdecore nulla posuere nascetur erroribus fabulas adipiscing scripta detraxit posse dolore dolores penatibus conclusionemque.  Periculispropriae assueverit theophrastus dapibus vehicula commune blandit placerat novum offendit.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174130,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cb7441b7-5e93-40c1-a4e3-cdaad3bea213\",\n        \"userNickname\": \"@potent\",\n        \"userFullName\": \"Terrence Chase\",\n        \"userAvatar\": \"file:///android_asset/avatars/118.jpg\",\n        \"text\": \"quisque quod noluisse\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174130,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"349e544e-bc47-4fac-93c5-8e08004262d4\",\n        \"userNickname\": \"@quidam\",\n        \"userFullName\": \"Amber Tillman\",\n        \"userAvatar\": \"file:///android_asset/avatars/148.jpg\",\n        \"text\": \"Voluptariafacilisis viris iriure ea utamur antiopam curabitur.  Graecivero reprehendunt solet ridens accumsan dui consul ei sed porro habeo omnesque ea viris.  Repudiareprompta tractatos sale lobortis eius dui pellentesque laudem voluptatibus orci conceptam prompta quas utroque prodesset.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374130,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cfc9f125-6fa2-422c-bc01-fea4827758e1\",\n        \"userNickname\": \"@deleni\",\n        \"userFullName\": \"Chris Morrow\",\n        \"userAvatar\": \"file:///android_asset/avatars/24.jpg\",\n        \"text\": \"🐶 novum😃🚽\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636975974130,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cd2176db-5b87-4c70-a127-bbc687bfbc9d\",\n        \"userNickname\": \"@mutat\",\n        \"userFullName\": \"Sidney Ellis\",\n        \"userAvatar\": \"file:///android_asset/avatars/97.jpg\",\n        \"text\": \"tincidunt facilisis ridens dicam\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174130,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637011974129,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"1efaeebb-c519-4bbf-ac26-2f993f0b6b77\",\n    \"userNickname\": \"@eripui\",\n    \"userFullName\": \"Wendell McDaniel\",\n    \"userAvatar\": \"file:///android_asset/avatars/37.jpg\",\n    \"text\": \"dolores  fabulas \\n🐑 \",\n    \"images\": [],\n    \"likes\": 12,\n    \"replyCount\": 2,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636968774130,\n    \"reply\": {\n      \"id\": \"0fb6238a-8966-4619-89ea-1ade1ec44bff\",\n      \"userNickname\": \"@sanctu\",\n      \"userFullName\": \"Kirsten Wyatt\",\n      \"userAvatar\": \"file:///android_asset/avatars/28.jpg\",\n      \"text\": \"Vocentsodales gubergren iudicabit graecis.  Priluctus suavitate viderer elementum laudem appetere dicam.  Maecenasmei persius vivendo viris pharetra mei viris pro mus velit ferri donec elit primis praesent movet quem.  Wisitortor latine error.\",\n      \"images\": [],\n      \"likes\": 522,\n      \"replyCount\": 549,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"890a989c-6df0-4384-b4d5-10737194299a\",\n          \"userNickname\": \"@ocurre\",\n          \"userFullName\": \"Eddie Leblanc\",\n          \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n          \"text\": \"🐁🍃  congue \\n🖼🥩 ✈️🌋  velit  🐁 🕕🈺  singulis  ⬇️🍕 🔱🍞  suas  🍞 recteque  meliore \\n⚒ convenire  fringilla  💙🔩 🛌👯  ultricies \\n🚑✉️ ♋️🐁  aptent  🙊🧦 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174130,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"7810f05c-ff0c-448d-ae75-e1468547b420\",\n          \"userNickname\": \"@mentit\",\n          \"userFullName\": \"Bruno Gamble\",\n          \"userAvatar\": \"file:///android_asset/avatars/152.jpg\",\n          \"text\": \"contentiones blandit  fugit  🍅 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636986774130,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"36f57c6f-1db9-49f8-b9d8-4018453bb648\",\n          \"userNickname\": \"@theoph\",\n          \"userFullName\": \"Rogelio Blackburn\",\n          \"userAvatar\": \"file:///android_asset/avatars/4.jpg\",\n          \"text\": \"☘️✌️ Petentiumcursus aeque mattis eripuit dico maiestatis aliquam noluisse.  Potentiintellegat utroque adipisci ne volumus suspendisse corrumpit porro option arcu liber dolorum odio vel hendrerit varius.  Quidamtortor viverra mutat vim adolescens melius augue arcu perpetua nonumy eget mi interesset simul eius placerat suas magnis.  Mentitumaperiri lectus cu invenire.🌏🏝 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636986774130,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636968774130,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"17b85247-bdfd-45c8-b008-f32de9cea6e3\",\n    \"userNickname\": \"@videre\",\n    \"userFullName\": \"Lee Fulton\",\n    \"userAvatar\": \"file:///android_asset/avatars/114.jpg\",\n    \"text\": \"👩‍❤️‍👩\\nceteros mus platea epicuri\\n\",\n    \"images\": [],\n    \"likes\": 71,\n    \"replyCount\": 182,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636972374130,\n    \"reply\": {\n      \"id\": \"e875f83c-4426-42ff-adc7-70356b2734aa\",\n      \"userNickname\": \"@amet\",\n      \"userFullName\": \"Lesa Mueller\",\n      \"userAvatar\": \"file:///android_asset/avatars/167.jpg\",\n      \"text\": \"Oratiocorrumpit mel dictas praesent diam cubilia instructior eripuit vim.  Decorenulla ea atqui mus possim an justo fusce vivendo scripta possim splendide tation homero vituperata eleifend condimentum.  Eiorci a ullamcorper bibendum massa.  Posuereimpetus nec a vix est eruditi aeque indoctum reque feugait unum signiferumque constituam impetus urbanitas.\",\n      \"images\": [],\n      \"likes\": 800,\n      \"replyCount\": 96,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"abf3631b-9e48-43c2-9d79-88730b1090a5\",\n          \"userNickname\": \"@pellen\",\n          \"userFullName\": \"Jana Chambers\",\n          \"userAvatar\": \"file:///android_asset/avatars/71.jpg\",\n          \"text\": \"Mnesarchumadipisci gloriatur propriae tantas menandri.  Sociosquiaculis faucibus conubia euripidis duis suspendisse conceptam mediocritatem sapientem discere luctus faucibus unum te molestie postea explicari nonumy nisi.  Librisrepudiare assueverit mattis propriae an per suavitate dis cras consul vero affert rhoncus egestas explicari eripuit.  Liberosolet nihil expetendis salutatus tellus melius magnis simul quis.  Eumaperiri a appareat maximus imperdiet graecis utinam lobortis mei quot iisque curae euismod ne vero populo convallis efficiantur potenti.  Ludusgravida ea mutat augue aliquip interdum sadipscing.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774130,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"dd4aa38e-5f42-4526-a0d3-dfe70794a0c3\",\n          \"userNickname\": \"@nulla\",\n          \"userFullName\": \"Norman Horton\",\n          \"userAvatar\": \"file:///android_asset/avatars/68.jpg\",\n          \"text\": \"ocurreret  dico  📀⏺ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374130,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636965174130,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"c60c3892-a8d8-4cb8-858b-1a8533e8ebf1\",\n    \"userNickname\": \"@conclu\",\n    \"userFullName\": \"Willis Lowery\",\n    \"userAvatar\": \"file:///android_asset/avatars/22.jpg\",\n    \"text\": \"⛱ viris tincidunt🦗🔩 \",\n    \"images\": [],\n    \"likes\": 59,\n    \"replyCount\": 83,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974130,\n    \"reply\": {\n      \"id\": \"e1f1596e-1706-4ac3-9c5f-1631f5333cb5\",\n      \"userNickname\": \"@homero\",\n      \"userFullName\": \"Mauricio Perry\",\n      \"userAvatar\": \"file:///android_asset/avatars/83.jpg\",\n      \"text\": \"🤢\\nQuaestioveniam tractatos sapientem sanctus graecis harum legere ubique fabellas ipsum definiebas necessitatibus duis reprehendunt qui iisque eleifend.  Interduminvenire utinam partiendo mi laoreet definitiones quaerendum deterruisset.  Scelerisquenostrum ceteros.\\n\",\n      \"images\": [\n        \"file:///android_asset/post_images/29.jpg\",\n        \"file:///android_asset/post_images/41.jpg\",\n        \"file:///android_asset/post_images/82.jpg\",\n        \"file:///android_asset/post_images/50.jpg\",\n        \"file:///android_asset/post_images/76.jpg\"\n      ],\n      \"likes\": 171,\n      \"replyCount\": 697,\n      \"messages\": 5,\n      \"comments\": [\n        {\n          \"id\": \"c8b632f2-4126-4a3c-969f-dc710f00b797\",\n          \"userNickname\": \"@in\",\n          \"userFullName\": \"Myra Burnett\",\n          \"userAvatar\": \"file:///android_asset/avatars/103.jpg\",\n          \"text\": \"🎥📆  sententiae  🗂🔬 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574130,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"71774129-6aa3-4611-8244-85c5115914f0\",\n          \"userNickname\": \"@mei\",\n          \"userFullName\": \"Whitney Stokes\",\n          \"userAvatar\": \"file:///android_asset/avatars/110.jpg\",\n          \"text\": \"😅🍁😈 Vestibulummalorum habeo nam.  Laboresnascetur lobortis ocurreret molestie pretium has nascetur aptent.  Elaboraretcontentiones fabulas iusto reprimique.  Antiopamcum sapien movet duo porttitor necessitatibus minim volumus vidisse moderatius noster efficitur posse suas epicurei dictum hac eruditi.  Brutesuscipiantur hendrerit deterruisset iuvaret adhuc suas docendi dolor class eloquentiam fames congue quot populo antiopam omittantur maiorum. \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"df4b18bc-5a55-4de1-8838-da951ab56da2\",\n          \"userNickname\": \"@sceler\",\n          \"userFullName\": \"Ofelia Holman\",\n          \"userAvatar\": \"file:///android_asset/avatars/173.jpg\",\n          \"text\": \"petentium  atomorum \\n🈲 interdum voluptaria  tritani  🌶 🍂🌉  ultricies \\n💌 🔊🍾  consectetuer  🚟📦 🙂🍼  brute  🍸 🚉🔶  invenire  🌟🚶 ius  doming \\n💟 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d56172da-59a7-4155-9ea6-f7646d415a9d\",\n          \"userNickname\": \"@lobort\",\n          \"userFullName\": \"Jim Rosario\",\n          \"userAvatar\": \"file:///android_asset/avatars/126.jpg\",\n          \"text\": \"Populovoluptatibus scripta purus expetendis consul delicata detracto non fabulas suas gravida.  Tractatossaperet iuvaret dolore sententiae vel appetere.  Auctorpossit interesset platonem sem legimus quisque feugait agam antiopam animal epicuri.  Ultriciesnumquam amet posse reque omittam tota suas fabellas himenaeos eloquentiam ei liber feugiat repudiandae simul nascetur omittam atqui.  Sapiendeseruisse interpretaris scripta quaerendum honestatis saperet ius eirmod amet option perpetua civibus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"96a78c41-4394-48ac-9a79-b02e14862469\",\n          \"userNickname\": \"@molest\",\n          \"userFullName\": \"Doyle Stanton\",\n          \"userAvatar\": \"file:///android_asset/avatars/39.jpg\",\n          \"text\": \"aperiri\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174131,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636997574130,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"53a86ee9-e06f-4c08-bffc-77aa49ce846f\",\n    \"userNickname\": \"@posse\",\n    \"userFullName\": \"Corine Serrano\",\n    \"userAvatar\": \"file:///android_asset/avatars/121.jpg\",\n    \"text\": \"👩‍🎤🚙\\nno aliquid maecenas🤣🌯📓\\n\",\n    \"images\": [],\n    \"likes\": 49,\n    \"replyCount\": 99,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974131,\n    \"reply\": {\n      \"id\": \"2e0c5980-20c6-4934-8f8f-9f76d82964c6\",\n      \"userNickname\": \"@volupt\",\n      \"userFullName\": \"Pauline Charles\",\n      \"userAvatar\": \"file:///android_asset/avatars/140.jpg\",\n      \"text\": \"📻🗑 Causaecursus graeco molestie accusata aenean feugait.  Posteafringilla libris ius tota singulis sit mentitum offendit tristique impetus tale lectus lorem.  Splendidefaucibus cetero cubilia simul ubique sumo mollis eros adversarium definitionem.  Efficiturpropriae causae augue vix proin cursus parturient libris a senserit.  Musiisque nunc.😀 \",\n      \"images\": [\n        \"file:///android_asset/post_images/55.jpg\",\n        \"file:///android_asset/post_images/7.jpg\"\n      ],\n      \"likes\": 930,\n      \"replyCount\": 539,\n      \"messages\": 7,\n      \"comments\": [\n        {\n          \"id\": \"5f18d78d-5222-46e8-a3de-b108a5b8c1c0\",\n          \"userNickname\": \"@luctus\",\n          \"userFullName\": \"Kurt Cantrell\",\n          \"userAvatar\": \"file:///android_asset/avatars/168.jpg\",\n          \"text\": \"quaerendum  hinc  🦅 fringilla  cetero  💵🌯 🍄💇‍♂  vel  🛂🍘 🆕🗞  veritus \\n🌯 🕑📙  nonumes \\n📑🥘 regione adolescens  voluptatum \\n🚕 🍘✌️  magna \\n🦅 🐹🚙  curae  🍇 dis  dolorem \\n👨‍👧‍👧 ⚙️🏭  eirmod \\n🎍👨‍🍳 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4d850bda-1c24-411a-bf66-fe8fa5e9a29a\",\n          \"userNickname\": \"@suavit\",\n          \"userFullName\": \"Arnold Strickland\",\n          \"userAvatar\": \"file:///android_asset/avatars/33.jpg\",\n          \"text\": \"electram corrumpit adolescens\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"7d53312a-9dba-43fb-b36e-c27522523e7c\",\n          \"userNickname\": \"@dicta\",\n          \"userFullName\": \"Lucille Watkins\",\n          \"userAvatar\": \"file:///android_asset/avatars/25.jpg\",\n          \"text\": \"Augueappetere antiopam pellentesque impetus audire ultrices tortor auctor ancillae scripta pro delicata class lectus interdum legere.  Posidoniumsententiae libero mi usu detraxit bibendum mnesarchum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"21df954d-7c47-45b4-bd1a-8716463b9e6a\",\n          \"userNickname\": \"@inimic\",\n          \"userFullName\": \"Clair Gilbert\",\n          \"userAvatar\": \"file:///android_asset/avatars/149.jpg\",\n          \"text\": \"Ubiqueconsul inceptos gravida maiestatis sea finibus dictas taciti noster est posuere detraxit constituto vim deseruisse.  Repudiareadhuc iaculis cubilia cras evertitur maximus.  Cubiliadocendi prodesset eu vestibulum dicit sonet sale nibh sententiae suspendisse.  Scelerisquerecteque vulputate fames mediocritatem fabellas suscipit latine pro magnis accusata utamur hendrerit placerat dolores noluisse mauris.  Definiebascurae platea suspendisse propriae sapien interdum malesuada eirmod tritani discere contentiones inimicus euismod iusto.  Interpretarissociis viderer ridiculus honestatis vituperatoribus aliquip.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"b28653ef-677a-462f-a0d8-6ba238edef32\",\n          \"userNickname\": \"@defini\",\n          \"userFullName\": \"Francisco Swanson\",\n          \"userAvatar\": \"file:///android_asset/avatars/0.jpg\",\n          \"text\": \"voluptatibus adipisci  definitiones \\n🚇🌒 at  usu \\n⚜️😞 💰🧣  dolorum  🚭🕵 epicurei legere  necessitatibus  🍳 cubilia  tincidunt \\n📁 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c36e78e2-2ab9-4999-964f-03fe86a5257c\",\n          \"userNickname\": \"@delect\",\n          \"userFullName\": \"Lacey Cochran\",\n          \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n          \"text\": \"🌑🚋  volumus \\n🍵🚢 ullamcorper vulputate  urna \\n⭐️🌃 🎢😧  nominavi \\n🕣 salutatus  potenti \\n🦅 🚛🍝  hinc  🍮👩‍🏫 😒🎉  facilis  🎂 partiendo necessitatibus  scripserit \\n👭👨‍👧‍👦 😲♊️  neque \\n⛽️ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374131,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"0eed87cb-94b3-453c-b31a-4cf41047ce90\",\n          \"userNickname\": \"@graece\",\n          \"userFullName\": \"Karin Conley\",\n          \"userAvatar\": \"file:///android_asset/avatars/108.jpg\",\n          \"text\": \"elitr qui mucius\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374131,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637008374131,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"4ee7d924-895d-4e5a-8e0d-0d45de4b481c\",\n    \"userNickname\": \"@aliqui\",\n    \"userFullName\": \"Miranda Robinson\",\n    \"userAvatar\": \"file:///android_asset/avatars/77.jpg\",\n    \"text\": \"📺👩‍👩‍👦💔 sumo pericula voluptatum\\n\",\n    \"images\": [],\n    \"likes\": 8,\n    \"replyCount\": 144,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374131,\n    \"reply\": {\n      \"id\": \"c8a6a07c-4cc0-484e-9d3d-71a445a5bc16\",\n      \"userNickname\": \"@litora\",\n      \"userFullName\": \"Jason Melton\",\n      \"userAvatar\": \"file:///android_asset/avatars/79.jpg\",\n      \"text\": \"🐖\\nNecessitatibuspri parturient posidonium legimus quisque urna.  Vocentdiscere id et feugait mel scripta facilisis quisque reprehendunt erroribus error ferri deseruisse.🏪\\n\",\n      \"images\": [\n        \"file:///android_asset/post_images/17.jpg\",\n        \"file:///android_asset/post_images/25.jpg\"\n      ],\n      \"likes\": 849,\n      \"replyCount\": 453,\n      \"messages\": 6,\n      \"comments\": [\n        {\n          \"id\": \"122a5f3d-16a5-4d90-bfe8-1f186e58b321\",\n          \"userNickname\": \"@impetu\",\n          \"userFullName\": \"Stacy Reed\",\n          \"userAvatar\": \"file:///android_asset/avatars/100.jpg\",\n          \"text\": \"⛩🆘 malesuada ipsum ne \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774132,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8e33a0ea-a30b-4f4e-8c3d-4c08b8b8e811\",\n          \"userNickname\": \"@partie\",\n          \"userFullName\": \"Dollie Holland\",\n          \"userAvatar\": \"file:///android_asset/avatars/92.jpg\",\n          \"text\": \"🦏🤶  facilisi  🍂 solet  montes  🌀🍕 🏕🖼  ridens \\n🦃 🚶‍♀🚥  invenire \\n🚲🍆 🗽🌒  fames \\n👩‍🚒 ⛲️👨‍⚕  eius \\n🦇 evertitur  suspendisse \\n🍙 graece  faucibus \\n🚽💪 magna corrumpit  nominavi  👨‍✈ ♒️🚡  noster \\n🕹 🍮🃏  epicurei \\n📓 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636986774132,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"69f628af-7c43-4d91-bfef-3905085ad380\",\n          \"userNickname\": \"@eleife\",\n          \"userFullName\": \"Amos Farmer\",\n          \"userAvatar\": \"file:///android_asset/avatars/195.jpg\",\n          \"text\": \"Honestatispri intellegebat urbanitas tantas parturient condimentum adipiscing delicata populo iaculis sea errem morbi ad iriure reque reprimique ligula constituto.  Nasceturaffert legere melius.  Sapientemut suscipit condimentum iuvaret et deseruisse.  Portaindoctum per tristique placerat iudicabit dolorum morbi fabulas persequeris imperdiet non vituperata propriae suspendisse nihil volumus suas neque.  Dolorintellegebat similique tamquam adipiscing aptent atqui deseruisse eloquentiam utinam veritus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374132,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c00b3ebc-84ec-4a8b-af54-f60055911ce7\",\n          \"userNickname\": \"@iriure\",\n          \"userFullName\": \"Tamra Warner\",\n          \"userAvatar\": \"file:///android_asset/avatars/194.jpg\",\n          \"text\": \"🏦🛫🎋 Penatibusipsum faucibus vituperata legere adhuc erat dicat vestibulum gloriatur id menandri.  Ignotaeloquentiam agam accumsan wisi leo disputationi partiendo.  Duiproin suscipit id vis patrioque option expetendis tristique no erroribus saepe inani utroque causae commodo.\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374132,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6398eed7-9b71-47f1-9671-12fcd79a2d08\",\n          \"userNickname\": \"@dolore\",\n          \"userFullName\": \"Claudette Bright\",\n          \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n          \"text\": \"fames volumus iudicabit\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174132,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"cff914b5-3081-4ee3-9241-d950bd70849d\",\n          \"userNickname\": \"@portti\",\n          \"userFullName\": \"Lorraine Bush\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"🚜📰🔐\\nFallierror partiendo mauris cetero curae errem.  Adhucei ei vituperata esse semper sumo torquent detracto.  Soletdicunt voluptatum ludus ligula condimentum arcu posuere.🍹🍻✒️ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174132,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636975974131,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"786ce361-4e41-4708-9f93-169ed1c4b45a\",\n    \"userNickname\": \"@nullam\",\n    \"userFullName\": \"Wilmer Barrera\",\n    \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n    \"text\": \"patrioque\",\n    \"images\": [],\n    \"likes\": 357,\n    \"replyCount\": 506,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"1b8ae1b2-2623-439d-b953-206e10bd4432\",\n        \"userNickname\": \"@alique\",\n        \"userFullName\": \"Rusty Olson\",\n        \"userAvatar\": \"file:///android_asset/avatars/36.jpg\",\n        \"text\": \"🆚\\nSuaslaoreet quo aliquid et ponderum volutpat elit.  Nisiplatonem accumsan vivendo aptent veniam torquent dico interesset vestibulum iriure intellegebat ex reprimique sodales.  Quampellentesque mus pharetra sumo imperdiet quas deseruisse constituam eget splendide autem pretium sagittis accusata recteque menandri errem.  Dicamaliquam inceptos delenit suspendisse no mucius dis dicit idque.  Meaaccommodare turpis no inciderint tellus arcu pericula detraxit tellus suspendisse civibus.🚾 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d59c4afa-621d-4094-90b0-610044f6a299\",\n        \"userNickname\": \"@tantas\",\n        \"userFullName\": \"Duane Warner\",\n        \"userAvatar\": \"file:///android_asset/avatars/8.jpg\",\n        \"text\": \"Offenditvoluptatibus pharetra sociis volutpat error fabulas constituam contentiones blandit minim aptent congue fermentum utroque vis.  Definiebasdictum periculis molestie.  Contentionesquaerendum definiebas causae veri eius quidam aperiri omittantur consequat corrumpit mei eius ponderum.  Ludusferri fastidii tibique oporteat debet aliquip qualisque nibh.  Requepostulant senectus elaboraret magnis percipit suspendisse vocent pertinax.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"81bb3dfa-47b1-467f-96ea-327f67b4099c\",\n        \"userNickname\": \"@possit\",\n        \"userFullName\": \"Leila Flynn\",\n        \"userAvatar\": \"file:///android_asset/avatars/96.jpg\",\n        \"text\": \"scripserit similique\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"74ca5162-fa3b-4ec3-a14d-f273c8b2b888\",\n        \"userNickname\": \"@nihil\",\n        \"userFullName\": \"Seth Burke\",\n        \"userAvatar\": \"file:///android_asset/avatars/52.jpg\",\n        \"text\": \"quaeque dapibus pretium felis metus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636975974132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9256de24-ee06-486e-b66d-ad66d3f9b38a\",\n        \"userNickname\": \"@recteq\",\n        \"userFullName\": \"Garth McCarty\",\n        \"userAvatar\": \"file:///android_asset/avatars/184.jpg\",\n        \"text\": \"↕️ Theophrastusanimal tale rutrum dolor eruditi dicat consequat nullam libero oporteat molestie dolore elaboraret interpretaris quem mazim consetetur.  Estetiam patrioque offendit mi invenire tale natum voluptaria.  Taleproin saepe reformidans ac euripidis quaeque sadipscing.  Gubergrenrecteque diam usu eum constituam natoque nec habitant vulputate volumus egestas imperdiet adolescens.🎋 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"70480e60-1133-49bb-bb7c-ed3e835b6ede\",\n        \"userNickname\": \"@adipis\",\n        \"userFullName\": \"Horace Franks\",\n        \"userAvatar\": \"file:///android_asset/avatars/50.jpg\",\n        \"text\": \"📅🥦🗿\\ntibique augue🙅‍♂😉\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"87b01179-78ad-4db1-ba22-321438c5f7ce\",\n        \"userNickname\": \"@habemu\",\n        \"userFullName\": \"Reynaldo Fletcher\",\n        \"userAvatar\": \"file:///android_asset/avatars/44.jpg\",\n        \"text\": \"📹🚓  accusata \\n🔖 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"69ae5d6d-5b66-4f6a-aaab-242d254a5c00\",\n        \"userNickname\": \"@penati\",\n        \"userFullName\": \"Francis Trevino\",\n        \"userAvatar\": \"file:///android_asset/avatars/53.jpg\",\n        \"text\": \"♍️👨‍👦🏔 indoctum cubilia quidam🥠😰\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774132,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"8acdfcbe-ebad-4041-8606-5ab8058f221c\",\n        \"userNickname\": \"@everti\",\n        \"userFullName\": \"Dexter Maddox\",\n        \"userAvatar\": \"file:///android_asset/avatars/160.jpg\",\n        \"text\": \"Graecipossim iusto.  Torquentdefinitiones altera curae quod latine tacimates lectus cetero tantas facilisi persecuti per duo.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974133,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637008374132,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1014,\n      \"positions\": [\n        {\n          \"text\": \"homero ornare ridiculus ponderum dictum\",\n          \"voted\": 18\n        },\n        {\n          \"text\": \"delenit dignissim repudiare ius legimus\",\n          \"voted\": 123\n        },\n        {\n          \"text\": \"nullam mollis constituto\",\n          \"voted\": 405\n        },\n        {\n          \"text\": \"invenire nostrum\",\n          \"voted\": 468\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"4bf06ee7-c0c0-4f20-8e49-f3cb2291761c\",\n    \"userNickname\": \"@sem\",\n    \"userFullName\": \"Ahmad Huff\",\n    \"userAvatar\": \"file:///android_asset/avatars/183.jpg\",\n    \"text\": \"Duobrute euripidis et principes adversarium vocent petentium nisi reprimique vocibus quot prodesset theophrastus aptent mutat.  Sumodoming utinam nonumy etiam mandamus elitr nullam vel.  Minimius duis periculis.\",\n    \"images\": [\n      \"file:///android_asset/post_images/42.jpg\"\n    ],\n    \"likes\": 344,\n    \"replyCount\": 456,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"a309f0e0-92ed-4235-9aef-3704b8ea43ff\",\n        \"userNickname\": \"@lobort\",\n        \"userFullName\": \"Everette Lamb\",\n        \"userAvatar\": \"file:///android_asset/avatars/87.jpg\",\n        \"text\": \"dicam has menandri\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"0ea24634-2e85-4c56-9e05-4072dbc0968e\",\n        \"userNickname\": \"@conval\",\n        \"userFullName\": \"Rod Wilkins\",\n        \"userAvatar\": \"file:///android_asset/avatars/122.jpg\",\n        \"text\": \"🍋🌗 Intellegebatconsequat verear taciti mei graece non theophrastus verterem nonumy nascetur gubergren bibendum te ut convenire.  Sanctusmassa omittantur varius dicam ligula idque nonumes nobis wisi altera veritus convallis ad.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"0a014bfa-fffb-4f03-8342-498451a965d1\",\n        \"userNickname\": \"@mollis\",\n        \"userFullName\": \"Marcos Phillips\",\n        \"userAvatar\": \"file:///android_asset/avatars/81.jpg\",\n        \"text\": \"👲💇‍♂🌏 Malesuadaagam natoque sale iusto adolescens congue.  Aptentsodales magna in porro quem sapientem magna wisi.🕴\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"5678cf7b-faad-4859-b382-ba7ccea65363\",\n        \"userNickname\": \"@utroqu\",\n        \"userFullName\": \"Fabian Charles\",\n        \"userAvatar\": \"file:///android_asset/avatars/73.jpg\",\n        \"text\": \"enim\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d95d0265-bc7c-4ef1-97e4-3bf7e1c19f39\",\n        \"userNickname\": \"@eius\",\n        \"userFullName\": \"Phillip England\",\n        \"userAvatar\": \"file:///android_asset/avatars/164.jpg\",\n        \"text\": \"eu placerat pretium\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a1af296f-1f37-46cf-b40d-9c6bf995f26c\",\n        \"userNickname\": \"@simili\",\n        \"userFullName\": \"Marlon Hanson\",\n        \"userAvatar\": \"file:///android_asset/avatars/99.jpg\",\n        \"text\": \"ancillae possim sonet nec\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b542afd6-57c2-447d-b146-3ffe5cb506be\",\n        \"userNickname\": \"@solet\",\n        \"userFullName\": \"Elwood Manning\",\n        \"userAvatar\": \"file:///android_asset/avatars/30.jpg\",\n        \"text\": \"at\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774133,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637008374133,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"102d2e4f-b2e7-4183-ac1a-7b808f331ac5\",\n    \"userNickname\": \"@ex\",\n    \"userFullName\": \"Alton Petty\",\n    \"userAvatar\": \"file:///android_asset/avatars/113.jpg\",\n    \"text\": \"qui mutat  aperiri \\n👱🌅 \",\n    \"images\": [],\n    \"likes\": 73,\n    \"replyCount\": 179,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636986774133,\n    \"reply\": {\n      \"id\": \"8f043c6e-fa5d-42ca-8a34-ef2587c4a6ac\",\n      \"userNickname\": \"@natoqu\",\n      \"userFullName\": \"Leah Nicholson\",\n      \"userAvatar\": \"file:///android_asset/avatars/32.jpg\",\n      \"text\": \"🍶🕸\\nBlanditeum explicari nihil persecuti scripserit dissentiunt mediocritatem graece.  Antereque atomorum solet errem morbi sapientem simul iusto voluptatum.  Verteremfinibus molestiae justo mea malesuada quas constituam mus elit veniam postea adipiscing.  Nonumesmelius tincidunt tation inani eloquentiam.  Inanidefinitiones legimus ultrices expetendis alia amet utroque voluptaria vidisse fermentum pertinax dicam te dissentiunt urbanitas quot ligula dicit dicant.🏠🐿 \",\n      \"images\": [],\n      \"likes\": 69,\n      \"replyCount\": 656,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"5cd900c8-1d2f-435b-8a67-ece5f0201763\",\n          \"userNickname\": \"@phasel\",\n          \"userFullName\": \"Kris Galloway\",\n          \"userAvatar\": \"file:///android_asset/avatars/176.jpg\",\n          \"text\": \"Feugiatmalorum tacimates dicta ex deterruisset vel ocurreret commune invidunt lacinia reprimique vituperatoribus odio instructior.  Ultricesvocent oratio impetus te graeco dicit persius sollicitudin solet solum aeque consectetuer tota penatibus a luptatum postea vocibus.  Requequam finibus delectus cursus felis scripserit voluptaria assueverit dicam.  Risuspercipit sadipscing pro civibus leo id sociosqu.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374133,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"57629c73-7f94-4f46-8ff8-92a6a36b1b43\",\n          \"userNickname\": \"@habemu\",\n          \"userFullName\": \"Levi Yang\",\n          \"userAvatar\": \"file:///android_asset/avatars/49.jpg\",\n          \"text\": \"👦💀  malesuada \\n🌆 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374133,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636986774133,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"b37fd49e-c6b9-41f4-8fc3-3edc9248cadc\",\n    \"userNickname\": \"@himena\",\n    \"userFullName\": \"Andre Long\",\n    \"userAvatar\": \"file:///android_asset/avatars/116.jpg\",\n    \"text\": \"Porttitorius sumo feugiat feugiat adolescens commune electram gloriatur interpretaris consul duis dolor maluisset dicta nihil tamquam decore.  Quaestiooratio a solum fusce eros intellegebat praesent urna suavitate viverra leo quas fabulas senectus dicat harum constituam.  Luptatumancillae faucibus porro suavitate oratio has eam no ad error simul senectus contentiones nunc eu vivendo.  Malesuadaornatus nonumes mucius suas decore assueverit usu sollicitudin euripidis adversarium efficitur purus deseruisse alienum gloriatur vocent pretium mandamus imperdiet.\",\n    \"images\": [\n      \"file:///android_asset/post_images/45.jpg\",\n      \"file:///android_asset/post_images/60.jpg\",\n      \"file:///android_asset/post_images/74.jpg\",\n      \"file:///android_asset/post_images/66.jpg\"\n    ],\n    \"likes\": 49,\n    \"replyCount\": 582,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"5fb56fc1-66f2-4a40-a61f-608002f34a71\",\n        \"userNickname\": \"@negleg\",\n        \"userFullName\": \"Howard Bird\",\n        \"userAvatar\": \"file:///android_asset/avatars/54.jpg\",\n        \"text\": \"💤👷‍♀  facilis \\n⌛️ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"5b9e8330-a2dd-48f3-bec6-ce0efff902dd\",\n        \"userNickname\": \"@fabell\",\n        \"userFullName\": \"Ethel Bowen\",\n        \"userAvatar\": \"file:///android_asset/avatars/14.jpg\",\n        \"text\": \"eros  aeque \\n🍿💊 🦊🍽  nihil  🗻 ➕™️  quam \\n👡 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9337e202-854e-449a-a882-1a6a6a9a9a3b\",\n        \"userNickname\": \"@sadips\",\n        \"userFullName\": \"Valentin Chavez\",\n        \"userAvatar\": \"file:///android_asset/avatars/9.jpg\",\n        \"text\": \"sollicitudin lectus prodesset singulis\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974133,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a2c2bb6e-0e4e-4e40-a089-f3c5f7dd0f7a\",\n        \"userNickname\": \"@dicam\",\n        \"userFullName\": \"Reba Sears\",\n        \"userAvatar\": \"file:///android_asset/avatars/143.jpg\",\n        \"text\": \"postulant mollis\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"783e5c81-f343-4275-b2d0-8904aeb0b373\",\n        \"userNickname\": \"@everti\",\n        \"userFullName\": \"Helena Lindsey\",\n        \"userAvatar\": \"file:///android_asset/avatars/174.jpg\",\n        \"text\": \"magna  vel \\n🍌🐋 saperet eloquentiam  tale \\n🌕🌱 posuere eros  ligula  🐦 💱🖊  consetetur \\n🔴🍗 prompta oporteat  facilis  🗒🐉 atqui  malorum \\n💵 ♠️✳️  verterem \\n🐂 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774134,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636929174133,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"885c360a-3723-4627-b315-64f1aaef8e0a\",\n    \"userNickname\": \"@interp\",\n    \"userFullName\": \"Boyd Woods\",\n    \"userAvatar\": \"file:///android_asset/avatars/193.jpg\",\n    \"text\": \"🌈🚒🦁 egestas vidisse🍊🗓🐣\\n\",\n    \"images\": [],\n    \"likes\": 33,\n    \"replyCount\": 37,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636932774134,\n    \"reply\": {\n      \"id\": \"522cbe28-8b3f-43c2-813f-a7eb9a7792e2\",\n      \"userNickname\": \"@vel\",\n      \"userFullName\": \"Earlene Ball\",\n      \"userAvatar\": \"file:///android_asset/avatars/42.jpg\",\n      \"text\": \"🌎☝️🌡\\nPorttitorsingulis dictumst fabulas brute regione maiorum sapien odio.  Corrumpitreque omittantur nobis eirmod praesent ancillae corrumpit elaboraret delicata dicunt id maximus ultrices populo mnesarchum semper molestie.  Inciderinteruditi cetero finibus convenire utroque alienum regione solet primis quaestio maecenas mediocritatem deserunt mus signiferumque dis fuisset prompta.  Erroribusignota comprehensam melius efficitur ornare persius proin eu inciderint praesent.🔯🚆\\n\",\n      \"images\": [],\n      \"likes\": 490,\n      \"replyCount\": 17,\n      \"messages\": 7,\n      \"comments\": [\n        {\n          \"id\": \"07b5ebac-402f-4b86-96a2-9c1cb736c098\",\n          \"userNickname\": \"@vulput\",\n          \"userFullName\": \"Austin Wilkins\",\n          \"userAvatar\": \"file:///android_asset/avatars/166.jpg\",\n          \"text\": \"Vulputatepatrioque volumus causae.  Audireiusto semper alia usu taciti conceptam.  Librispro eirmod necessitatibus iuvaret dolore pulvinar suscipiantur metus suavitate.  Accommodareius magnis sapientem mauris causae etiam adipiscing mutat.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c305ca64-072e-4b65-a3d5-bcd45270f65c\",\n          \"userNickname\": \"@offend\",\n          \"userFullName\": \"Jeri Stewart\",\n          \"userAvatar\": \"file:///android_asset/avatars/125.jpg\",\n          \"text\": \"aperiri molestie  cras  🌾💩 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4b8367ca-8089-4540-9ad5-93c70dbb3a80\",\n          \"userNickname\": \"@disput\",\n          \"userFullName\": \"Barton Swanson\",\n          \"userAvatar\": \"file:///android_asset/avatars/192.jpg\",\n          \"text\": \"Wisidefinitionem cras.  Dictaquisque lobortis altera impetus pertinacia imperdiet has eu neglegentur a repudiare erat venenatis equidem epicurei iuvaret.  Expetendaoratio discere wisi convenire posidonium nihil.  Iuvaretdapibus alterum tamquam discere senectus constituam epicurei arcu debet libero vix urbanitas molestiae urbanitas.  Aliaquidam latine convallis utroque expetenda maximus theophrastus ligula idque non inceptos utamur dicat curabitur eleifend quis nunc tibique definiebas.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"05d43465-ba00-4f01-a250-d3aefe82ed21\",\n          \"userNickname\": \"@sanctu\",\n          \"userFullName\": \"Rosemary Hood\",\n          \"userAvatar\": \"file:///android_asset/avatars/188.jpg\",\n          \"text\": \"🈳👢  in \\n🔐🥠 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a3c1ad34-a213-4a9b-b686-174ca27ab883\",\n          \"userNickname\": \"@urna\",\n          \"userFullName\": \"Aimee Hood\",\n          \"userAvatar\": \"file:///android_asset/avatars/117.jpg\",\n          \"text\": \"labores\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d7579ea2-c8d9-42cf-8b61-40876f502128\",\n          \"userNickname\": \"@dui\",\n          \"userFullName\": \"Leah Foster\",\n          \"userAvatar\": \"file:///android_asset/avatars/10.jpg\",\n          \"text\": \"🥦🥐  quaeque  🐖 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974134,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"80776b94-02bd-4d9c-9da7-fd0fa79057a2\",\n          \"userNickname\": \"@detrac\",\n          \"userFullName\": \"Leola Potter\",\n          \"userAvatar\": \"file:///android_asset/avatars/115.jpg\",\n          \"text\": \"Docendidiscere gravida aptent.  Loremelementum vocibus vituperatoribus cu habemus assueverit regione equidem periculis.  Penatibusdicat theophrastus vim mucius hac ultricies esse sapientem finibus.  Quodluctus inani partiendo quas.  Melmalorum cubilia praesent indoctum tation viderer quidam comprehensam elaboraret fuisset habitasse tation hinc perpetua.  Quidetraxit tortor voluptatum erroribus voluptaria pri noluisse efficitur autem consul interpretaris habemus periculis.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974134,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636932774134,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"15d8ce90-27c9-4233-a288-ef686f14008f\",\n    \"userNickname\": \"@orci\",\n    \"userFullName\": \"Ruben Chandler\",\n    \"userAvatar\": \"file:///android_asset/avatars/3.jpg\",\n    \"text\": \"Senectusodio partiendo ac maecenas et aperiri idque delenit esse varius dicam lorem justo varius ignota class.  Mediocrempossit est eos wisi.  Ultriciesluptatum omittam persequeris viverra maiorum veri at ipsum errem nulla saperet reformidans aliquip debet posse.  Signiferumquepropriae ac ferri cubilia odio gravida habitasse definitiones altera neglegentur inani habitant sea.  Admaiestatis veri interesset debet urbanitas invenire qualisque impetus accumsan pertinacia definiebas dicam erroribus.  Adipiscidebet option viverra suas tantas laudem adipisci massa utamur dico nisl reque commune fugit mel ubique prompta.\",\n    \"images\": [],\n    \"likes\": 449,\n    \"replyCount\": 72,\n    \"messages\": 6,\n    \"comments\": [\n      {\n        \"id\": \"c88a0265-e185-4660-8be0-0efd5cfa669e\",\n        \"userNickname\": \"@iaculi\",\n        \"userFullName\": \"Felecia Hampton\",\n        \"userAvatar\": \"file:///android_asset/avatars/154.jpg\",\n        \"text\": \"💠🔰\\nVidissepersius tractatos finibus gubergren tacimates interdum reformidans.  Eroserror delicata curae civibus finibus ornare oratio eruditi minim tractatos.  Atquitantas nulla habemus recteque altera leo pericula ornare quot.  Duispartiendo civibus electram efficiantur.  Omnesqueerroribus alia maiorum nonumes referrentur curabitur efficiantur pellentesque tellus fermentum quot ridiculus sadipscing consectetur postea quas elitr.  Convenireequidem eros viderer mus quaerendum gravida eget. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e4ef5b33-ae70-4e43-be0a-8801eaba774e\",\n        \"userNickname\": \"@facili\",\n        \"userFullName\": \"Roman Gill\",\n        \"userAvatar\": \"file:///android_asset/avatars/133.jpg\",\n        \"text\": \"Omittanturte corrumpit lacus ei fringilla est taciti convenire populo homero agam.  Idquelaudem offendit ullamcorper movet platonem prompta assueverit.  Proinveri ante nascetur venenatis liber venenatis petentium pro gloriatur aperiri cetero a deseruisse causae.  Noluissepri ponderum has has suas molestiae brute integer tempor tibique discere mus vituperata sem oratio sale felis consectetuer ridiculus.  Atquicomprehensam definitiones percipit moderatius inani appetere commune montes nascetur mollis.  Promptamollis montes minim inani est invidunt.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c238a894-bf39-450e-b293-0140b649bee9\",\n        \"userNickname\": \"@fermen\",\n        \"userFullName\": \"Bobbie Spence\",\n        \"userAvatar\": \"file:///android_asset/avatars/57.jpg\",\n        \"text\": \"malesuada velit aliquam dictumst definiebas\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f5f8ca5a-50d7-4ca8-aaab-58a29c63f8d8\",\n        \"userNickname\": \"@odio\",\n        \"userFullName\": \"Terra Hancock\",\n        \"userAvatar\": \"file:///android_asset/avatars/35.jpg\",\n        \"text\": \"porttitor lorem  suas  🌺🕚 🏕📘  moderatius \\n🍇💝 📴🍣  causae  🍬♨️ 📞🗒  sociis \\n🧣 🐌🐚  sumo  🏬🌍 turpis petentium  aliquam  😅 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d1fc53ed-ec68-450e-b57c-1ee27472eed1\",\n        \"userNickname\": \"@enim\",\n        \"userFullName\": \"Douglas Manning\",\n        \"userAvatar\": \"file:///android_asset/avatars/15.jpg\",\n        \"text\": \"nihil vim nec\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374134,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"5882a73f-c9e6-4376-a0e9-38a63bd62912\",\n        \"userNickname\": \"@inani\",\n        \"userFullName\": \"Carolina Logan\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"🕍🚆\\nmandamus maecenas quis a \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374134,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636943574134,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"518e4fbe-c42a-4d42-8a6e-b4116dfd722f\",\n    \"userNickname\": \"@quisqu\",\n    \"userFullName\": \"Rolando Shepherd\",\n    \"userAvatar\": \"file:///android_asset/avatars/127.jpg\",\n    \"text\": \"fusce commune\",\n    \"images\": [],\n    \"likes\": 41,\n    \"replyCount\": 7,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636968774134,\n    \"reply\": {\n      \"id\": \"e69e8290-941c-4102-a8a3-2c1277cac2a3\",\n      \"userNickname\": \"@dapibu\",\n      \"userFullName\": \"Lorenzo Campos\",\n      \"userAvatar\": \"file:///android_asset/avatars/185.jpg\",\n      \"text\": \"Sollicitudinullamcorper menandri.  Tritanimandamus augue feugait facilisi aeque ipsum nonumes primis deseruisse quod.  Aeneanalterum dictumst convallis reque venenatis aptent molestiae velit voluptatibus malorum delectus.  Vocentsem aliquet mollis lectus legimus mus luptatum graece semper habitant magna ad.\",\n      \"images\": [\n        \"file:///android_asset/post_images/47.jpg\",\n        \"file:///android_asset/post_images/94.jpg\",\n        \"file:///android_asset/post_images/11.jpg\"\n      ],\n      \"likes\": 58,\n      \"replyCount\": 194,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"3aed902b-6a10-448f-be46-396264039d78\",\n          \"userNickname\": \"@saluta\",\n          \"userFullName\": \"Jasper Stanton\",\n          \"userAvatar\": \"file:///android_asset/avatars/78.jpg\",\n          \"text\": \"Totarepudiandae reprimique dicat adhuc sed vivendo posuere instructior.  Lacusaltera eos viris ornare putent efficitur.  Delectuspertinax regione maiestatis mnesarchum harum conceptam at iuvaret tritani neglegentur erroribus vocibus varius luctus.  Dictasconsequat rutrum populo eripuit voluptatibus mus.  Muspersecuti fuisset theophrastus expetenda noster maecenas phasellus ludus ignota sale.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374135,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"0a9b8800-79f8-4657-909c-91ac9be791e6\",\n          \"userNickname\": \"@luptat\",\n          \"userFullName\": \"Donna Pollard\",\n          \"userAvatar\": \"file:///android_asset/avatars/29.jpg\",\n          \"text\": \"nisi  omittam \\n🛸 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774135,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f97bc0b1-83d3-42a1-bcc0-3850320d9fea\",\n          \"userNickname\": \"@neque\",\n          \"userFullName\": \"Lara Castro\",\n          \"userAvatar\": \"file:///android_asset/avatars/93.jpg\",\n          \"text\": \"Mattishomero referrentur maximus affert platea antiopam sonet comprehensam ridens eget gravida signiferumque tation sonet.  Civibuspersius molestie lacus persecuti ante tempor.  Finibusmei utamur salutatus meliore.  Tevoluptatum sapien nostra patrioque mea option delicata voluptaria quas definitiones class arcu sodales impetus dictum sollicitudin evertitur sonet.  Rhoncusinterdum vehicula bibendum utroque.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574135,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636965174134,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"459696a5-8801-4464-bbd0-fe012e6b2ef3\",\n    \"userNickname\": \"@tota\",\n    \"userFullName\": \"Reginald Davidson\",\n    \"userAvatar\": \"file:///android_asset/avatars/21.jpg\",\n    \"text\": \"dico\",\n    \"images\": [],\n    \"likes\": 168,\n    \"replyCount\": 345,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"4a52f3ec-a38c-451d-b43c-cc0f773fc295\",\n        \"userNickname\": \"@eirmod\",\n        \"userFullName\": \"Monty Velez\",\n        \"userAvatar\": \"file:///android_asset/avatars/72.jpg\",\n        \"text\": \"nascetur vis  mollis  🍺🚦 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2ace4ca6-ca1d-45aa-a65e-64053486d9e6\",\n        \"userNickname\": \"@iaculi\",\n        \"userFullName\": \"Sam Juarez\",\n        \"userAvatar\": \"file:///android_asset/avatars/198.jpg\",\n        \"text\": \"🌹🤠\\nbibendum💦 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1fa693f6-34ab-48c1-92d5-7ab689e81fb3\",\n        \"userNickname\": \"@cu\",\n        \"userFullName\": \"Jake Delaney\",\n        \"userAvatar\": \"file:///android_asset/avatars/46.jpg\",\n        \"text\": \"Duivulputate error aliquet graeco dicta iaculis fusce phasellus.  Himenaeospulvinar sit reprimique appareat urna dicta risus.  Dapibussit elaboraret epicuri habitant omittam ultrices aenean mnesarchum leo sollicitudin litora omittam commodo docendi gravida eius.  Aliquammus ignota placerat verear.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1d47d0f0-082c-40c1-b87c-af0db31cc257\",\n        \"userNickname\": \"@affert\",\n        \"userFullName\": \"Clare Hartman\",\n        \"userAvatar\": \"file:///android_asset/avatars/5.jpg\",\n        \"text\": \"suas dicit ornare facilisi malorum\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"18b03d7e-719c-40d8-9a9f-7b3dc71434e0\",\n        \"userNickname\": \"@vel\",\n        \"userFullName\": \"Rickey Patel\",\n        \"userAvatar\": \"file:///android_asset/avatars/162.jpg\",\n        \"text\": \"Eratcommune dolore periculis euripidis dolore erroribus natum metus aenean nibh postea quaerendum veritus saepe voluptatum laoreet euismod scripta auctor.  Conclusionemqueignota debet imperdiet efficitur.  Repudiandaesenserit detraxit nibh appareat convenire orci.  Dolorempericula laudem solet altera malesuada senserit mei autem instructior tempus falli.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574135,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636939974135,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1120,\n      \"positions\": [\n        {\n          \"text\": \"falli deterruisset tractatos sodales detraxit\",\n          \"voted\": 267\n        },\n        {\n          \"text\": \"tempor altera pertinacia\",\n          \"voted\": 466\n        },\n        {\n          \"text\": \"senectus iudicabit utinam ea\",\n          \"voted\": 142\n        },\n        {\n          \"text\": \"nisi conclusionemque dapibus\",\n          \"voted\": 158\n        },\n        {\n          \"text\": \"wisi risus\",\n          \"voted\": 87\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"3ea8c272-d083-4cc6-8267-cf5d5a01d021\",\n    \"userNickname\": \"@laudem\",\n    \"userFullName\": \"Gladys Martin\",\n    \"userAvatar\": \"file:///android_asset/avatars/180.jpg\",\n    \"text\": \"💶⏳ Semvoluptatibus ipsum suas quisque.  Habeodisputationi ne ludus tibique maecenas verear inceptos persius latine intellegebat sale graece luctus sonet repudiare singulis conubia turpis sociosqu.\\n\",\n    \"images\": [\n      \"file:///android_asset/post_images/14.jpg\"\n    ],\n    \"likes\": 522,\n    \"replyCount\": 487,\n    \"messages\": 4,\n    \"comments\": [\n      {\n        \"id\": \"cab167e0-f951-4936-abcc-57bf1bd37be6\",\n        \"userNickname\": \"@simul\",\n        \"userFullName\": \"Greta Conley\",\n        \"userAvatar\": \"file:///android_asset/avatars/66.jpg\",\n        \"text\": \"Aptentconceptam mea.  Aperiricontentiones fugit suscipit interpretaris sollicitudin risus accommodare tale graeci dolores ei convenire corrumpit graeci fastidii.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cab56bbe-15ba-4407-bb28-c66357d5a28d\",\n        \"userNickname\": \"@duis\",\n        \"userFullName\": \"Tania Leblanc\",\n        \"userAvatar\": \"file:///android_asset/avatars/76.jpg\",\n        \"text\": \"🥫🏖  et  ⏭ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"16a8cb2b-2e5f-4af2-ba4a-aff60fabea7d\",\n        \"userNickname\": \"@habemu\",\n        \"userFullName\": \"Lemuel Reyes\",\n        \"userAvatar\": \"file:///android_asset/avatars/86.jpg\",\n        \"text\": \"legere egestas  error \\n⏱↕️ 🎒▫️  assueverit  🎓🐄 🈹📀  habitasse \\n🥌 👼🏘  penatibus  👩‍✈ 🔒🙊  agam  🥒 dicunt  efficiantur  🌸📯 🚘🍫  vim \\n🍃🍢 📁🍑  putent  🥌 consectetuer tibique  litora \\n📩 🐟🛎  propriae \\n🍑 🐇🦃  pericula \\n🐓 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574135,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"378347b2-f8e1-439d-81a7-19aad17442f9\",\n        \"userNickname\": \"@omitta\",\n        \"userFullName\": \"Miranda Callahan\",\n        \"userAvatar\": \"file:///android_asset/avatars/90.jpg\",\n        \"text\": \"Inceptospersecuti platonem parturient maximus.  Molestiehabemus cursus mollis persius melius quas dico his dolore iisque.  Singulisepicurei donec dolorem ornare dui gloriatur esse nostra salutatus consequat usu noster eros.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174135,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636979574135,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"c750dc62-7fb1-43dd-9519-f9d1143076de\",\n    \"userNickname\": \"@conval\",\n    \"userFullName\": \"Lloyd Castro\",\n    \"userAvatar\": \"file:///android_asset/avatars/105.jpg\",\n    \"text\": \"🍡🎴 interesset ex rhoncus hac🈂️\\n\",\n    \"images\": [],\n    \"likes\": 81,\n    \"replyCount\": 18,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974136,\n    \"reply\": {\n      \"id\": \"e89d9e74-0f92-496b-b206-4b7cec3bcb82\",\n      \"userNickname\": \"@conset\",\n      \"userFullName\": \"Maria Wilson\",\n      \"userAvatar\": \"file:///android_asset/avatars/107.jpg\",\n      \"text\": \"Commodocondimentum suavitate ridiculus tempor.  Viscras simul tantas consetetur aeque alia lorem his nonumes mediocrem cum agam cum morbi melius nonumes.  Mnesarchumgraecis interpretaris accusata suscipiantur impetus magna repudiare corrumpit scripta iisque delenit offendit lacinia mediocritatem gravida enim epicurei deserunt.  Primisaliquet invenire sonet neque elitr eu bibendum ancillae civibus dolore non petentium noster.  Appeterepellentesque natum ultrices suas pulvinar intellegat ancillae fringilla interpretaris quaeque molestiae.  Oporteatsapientem assueverit arcu dolorem malorum dolorum suavitate expetendis reprehendunt neque lectus sapientem corrumpit.\",\n      \"images\": [],\n      \"likes\": 166,\n      \"replyCount\": 218,\n      \"messages\": 6,\n      \"comments\": [\n        {\n          \"id\": \"97fb486a-3503-40ef-b633-89fc225487d1\",\n          \"userNickname\": \"@himena\",\n          \"userFullName\": \"Pierre McGowan\",\n          \"userAvatar\": \"file:///android_asset/avatars/175.jpg\",\n          \"text\": \"movet delenit suas\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374135,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"3bec4ba5-27de-490b-a360-f81c6edeab99\",\n          \"userNickname\": \"@fermen\",\n          \"userFullName\": \"Joann Kennedy\",\n          \"userAvatar\": \"file:///android_asset/avatars/145.jpg\",\n          \"text\": \"🤳☯️ Appareatfames ex dignissim in ocurreret adolescens fastidii litora.  Miharum mazim legimus persecuti.  Eiushinc melius aptent quas pellentesque graecis facilis simul.⚛️🚞🥟 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636979574135,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"cd133d87-e1be-4bd9-ae82-f3ce303d1338\",\n          \"userNickname\": \"@oporte\",\n          \"userFullName\": \"Dino Neal\",\n          \"userAvatar\": \"file:///android_asset/avatars/55.jpg\",\n          \"text\": \"🍋🐐\\neuismod suscipiantur🦃🌾🚗 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6e517f8d-deb7-45a1-b075-a7a9f30da9db\",\n          \"userNickname\": \"@lacus\",\n          \"userFullName\": \"Rae Rios\",\n          \"userAvatar\": \"file:///android_asset/avatars/23.jpg\",\n          \"text\": \"✨🥒\\nLaoreeterrem primis intellegat neque proin volumus dolorem noster viderer has offendit quo vituperatoribus eget eos pro dissentiunt latine.  Felisadhuc eget libris.  Alterumsolum dolorem ferri ridens explicari tale tibique.👽🍼\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"bba47c19-d3d6-4a33-8b00-8f07ba610138\",\n          \"userNickname\": \"@maximu\",\n          \"userFullName\": \"Reyna Tucker\",\n          \"userAvatar\": \"file:///android_asset/avatars/159.jpg\",\n          \"text\": \"🕷 Verteremrutrum ac nec oratio mnesarchum condimentum.  Wisicras sociosqu referrentur utamur graeco referrentur habeo.  Iusdisputationi primis porttitor ne quas definitionem accommodare principes dicunt novum noster mi mea.  Verearquem omnesque inciderint mea aeque iudicabit alienum saepe maluisset vivamus perpetua egestas.👧🍙\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"ca41f744-2872-429e-b683-07556d96d786\",\n          \"userNickname\": \"@vehicu\",\n          \"userFullName\": \"Erwin Stevenson\",\n          \"userAvatar\": \"file:///android_asset/avatars/132.jpg\",\n          \"text\": \"Putentaltera suscipit porttitor purus.  Atinimicus nec vel ceteros prodesset tempus ornare.  Dissentiuntsadipscing amet aeque ridiculus maximus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974136,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637004774135,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"af6f19fe-bf49-4503-b8f3-f6e57e785600\",\n    \"userNickname\": \"@quot\",\n    \"userFullName\": \"Frederic Spence\",\n    \"userAvatar\": \"file:///android_asset/avatars/39.jpg\",\n    \"text\": \"🛀\\nmagnis adolescens quaestio invidunt tractatos🚘 \",\n    \"images\": [],\n    \"likes\": 18,\n    \"replyCount\": 152,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636993974136,\n    \"reply\": {\n      \"id\": \"d20088b5-09bf-473d-a97f-0dbcb6f86420\",\n      \"userNickname\": \"@mus\",\n      \"userFullName\": \"Stevie Maynard\",\n      \"userAvatar\": \"file:///android_asset/avatars/64.jpg\",\n      \"text\": \"Atquierror liber constituam aliquam amet.  Facilisimolestiae sem vim usu corrumpit putent nam sonet fermentum nostrum odio senectus nascetur his feugait nisi maiestatis tritani movet.  Nullamet sea suavitate splendide repudiare deseruisse iaculis habitant sanctus nonumy pretium dicat dolorum porro conclusionemque ornare prompta.  Epicuriinteger metus euripidis.\",\n      \"images\": [],\n      \"likes\": 370,\n      \"replyCount\": 633,\n      \"messages\": 6,\n      \"comments\": [\n        {\n          \"id\": \"e886234e-4a53-4f25-ac2d-bb6a4ad49789\",\n          \"userNickname\": \"@movet\",\n          \"userFullName\": \"Percy Spencer\",\n          \"userAvatar\": \"file:///android_asset/avatars/59.jpg\",\n          \"text\": \"sed recteque\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"bb60aa12-5631-44f6-93da-c47b98e881fc\",\n          \"userNickname\": \"@ridens\",\n          \"userFullName\": \"Amos Hays\",\n          \"userAvatar\": \"file:///android_asset/avatars/50.jpg\",\n          \"text\": \"Expetendismollis tamquam parturient intellegebat iisque harum leo nunc dicta magnis persius scripta agam gloriatur noluisse ancillae.  Tristiquesuscipiantur interdum ea definiebas molestiae suspendisse facilisi affert duo habeo ipsum tota aenean quidam neque melius dicat.  Necdelenit ac vidisse pertinax tincidunt utinam nec ut.  Sedaudire urbanitas error inceptos expetenda molestie habitasse sapien turpis contentiones.  Vimmandamus dictas iudicabit pellentesque qui.  Tationei alienum verear delenit sadipscing docendi salutatus viderer viderer leo per sit commodo omnesque.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636936374136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"e55c5caa-c879-45e1-9228-a6bf8cfa30ca\",\n          \"userNickname\": \"@repudi\",\n          \"userFullName\": \"Milton Church\",\n          \"userAvatar\": \"file:///android_asset/avatars/51.jpg\",\n          \"text\": \"Iaculissodales antiopam.  Dissentiuntreferrentur feugait principes montes dictumst enim litora quaeque parturient imperdiet fringilla dictum delenit.  Urbanitasturpis natoque ultricies.  Estsimul nullam augue constituto tale et brute liber mea utinam impetus interesset.  Adversariumlatine verear brute voluptaria eirmod moderatius eos adhuc persequeris elementum aenean labores simul pri.  Mnesarchumid eleifend eripuit postea.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c37e2d1e-81bd-4da9-aeeb-2ec9b8694f6c\",\n          \"userNickname\": \"@tacima\",\n          \"userFullName\": \"Romeo Bauer\",\n          \"userAvatar\": \"file:///android_asset/avatars/3.jpg\",\n          \"text\": \"velit conceptam quis montes morbi\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"3fbee157-e21f-42b1-a979-62c8af9227f9\",\n          \"userNickname\": \"@medioc\",\n          \"userFullName\": \"Luella Ortega\",\n          \"userAvatar\": \"file:///android_asset/avatars/148.jpg\",\n          \"text\": \"Referrenturconstituam odio verear partiendo nec ex constituto urbanitas mea persius enim vix torquent autem orci dignissim.  Phasellusmattis minim tractatos auctor voluptatum accumsan dictas congue reformidans possim inciderint vestibulum inceptos quas vehicula appareat.  Persecutiridens mutat ne faucibus prompta interpretaris ne vocent etiam aeque splendide sit graeci appetere discere mediocrem aptent graeco odio.  Conguelegere porro debet persecuti curae possim doctus neque reque alia porro natum gubergren sem reque volutpat splendide.  Duisodales dicunt indoctum urbanitas suscipit etiam voluptatum vocent novum habitant pellentesque sadipscing sem.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374136,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"096a2e9b-1173-469e-bd23-511ad9b084de\",\n          \"userNickname\": \"@deseru\",\n          \"userFullName\": \"Alphonso Watson\",\n          \"userAvatar\": \"file:///android_asset/avatars/116.jpg\",\n          \"text\": \"💂 Cursusconclusionemque nominavi sapientem homero.  Fringillapharetra voluptatum velit interdum oratio rutrum minim.🐩 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174136,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636979574136,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"e57fc765-87d1-46e7-80c5-aff6aefae609\",\n    \"userNickname\": \"@ornare\",\n    \"userFullName\": \"Ben Russo\",\n    \"userAvatar\": \"file:///android_asset/avatars/50.jpg\",\n    \"text\": \"elaboraret ignota natoque\",\n    \"images\": [],\n    \"likes\": 119,\n    \"replyCount\": 364,\n    \"messages\": 5,\n    \"comments\": [\n      {\n        \"id\": \"b91e4fff-6be6-43f4-9257-53e871511358\",\n        \"userNickname\": \"@negleg\",\n        \"userFullName\": \"James Hays\",\n        \"userAvatar\": \"file:///android_asset/avatars/82.jpg\",\n        \"text\": \"utinam  persecuti \\n🕊 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"464538b2-a903-412e-9408-6ed505e45d0d\",\n        \"userNickname\": \"@utroqu\",\n        \"userFullName\": \"Sebastian Gonzalez\",\n        \"userAvatar\": \"file:///android_asset/avatars/10.jpg\",\n        \"text\": \"♐️🌎\\nquaestio sollicitudin \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d45b825d-c328-45a8-84c7-4043c90acd70\",\n        \"userNickname\": \"@aliqui\",\n        \"userFullName\": \"Yvonne Hawkins\",\n        \"userAvatar\": \"file:///android_asset/avatars/148.jpg\",\n        \"text\": \"Facilisismazim sententiae ferri dapibus invidunt persius natoque dictum cubilia.  Gubergrenlacus rhoncus libero malesuada doming porttitor purus utroque reprimique veritus ea nec netus eloquentiam.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"72736599-0e0d-49f2-9b54-016fcd1defa1\",\n        \"userNickname\": \"@oratio\",\n        \"userFullName\": \"Kelly Olsen\",\n        \"userAvatar\": \"file:///android_asset/avatars/110.jpg\",\n        \"text\": \"⁉️➡️  tellus \\n🛢👭 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cbb0493e-8a95-4c8a-a691-75b9cfee0b89\",\n        \"userNickname\": \"@simili\",\n        \"userFullName\": \"Elinor Moran\",\n        \"userAvatar\": \"file:///android_asset/avatars/162.jpg\",\n        \"text\": \"saperet nascetur  omittantur  🍺🐍 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374137,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636979574136,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 351,\n      \"positions\": [\n        {\n          \"text\": \"urna dictas phasellus\",\n          \"voted\": 128\n        },\n        {\n          \"text\": \"dui regione doctus persequeris\",\n          \"voted\": 63\n        },\n        {\n          \"text\": \"iusto\",\n          \"voted\": 160\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"9891b32f-a843-439a-96b1-9fd81b14ecc8\",\n    \"userNickname\": \"@adoles\",\n    \"userFullName\": \"Sanford Allen\",\n    \"userAvatar\": \"file:///android_asset/avatars/147.jpg\",\n    \"text\": \"repudiandae\",\n    \"images\": [],\n    \"likes\": 609,\n    \"replyCount\": 43,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"6a46ba9f-49d0-4292-aebb-23ef9b21a3e1\",\n        \"userNickname\": \"@nisi\",\n        \"userFullName\": \"Lamont Frederick\",\n        \"userAvatar\": \"file:///android_asset/avatars/111.jpg\",\n        \"text\": \"omittantur\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2aba3888-b139-4dcf-9bd6-051b14d852d4\",\n        \"userNickname\": \"@suscip\",\n        \"userFullName\": \"Tamika Tate\",\n        \"userAvatar\": \"file:///android_asset/avatars/11.jpg\",\n        \"text\": \"lobortis movet utamur\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574137,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"38248467-3b1f-43cc-a9e3-1b7f08ff0b16\",\n        \"userNickname\": \"@consec\",\n        \"userFullName\": \"Levi Dominguez\",\n        \"userAvatar\": \"file:///android_asset/avatars/36.jpg\",\n        \"text\": \"Hascondimentum atomorum gravida sea aliquid mentitum cras definitiones antiopam mi possim labores condimentum.  Alienumcongue iudicabit usu delenit dicit vulputate habitant diam posidonium legere cras necessitatibus epicurei assueverit.  Quisquemagna definitionem a.  Mediocremharum netus debet elaboraret malorum ius aenean.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774137,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636950774137,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1080,\n      \"positions\": [\n        {\n          \"text\": \"luptatum laudem\",\n          \"voted\": 167\n        },\n        {\n          \"text\": \"utroque hinc mnesarchum pro\",\n          \"voted\": 377\n        },\n        {\n          \"text\": \"mel utinam imperdiet principes\",\n          \"voted\": 345\n        },\n        {\n          \"text\": \"maximus pri eloquentiam\",\n          \"voted\": 171\n        },\n        {\n          \"text\": \"lacinia iaculis duis malorum\",\n          \"voted\": 20\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"ac5be1be-59db-4090-ae29-28c91a6b3e2b\",\n    \"userNickname\": \"@aenean\",\n    \"userFullName\": \"Jill Booker\",\n    \"userAvatar\": \"file:///android_asset/avatars/171.jpg\",\n    \"text\": \"🐜 nonumy vituperata utroque👧\\n\",\n    \"images\": [],\n    \"likes\": 3,\n    \"replyCount\": 199,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636975974137,\n    \"reply\": {\n      \"id\": \"5f6291fd-2547-4119-8b86-70c7a14afc2f\",\n      \"userNickname\": \"@deseru\",\n      \"userFullName\": \"Corrine Gordon\",\n      \"userAvatar\": \"file:///android_asset/avatars/134.jpg\",\n      \"text\": \"pretium sociis  pericula \\n🔱🐻 📬💉  viverra \\n🛣🚟 👩‍🎤🐑  utinam \\n🤒⛴ te  mazim  💜🖍 iriure usu  ultrices \\n🔻⏹ \",\n      \"images\": [\n        \"file:///android_asset/post_images/59.jpg\",\n        \"file:///android_asset/post_images/13.jpg\",\n        \"file:///android_asset/post_images/48.jpg\",\n        \"file:///android_asset/post_images/92.jpg\"\n      ],\n      \"likes\": 974,\n      \"replyCount\": 698,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"3f34c23e-d111-4d9e-940e-a099f11563d6\",\n          \"userNickname\": \"@iusto\",\n          \"userFullName\": \"German Preston\",\n          \"userAvatar\": \"file:///android_asset/avatars/192.jpg\",\n          \"text\": \"ac fugit affert feugait\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174137,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"29102eff-89d0-4df7-bc3f-896528a034dc\",\n          \"userNickname\": \"@senser\",\n          \"userFullName\": \"Roberta Clay\",\n          \"userAvatar\": \"file:///android_asset/avatars/183.jpg\",\n          \"text\": \"ludus facilisi iudicabit\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774137,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"b2fad259-59ab-4b68-97a0-b5061f1769eb\",\n          \"userNickname\": \"@vel\",\n          \"userFullName\": \"Carlene Carney\",\n          \"userAvatar\": \"file:///android_asset/avatars/118.jpg\",\n          \"text\": \"🍘\\nscripserit quaeque quaeque📹 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574137,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"68130072-eb70-46a6-aa03-91d5578f207c\",\n          \"userNickname\": \"@aperir\",\n          \"userFullName\": \"Sharon Burnett\",\n          \"userAvatar\": \"file:///android_asset/avatars/158.jpg\",\n          \"text\": \"percipit\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774137,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636968774137,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"23b633f6-551b-40fc-82e1-6df5cdf4b6a7\",\n    \"userNickname\": \"@alique\",\n    \"userFullName\": \"Orville Preston\",\n    \"userAvatar\": \"file:///android_asset/avatars/110.jpg\",\n    \"text\": \"🈯️\\nassueverit pharetra prodesset vel🧣🐻🦇 \",\n    \"images\": [],\n    \"likes\": 16,\n    \"replyCount\": 120,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636957974138,\n    \"reply\": {\n      \"id\": \"b9f8a988-cbd8-46bd-8f7b-07293ec51e46\",\n      \"userNickname\": \"@condim\",\n      \"userFullName\": \"Bethany Melton\",\n      \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n      \"text\": \"errem condimentum  suscipit  🌴🌠 graece  quam \\n🌯 per pharetra  antiopam  🚙 pellentesque omnesque  vulputate  🎅 periculis  vituperatoribus  👗😭 📦🚇  wisi \\n⭐️ habitant  omittantur  👺 an debet  periculis  🥛 \",\n      \"images\": [],\n      \"likes\": 588,\n      \"replyCount\": 352,\n      \"messages\": 7,\n      \"comments\": [\n        {\n          \"id\": \"af4dc6a9-1113-4b72-97fc-3d165dfd245f\",\n          \"userNickname\": \"@repreh\",\n          \"userFullName\": \"Don Long\",\n          \"userAvatar\": \"file:///android_asset/avatars/56.jpg\",\n          \"text\": \"🍼👮‍♀🦔 electram delenit justo tritani👿🦖👨‍👦‍👦\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"012330c5-ccd0-4676-9e51-459dbcf4c40d\",\n          \"userNickname\": \"@vis\",\n          \"userFullName\": \"Ingrid Dickson\",\n          \"userAvatar\": \"file:///android_asset/avatars/165.jpg\",\n          \"text\": \"habitant sociosqu\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"20a8a530-a696-4b1c-8b9d-ee0c3d4b2eb1\",\n          \"userNickname\": \"@habeo\",\n          \"userFullName\": \"Bobbie Glenn\",\n          \"userAvatar\": \"file:///android_asset/avatars/180.jpg\",\n          \"text\": \"sea fames accumsan inceptos\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"e54e84d5-a730-42c9-a915-621375743045\",\n          \"userNickname\": \"@vim\",\n          \"userFullName\": \"Guy Ware\",\n          \"userAvatar\": \"file:///android_asset/avatars/122.jpg\",\n          \"text\": \"🥚🙆‍♂  aperiri \\n😋 vitae fuisset  ad  🐼🌤 🗺🌻  maiorum  ☂️💴 movet latine  alterum \\n🥔🙋‍♂ 🏘👩‍💻  eloquentiam  🚊 expetendis  luctus  📳 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"80505e2c-58fe-481d-9352-8d1af072e62c\",\n          \"userNickname\": \"@ridens\",\n          \"userFullName\": \"Gloria Pratt\",\n          \"userAvatar\": \"file:///android_asset/avatars/11.jpg\",\n          \"text\": \"nominavi\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636957974138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"ffc8dfcb-69ff-4f6c-98e6-085cb3a4ff81\",\n          \"userNickname\": \"@fermen\",\n          \"userFullName\": \"Patrick Shields\",\n          \"userAvatar\": \"file:///android_asset/avatars/178.jpg\",\n          \"text\": \"🛷💢🍮 Eleifendcivibus intellegat possim arcu omittantur signiferumque suscipiantur tamquam errem constituto fabulas.  Malorumcommune aliquip verterem habeo sententiae ridiculus voluptaria consectetur dicant intellegat.  Quemgraece expetendis conceptam quod quaeque eloquentiam discere pretium proin repudiandae luctus autem iriure auctor fabulas gloriatur.  Mollistibique integer propriae.  Nullafaucibus error semper nullam definitiones.  Molestieomittantur graecis wisi ignota aeque saepe referrentur eam utamur nam.💇 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"19f08eb2-771a-48f9-9a66-5155173452f6\",\n          \"userNickname\": \"@atqui\",\n          \"userFullName\": \"Neva Fernandez\",\n          \"userAvatar\": \"file:///android_asset/avatars/188.jpg\",\n          \"text\": \"sadipscing  neque  🔩👗 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374138,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636950774138,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"e93d358c-eade-43f8-8fd8-5dad935f8a96\",\n    \"userNickname\": \"@lacini\",\n    \"userFullName\": \"Conrad Santana\",\n    \"userAvatar\": \"file:///android_asset/avatars/150.jpg\",\n    \"text\": \"Vivamusappareat porta eleifend luctus graeci labores pharetra risus sumo solum melius cu animal similique dui.  Montesnumquam litora prompta eleifend meliore risus definitionem antiopam affert interesset vehicula sanctus altera interesset egestas sapientem.\",\n    \"images\": [],\n    \"likes\": 266,\n    \"replyCount\": 202,\n    \"messages\": 8,\n    \"comments\": [\n      {\n        \"id\": \"af2b4194-afe0-480d-a7e7-1faf3e5872e5\",\n        \"userNickname\": \"@errem\",\n        \"userFullName\": \"Dawn Schmidt\",\n        \"userAvatar\": \"file:///android_asset/avatars/177.jpg\",\n        \"text\": \"qui cras lorem sea\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"83adcea4-64ee-47a2-a22f-2bb95f6bcc57\",\n        \"userNickname\": \"@vocibu\",\n        \"userFullName\": \"Jeanette Rivera\",\n        \"userAvatar\": \"file:///android_asset/avatars/177.jpg\",\n        \"text\": \"Ponderumridiculus platonem latine reprimique pertinax alterum.  Tibiquesumo quod sumo aliquid quisque sea autem ancillae elitr pericula porro vidisse detraxit evertitur parturient postulant porttitor ornare.  Salehabeo suspendisse commune arcu nonumes tation posidonium tractatos graecis dicunt definitiones pulvinar mus.  Conubiadecore inciderint aliquip debet quam nostra his mucius vel phasellus.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"8e7610aa-ff41-4a69-8961-3dd28e4479ab\",\n        \"userNickname\": \"@pertin\",\n        \"userFullName\": \"Ivory Ball\",\n        \"userAvatar\": \"file:///android_asset/avatars/94.jpg\",\n        \"text\": \"nobis suspendisse  conubia \\n🍁📿 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"525e6d3e-fcc8-4c3d-8fc5-c9b8cc1eda5e\",\n        \"userNickname\": \"@ponder\",\n        \"userFullName\": \"Darryl Woodard\",\n        \"userAvatar\": \"file:///android_asset/avatars/134.jpg\",\n        \"text\": \"fames vituperatoribus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"90ef0e14-eca5-40e9-b9c5-9b7b4fdc230d\",\n        \"userNickname\": \"@egesta\",\n        \"userFullName\": \"Alfredo Dejesus\",\n        \"userAvatar\": \"file:///android_asset/avatars/192.jpg\",\n        \"text\": \"🛋👩‍👧\\nposidonium justo\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c0045829-89e1-4350-84e4-f78313bfef0b\",\n        \"userNickname\": \"@singul\",\n        \"userFullName\": \"Danial Kirkland\",\n        \"userAvatar\": \"file:///android_asset/avatars/83.jpg\",\n        \"text\": \"🥃\\ngubergren omittam tota inimicus laoreet🔥 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b20b7e54-482f-49a6-8d6f-e2dd5fdc0d6d\",\n        \"userNickname\": \"@nisi\",\n        \"userFullName\": \"Stacy Cameron\",\n        \"userAvatar\": \"file:///android_asset/avatars/51.jpg\",\n        \"text\": \"🐱🔑🚮\\nVelitultrices donec usu atqui ponderum per cras wisi tortor etiam.  Causaeblandit primis invenire libero solet possit ne homero molestie autem convenire nostra sonet suscipiantur non omnesque delicata altera delenit.  Erataliquip labores expetenda moderatius disputationi repudiandae.  Decoremus viris pretium elaboraret numquam principes graece explicari.🚑 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774138,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f8d90b10-cca7-49cc-af6c-7a6d0a4cd4c7\",\n        \"userNickname\": \"@phasel\",\n        \"userFullName\": \"Desiree Pratt\",\n        \"userAvatar\": \"file:///android_asset/avatars/121.jpg\",\n        \"text\": \"praesent morbi  suscipiantur  🐫🍿 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374138,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636947174138,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"28fff591-27c3-47a4-bdd1-1ccb72c01799\",\n    \"userNickname\": \"@vel\",\n    \"userFullName\": \"Jerome Sandoval\",\n    \"userAvatar\": \"file:///android_asset/avatars/58.jpg\",\n    \"text\": \"📥🈂️ posidonium petentium dicant sed😨💧\\n\",\n    \"images\": [],\n    \"likes\": 25,\n    \"replyCount\": 145,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636950774138,\n    \"reply\": {\n      \"id\": \"528e10f8-0b29-4808-9bb6-2790979e092d\",\n      \"userNickname\": \"@fabell\",\n      \"userFullName\": \"Patti Hammond\",\n      \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n      \"text\": \"☕️🚫➡️\\nLeopossim vim suscipit eirmod intellegebat ipsum reprehendunt.  Possecum indoctum referrentur expetenda quis.📮🍇 \",\n      \"images\": [],\n      \"likes\": 973,\n      \"replyCount\": 696,\n      \"messages\": 6,\n      \"comments\": [\n        {\n          \"id\": \"f513574a-8453-4211-a07e-0d77465c6d21\",\n          \"userNickname\": \"@semper\",\n          \"userFullName\": \"Dale Howe\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"Aptentrecteque tale morbi menandri veri varius perpetua in litora accommodare definiebas et quot porta pulvinar.  Condimentumefficiantur mandamus mi tibique voluptaria prodesset dicam oporteat antiopam velit legere.  Prosolum quaeque arcu novum donec ipsum erroribus tempor utamur harum est quisque taciti ne patrioque saperet civibus delenit nihil.  Agamgubergren nisi ultricies doctus honestatis nascetur ancillae.  Quemperpetua efficitur quaeque neglegentur magnis accommodare lacus vivamus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174138,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"69fe2f6f-ba49-4924-ad86-ce9ef90e4f6b\",\n          \"userNickname\": \"@altera\",\n          \"userFullName\": \"Alden Head\",\n          \"userAvatar\": \"file:///android_asset/avatars/41.jpg\",\n          \"text\": \"🌹👾  primis  🦕🌘 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d027cf4e-d324-4edc-9c0a-b40e0b46f670\",\n          \"userNickname\": \"@suas\",\n          \"userFullName\": \"Jesus Mercado\",\n          \"userAvatar\": \"file:///android_asset/avatars/9.jpg\",\n          \"text\": \"🍗👩‍👦  maiorum \\n🚎 dolores autem  gravida \\n🍔🍛 🔋🐿  accumsan \\n🍭 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"5b175447-7c7d-44a2-8d74-987f44e25e58\",\n          \"userNickname\": \"@molest\",\n          \"userFullName\": \"Marty Jenkins\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"Veniamexpetendis eget mazim ne blandit inimicus accusata necessitatibus integer per.  Intellegatdui arcu feugiat decore vituperata maluisset suspendisse.  Causaemei postulant sollicitudin.  Himenaeoshonestatis condimentum ferri sadipscing dicit iuvaret malesuada usu vituperatoribus arcu.  Habeoturpis vel luctus dico constituam ac iusto tractatos ubique aliquip.  Eaodio quas petentium vivendo interesset himenaeos magna senectus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c22cd54a-ec70-4026-a163-d6f45d61a7cf\",\n          \"userNickname\": \"@mollis\",\n          \"userFullName\": \"Enrique Davenport\",\n          \"userAvatar\": \"file:///android_asset/avatars/89.jpg\",\n          \"text\": \"👽🥝🔢 an salutatus\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636979574139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"25b03045-79d4-4b75-8991-3743c11726c4\",\n          \"userNickname\": \"@ponder\",\n          \"userFullName\": \"Vance Underwood\",\n          \"userAvatar\": \"file:///android_asset/avatars/85.jpg\",\n          \"text\": \"consectetuer  civibus \\n㊗️ utinam  tempus  🙏🎄 💢🦉  iudicabit  🗒🚬 duo elitr  invidunt  🐥 quaeque  inimicus \\n💛🍩 🏯🐒  qualisque \\n🍱 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374139,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636950774138,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"0f204a16-eaac-41ad-86ac-d314a9646bd6\",\n    \"userNickname\": \"@vidiss\",\n    \"userFullName\": \"Clinton Moss\",\n    \"userAvatar\": \"file:///android_asset/avatars/168.jpg\",\n    \"text\": \"sapientem  maecenas \\n⚗️ \",\n    \"images\": [],\n    \"likes\": 743,\n    \"replyCount\": 596,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"48bca3dc-80bb-479a-bd6f-9ef277e29a07\",\n        \"userNickname\": \"@tamqua\",\n        \"userFullName\": \"Harriet Warner\",\n        \"userAvatar\": \"file:///android_asset/avatars/77.jpg\",\n        \"text\": \"👠⤴️  curae \\n🍑 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3537eeec-5592-463a-871c-4556ab763048\",\n        \"userNickname\": \"@socios\",\n        \"userFullName\": \"Glenna Fitzgerald\",\n        \"userAvatar\": \"file:///android_asset/avatars/0.jpg\",\n        \"text\": \"🎠🚧  legimus  😢 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"40fd3639-1c3d-446e-8cc6-ebff4fde00c0\",\n        \"userNickname\": \"@vocent\",\n        \"userFullName\": \"Cameron Galloway\",\n        \"userAvatar\": \"file:///android_asset/avatars/115.jpg\",\n        \"text\": \"Cumdignissim dolore nihil iriure interpretaris convenire consectetur netus utroque vehicula causae donec augue latine voluptaria option.  Hisodio graeco appareat phasellus suavitate viderer lacinia faucibus eos tempus metus dolor congue urna orci sapientem saperet.  Tritanisea voluptatibus inimicus adolescens quaestio mediocritatem graece quisque dictumst idque sale hendrerit civibus scelerisque.  Efficianturnoluisse detracto adipisci simul postulant moderatius eirmod penatibus at brute facilisi.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b69b620d-8111-4c17-a032-57a0cdc87131\",\n        \"userNickname\": \"@sonet\",\n        \"userFullName\": \"Elvin Calhoun\",\n        \"userAvatar\": \"file:///android_asset/avatars/191.jpg\",\n        \"text\": \"🍎🍥💁‍♂\\nnunc maecenas referrentur ultrices🍺🥤\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"fcd581c5-747e-4e49-ad03-77c5885cccac\",\n        \"userNickname\": \"@esse\",\n        \"userFullName\": \"Tami Mann\",\n        \"userAvatar\": \"file:///android_asset/avatars/148.jpg\",\n        \"text\": \"Vocentsit convenire.  Acper ludus atomorum lectus noster integer efficitur diam etiam habitant nulla blandit graece mei eum eget menandri decore.  Urnaadversarium singulis pulvinar melius discere tantas vivamus convenire deseruisse sit alia consectetur solum vocent euripidis mandamus diam laoreet nisl.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3104c2b7-0b56-44dc-9a6b-ccac9acc84c8\",\n        \"userNickname\": \"@urna\",\n        \"userFullName\": \"Bernadette Duran\",\n        \"userAvatar\": \"file:///android_asset/avatars/96.jpg\",\n        \"text\": \"fames pertinacia interesset docendi\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974139,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a1095a4b-520f-4df8-aba1-cf51974dc75c\",\n        \"userNickname\": \"@ea\",\n        \"userFullName\": \"Major Stein\",\n        \"userAvatar\": \"file:///android_asset/avatars/9.jpg\",\n        \"text\": \"Esterat dapibus ponderum dicunt vero epicurei quaestio diam vidisse vivamus usu.  Velnunc wisi lorem saepe deseruisse mauris.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174139,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636950774139,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1113,\n      \"positions\": [\n        {\n          \"text\": \"vis moderatius minim\",\n          \"voted\": 483\n        },\n        {\n          \"text\": \"persius vivamus risus falli\",\n          \"voted\": 337\n        },\n        {\n          \"text\": \"non\",\n          \"voted\": 222\n        },\n        {\n          \"text\": \"duis semper\",\n          \"voted\": 29\n        },\n        {\n          \"text\": \"nulla sumo\",\n          \"voted\": 42\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"8331cffc-c0d8-46b0-b0ee-56bd4b0f5309\",\n    \"userNickname\": \"@legimu\",\n    \"userFullName\": \"Evangeline Ratliff\",\n    \"userAvatar\": \"file:///android_asset/avatars/59.jpg\",\n    \"text\": \"periculis ferri\",\n    \"images\": [],\n    \"likes\": 3,\n    \"replyCount\": 5,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636947174139,\n    \"reply\": {\n      \"id\": \"c4920f4e-868e-43f9-a39b-e096885b06b3\",\n      \"userNickname\": \"@eu\",\n      \"userFullName\": \"Susie Huffman\",\n      \"userAvatar\": \"file:///android_asset/avatars/100.jpg\",\n      \"text\": \"Porroneque dictum inciderint ut scripta rhoncus lacinia mnesarchum elitr evertitur sapientem blandit.  Maluissethabemus postulant neque pertinacia pericula maiorum appetere.  Maximuseloquentiam sapien detraxit graeco proin commodo no ea agam nostra no luctus sale.  Doloressenserit nisl ultricies faucibus nisl habitasse posidonium vim repudiandae constituam reprimique dis tristique theophrastus accommodare augue class ludus.\",\n      \"images\": [],\n      \"likes\": 554,\n      \"replyCount\": 675,\n      \"messages\": 9,\n      \"comments\": [\n        {\n          \"id\": \"84a22d2a-2e4a-4feb-b989-5be2c5c3e91f\",\n          \"userNickname\": \"@mollis\",\n          \"userFullName\": \"Dave Sharpe\",\n          \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n          \"text\": \"tristique\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4e63f4e7-fecf-45f0-9b6f-811be166a19e\",\n          \"userNickname\": \"@labore\",\n          \"userFullName\": \"Abe Black\",\n          \"userAvatar\": \"file:///android_asset/avatars/42.jpg\",\n          \"text\": \"Meaviverra urna donec velit odio quas auctor prompta intellegebat quis mauris pri option nunc nihil.  Domingmenandri consectetur finibus meliore solet malesuada habitant suscipit feugiat invenire iaculis.  Elitnominavi saperet veniam.  Persecutiindoctum suscipit tractatos cu nam dolore maluisset non sonet fermentum usu appetere quod.  Imperdietlegere no proin quisque pretium impetus feugait duis interdum tota iudicabit finibus invenire quas ultrices ancillae brute.  Magnislobortis at tamquam viderer intellegebat sapien dictas ponderum inciderint assueverit tractatos suscipit invenire mauris eripuit.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"e2a68f71-8a4b-43e0-94ec-8893bae1e88d\",\n          \"userNickname\": \"@magna\",\n          \"userFullName\": \"Dick Potts\",\n          \"userAvatar\": \"file:///android_asset/avatars/194.jpg\",\n          \"text\": \"delicata ne  expetenda \\n🚡👨‍🍳 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"32a240d5-a71d-4e1a-8ba7-2b983ef27ac6\",\n          \"userNickname\": \"@errem\",\n          \"userFullName\": \"Israel Barrett\",\n          \"userAvatar\": \"file:///android_asset/avatars/161.jpg\",\n          \"text\": \"porttitor meliore  falli \\n⛑ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"5bc1098f-7fcf-4dfb-b5e8-dd51b97246b3\",\n          \"userNickname\": \"@praese\",\n          \"userFullName\": \"Blanca Leblanc\",\n          \"userAvatar\": \"file:///android_asset/avatars/49.jpg\",\n          \"text\": \"nec comprehensam finibus omittantur nostra\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"1850136a-2bbe-4769-9020-5af194438519\",\n          \"userNickname\": \"@omitta\",\n          \"userFullName\": \"Annabelle Roth\",\n          \"userAvatar\": \"file:///android_asset/avatars/152.jpg\",\n          \"text\": \"minim quidam\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"0fa96951-6356-4bf5-892c-69355e79a23a\",\n          \"userNickname\": \"@dolor\",\n          \"userFullName\": \"Marcy Douglas\",\n          \"userAvatar\": \"file:///android_asset/avatars/133.jpg\",\n          \"text\": \"utroque  porro  🚟🌏 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"77b92719-deb2-4d5c-b81e-972fa6565eba\",\n          \"userNickname\": \"@ius\",\n          \"userFullName\": \"Sophie Boyd\",\n          \"userAvatar\": \"file:///android_asset/avatars/46.jpg\",\n          \"text\": \"impetus ac singulis causae habitasse\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774139,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6ba5a6c8-90e7-49d1-8600-8a4f8a20617f\",\n          \"userNickname\": \"@veri\",\n          \"userFullName\": \"Willard Santos\",\n          \"userAvatar\": \"file:///android_asset/avatars/13.jpg\",\n          \"text\": \"🚌🐖  quisque  👖 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636997574140,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636939974139,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"10da7f9c-6f5c-41f5-a83a-609a04160c43\",\n    \"userNickname\": \"@faucib\",\n    \"userFullName\": \"Briana Lloyd\",\n    \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n    \"text\": \"🥔😋  ac  🏩 \",\n    \"images\": [],\n    \"likes\": 93,\n    \"replyCount\": 124,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636979574140,\n    \"reply\": {\n      \"id\": \"fd9cac03-da54-4443-b4f9-9a8bdd9c3a05\",\n      \"userNickname\": \"@habita\",\n      \"userFullName\": \"Elba Pickett\",\n      \"userAvatar\": \"file:///android_asset/avatars/146.jpg\",\n      \"text\": \"2️⃣🛰\\nPetentiumnatum conclusionemque eruditi debet hac vulputate prodesset fuisset adversarium potenti harum nisl dissentiunt integer senectus appareat.  Tantasmenandri ex inani scelerisque mel tacimates dico metus oporteat urna sit utroque elaboraret corrumpit.  Ubiquedicit adolescens meliore animal viderer vitae viris doming ignota elit vulputate finibus posuere metus vis habitant maiestatis.  Persiusdicam cum.  Justoagam constituam id constituam persecuti agam fringilla posidonium facilisi quam voluptaria aliquet tota aliquet detracto nascetur tritani.🥝\\n\",\n      \"images\": [\n        \"file:///android_asset/post_images/79.jpg\"\n      ],\n      \"likes\": 372,\n      \"replyCount\": 603,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"39de84a4-7fc0-4648-96a6-3139707969c9\",\n          \"userNickname\": \"@vocibu\",\n          \"userFullName\": \"Harriett Pennington\",\n          \"userAvatar\": \"file:///android_asset/avatars/122.jpg\",\n          \"text\": \"eros  nominavi \\n🚞🔢 utroque  rutrum \\n🥖 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774140,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c285bab5-9029-4efa-8901-ee5f356d63d7\",\n          \"userNickname\": \"@enim\",\n          \"userFullName\": \"Lillie Underwood\",\n          \"userAvatar\": \"file:///android_asset/avatars/39.jpg\",\n          \"text\": \"tempor mi\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774140,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636972374140,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"f17772ac-6203-47a4-b7ac-0f3b99bc7789\",\n    \"userNickname\": \"@offend\",\n    \"userFullName\": \"Lucinda Ochoa\",\n    \"userAvatar\": \"file:///android_asset/avatars/126.jpg\",\n    \"text\": \"Tamquamsigniferumque reque altera tacimates vivamus singulis.  Utfeugait graeci.  Puruspossit class mediocritatem conceptam.  Alteravituperata meliore pharetra qui nonumes scelerisque putent pellentesque aliquet.\",\n    \"images\": [],\n    \"likes\": 48,\n    \"replyCount\": 60,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"23d1f7b4-6666-42e6-8213-3dbf9ac53df2\",\n        \"userNickname\": \"@consul\",\n        \"userFullName\": \"Miguel Quinn\",\n        \"userAvatar\": \"file:///android_asset/avatars/12.jpg\",\n        \"text\": \"Congueaenean dolorem intellegat vestibulum dicat explicari atqui legimus noluisse moderatius ornare dignissim delectus.  Luduscondimentum pri delectus scripta adversarium causae affert urna.  Tinciduntaccommodare fastidii dignissim bibendum accommodare duo nisi eirmod partiendo verear oporteat vivendo.  Tellusepicuri alienum varius odio deserunt primis erat dico neque eu.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"98c74ce3-9680-47e6-9f70-54062ac128e0\",\n        \"userNickname\": \"@lacus\",\n        \"userFullName\": \"Marion McMahon\",\n        \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n        \"text\": \"🌳🍹🕌 Pulvinarinterpretaris erat persius at consequat omnesque qui erroribus cras odio suscipiantur consequat habitasse metus.  Nisirhoncus decore fusce cu voluptatum reformidans pro honestatis utamur justo habitasse.  Iaculisgubergren viderer affert pertinacia taciti proin graeco vero omittam necessitatibus appareat ludus dissentiunt fringilla ac verear.  Impetusaptent detracto vehicula.⛲️📪\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d4dac04c-9051-4e4c-a7a1-829816ff5b24\",\n        \"userNickname\": \"@platea\",\n        \"userFullName\": \"Phillip Gates\",\n        \"userAvatar\": \"file:///android_asset/avatars/137.jpg\",\n        \"text\": \"🔐🎐🕳\\nProdicta expetendis dicunt verear mauris ponderum ligula inciderint mnesarchum duis deterruisset dico et netus interesset dictumst lacinia vehicula sapien.  Delenitvoluptaria placerat conceptam definitiones quaestio tincidunt nascetur turpis mnesarchum.  Malorumminim finibus comprehensam persequeris possim ut eum sit rhoncus tritani numquam consectetuer vulputate.  Viverracurabitur neglegentur faucibus sociis sollicitudin pretium nibh voluptatum consul.  Splendidemauris urna in noluisse est consectetuer litora purus similique convenire moderatius iudicabit te mucius.  Requeadolescens sodales est hendrerit.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d2b3e991-e1e1-4e83-bfbe-ea9317fc2d27\",\n        \"userNickname\": \"@dicat\",\n        \"userFullName\": \"Dionne Sparks\",\n        \"userAvatar\": \"file:///android_asset/avatars/76.jpg\",\n        \"text\": \"👨‍💻🚍  consectetuer \\n📎👻 💭🚎  debet \\n🛁🍔 🐜🚗  habitasse \\n👨‍👨‍👦🏤 🥨🥑  qui  🏗🗺 partiendo litora  sodales \\n🥂✂️ verterem ubique  penatibus \\n🍘🐄 🥠🍚  sumo \\n⛩ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"af479216-0b7c-4020-802f-81f8f5d86b97\",\n        \"userNickname\": \"@conubi\",\n        \"userFullName\": \"Teddy Cameron\",\n        \"userAvatar\": \"file:///android_asset/avatars/121.jpg\",\n        \"text\": \"Necnatoque commodo deterruisset aliquam urbanitas gubergren vix quis ligula hac litora nisi quis massa vocent.  Tinciduntfugit taciti luctus veniam nullam rutrum posidonium ultrices evertitur pulvinar suavitate laudem himenaeos.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"91fc76c7-878d-4283-b756-d4fdbaddf44f\",\n        \"userNickname\": \"@perpet\",\n        \"userFullName\": \"Weldon Lowe\",\n        \"userAvatar\": \"file:///android_asset/avatars/184.jpg\",\n        \"text\": \"varius a\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374140,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ccd5af6a-98d3-4d05-a9dc-066be25cd99b\",\n        \"userNickname\": \"@prompt\",\n        \"userFullName\": \"Hung Freeman\",\n        \"userAvatar\": \"file:///android_asset/avatars/45.jpg\",\n        \"text\": \"contentiones doming\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174140,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636983174140,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"0b502b83-9275-4a5c-b70f-bb0c2999ce16\",\n    \"userNickname\": \"@mus\",\n    \"userFullName\": \"Carl Wilcox\",\n    \"userAvatar\": \"file:///android_asset/avatars/129.jpg\",\n    \"text\": \"senserit has  theophrastus  🏜 \",\n    \"images\": [],\n    \"likes\": 1,\n    \"replyCount\": 177,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636939974140,\n    \"reply\": {\n      \"id\": \"406524b9-afb4-46e6-a051-e6e7de730909\",\n      \"userNickname\": \"@inani\",\n      \"userFullName\": \"Tessa Wong\",\n      \"userAvatar\": \"file:///android_asset/avatars/169.jpg\",\n      \"text\": \"🖨 Conubiavivamus intellegebat detraxit pretium latine volumus novum hinc vituperata aenean.  Nihildicta deserunt convenire nulla congue leo detraxit urna repudiandae mauris fuisset sagittis nulla melius quidam putent leo quidam.  Adsenectus quaerendum voluptaria justo errem est contentiones molestie accusata iriure placerat dicunt felis.  Melioredetracto explicari interpretaris quidam mea indoctum oratio periculis nostra nobis partiendo duis auctor aliquip fugit.🔏🚃 \",\n      \"images\": [],\n      \"likes\": 30,\n      \"replyCount\": 600,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"1cbb7d1f-0fc0-490f-9ec1-05140c8fc7f9\",\n          \"userNickname\": \"@dicit\",\n          \"userFullName\": \"Lazaro Schroeder\",\n          \"userAvatar\": \"file:///android_asset/avatars/85.jpg\",\n          \"text\": \"🚀🚆  cetero  🏭 ✒️📟  antiopam \\n📓 🥖👩‍🔧  legimus \\n🐺💻 🍸🥜  mea  🚮👨‍👧 🎂🎐  dicam \\n😜 🍷🍔  melius  🏤 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174140,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d68fcaf3-e938-496b-a265-76f426b2f861\",\n          \"userNickname\": \"@vivend\",\n          \"userFullName\": \"Deena Chen\",\n          \"userAvatar\": \"file:///android_asset/avatars/14.jpg\",\n          \"text\": \"Attale iuvaret eget.  Comprehensamnominavi regione nullam cetero consectetur no suas populo postea scelerisque luptatum.  Elementumgraece recteque an vix.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774140,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c6f9c7c0-e41a-4fd4-8712-798954875cfc\",\n          \"userNickname\": \"@imperd\",\n          \"userFullName\": \"Rosanne Hanson\",\n          \"userAvatar\": \"file:///android_asset/avatars/30.jpg\",\n          \"text\": \"tempus eos eam quaerendum enim\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174140,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636929174140,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"33dfd376-73b7-40ba-af5f-ad72ce7f851c\",\n    \"userNickname\": \"@suscip\",\n    \"userFullName\": \"Cornelius Flowers\",\n    \"userAvatar\": \"file:///android_asset/avatars/102.jpg\",\n    \"text\": \"percipit animal detraxit evertitur\",\n    \"images\": [],\n    \"likes\": 78,\n    \"replyCount\": 75,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636997574140,\n    \"reply\": {\n      \"id\": \"1e0d53c7-8775-4beb-afe9-4b20ea686b07\",\n      \"userNickname\": \"@metus\",\n      \"userFullName\": \"Melba Macias\",\n      \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n      \"text\": \"pertinacia ponderum nostra\",\n      \"images\": [],\n      \"likes\": 650,\n      \"replyCount\": 377,\n      \"messages\": 8,\n      \"comments\": [\n        {\n          \"id\": \"87bcdeb5-f1f6-4172-8ddb-ae18a7b84b01\",\n          \"userNickname\": \"@rhoncu\",\n          \"userFullName\": \"Ned Everett\",\n          \"userAvatar\": \"file:///android_asset/avatars/122.jpg\",\n          \"text\": \"Comprehensamdetraxit reque ludus bibendum postulant.  Consecteturposuere reprimique ridiculus tortor alia nunc necessitatibus.  Arcuposuere pellentesque ornare alia inimicus sociosqu amet vix.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174140,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f6f3ee88-9920-4967-97eb-9e84b7858de9\",\n          \"userNickname\": \"@ipsum\",\n          \"userFullName\": \"Tisha Burt\",\n          \"userAvatar\": \"file:///android_asset/avatars/116.jpg\",\n          \"text\": \"🍼🥂🖱 graecis💾 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4f9030e7-c771-4739-a250-b4155042f6d6\",\n          \"userNickname\": \"@torque\",\n          \"userFullName\": \"Alfonso Vasquez\",\n          \"userAvatar\": \"file:///android_asset/avatars/51.jpg\",\n          \"text\": \"Pulvinarvis fermentum libero nostrum menandri suavitate dui altera posse dictum viderer meliore.  Fusceridiculus dictum debet quas eos veri consetetur dolorum ante natoque verterem instructior homero a venenatis fames voluptatum sea.  Percipitmaiorum a commune solet sea vitae quot gravida.  Consectetuersodales taciti dicunt porta odio amet solum montes litora tractatos ullamcorper pharetra in diam.  Orcisplendide accusata diam repudiare cras veri accommodare iriure persequeris commodo oratio elementum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8aa56329-85ea-47f2-a2dc-6cf5addacf33\",\n          \"userNickname\": \"@fugit\",\n          \"userFullName\": \"Geneva Montoya\",\n          \"userAvatar\": \"file:///android_asset/avatars/88.jpg\",\n          \"text\": \"recteque deseruisse  dapibus \\n🌆 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"32ba2a66-82e2-4d7c-b3b8-5e10d6d57cf3\",\n          \"userNickname\": \"@aliqui\",\n          \"userFullName\": \"Barney Carlson\",\n          \"userAvatar\": \"file:///android_asset/avatars/167.jpg\",\n          \"text\": \"📕\\nduis ante indoctum \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"08ac4c53-939d-4e34-88bf-0f179595246c\",\n          \"userNickname\": \"@prompt\",\n          \"userFullName\": \"Everett Franks\",\n          \"userAvatar\": \"file:///android_asset/avatars/189.jpg\",\n          \"text\": \"aliquip alienum  posidonium  🚫 🚐🐥  primis \\n😎 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"60f10bfd-7073-491b-902a-97a330384557\",\n          \"userNickname\": \"@deseru\",\n          \"userFullName\": \"Sonny Gutierrez\",\n          \"userAvatar\": \"file:///android_asset/avatars/16.jpg\",\n          \"text\": \"numquam ultricies consetetur\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974141,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a782f8de-ae8d-468c-8cd4-929e8bf7a93f\",\n          \"userNickname\": \"@omnesq\",\n          \"userFullName\": \"Dustin Roach\",\n          \"userAvatar\": \"file:///android_asset/avatars/193.jpg\",\n          \"text\": \"Egetunum laudem volumus diam vestibulum.  Pertinaciaquas latine condimentum nonumes iisque adipiscing dolores gubergren per ludus senserit nisl porro vidisse fringilla imperdiet orci vero.  Patrioquedonec mucius.  Turpisquas maiestatis nostra purus dicant penatibus fabellas senectus blandit tincidunt lacus minim maiorum ancillae mi ius.  Menandriverterem ex urna odio postea aliquet enim dui splendide eros accusata inceptos venenatis vel.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174141,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636986774140,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": false,\n        \"totalVotes\": 844,\n        \"positions\": [\n          {\n            \"text\": \"maiorum agam intellegat porta ridiculus\",\n            \"voted\": 245\n          },\n          {\n            \"text\": \"odio vim docendi\",\n            \"voted\": 201\n          },\n          {\n            \"text\": \"commune similique mauris\",\n            \"voted\": 398\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"882f0c17-c45b-4fdf-9fd3-cb1b4ca36ece\",\n    \"userNickname\": \"@sit\",\n    \"userFullName\": \"Carlene Jimenez\",\n    \"userAvatar\": \"file:///android_asset/avatars/198.jpg\",\n    \"text\": \"his  delectus \\n✒️ 🍧🎍  sollicitudin \\n🌹 tractatos impetus  curabitur \\n📆 ‼️🔚  singulis \\n🍨🤰 🎄▪️  qui \\n🍧 adversarium quam  audire  🛠🕶 pretium ponderum  amet \\n🔯 🍃🌲  vivendo \\n🤘🐆 auctor mediocrem  mediocrem  🚅🍳 quaeque  constituam \\n🦀 \",\n    \"images\": [\n      \"file:///android_asset/post_images/70.jpg\"\n    ],\n    \"likes\": 632,\n    \"replyCount\": 642,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637001174141,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"43649f12-5aae-4f2f-a69f-c025aa630208\",\n    \"userNickname\": \"@ultric\",\n    \"userFullName\": \"Erna Barker\",\n    \"userAvatar\": \"file:///android_asset/avatars/136.jpg\",\n    \"text\": \"dicat  convenire  ➰🍱 novum utroque  malorum  🛋 🐻🍽  euripidis  😻 fabulas dignissim  petentium \\n💂‍♀ lobortis  interpretaris  🏍 😟↘️  ultricies \\n☘️ \",\n    \"images\": [\n      \"file:///android_asset/post_images/80.jpg\"\n    ],\n    \"likes\": 779,\n    \"replyCount\": 31,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"066907ab-7431-476e-8eff-b2ad6eb5c207\",\n        \"userNickname\": \"@volupt\",\n        \"userFullName\": \"Trina Torres\",\n        \"userAvatar\": \"file:///android_asset/avatars/108.jpg\",\n        \"text\": \"Promptahabemus dignissim mauris aliquet menandri ancillae lectus persius definitiones aenean menandri integer fringilla.  Litoraconvallis offendit donec conclusionemque habitasse lorem vehicula vulputate nonumes esse enim ignota malorum.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a2e468a3-261c-43cb-88a3-267f72e6fb3d\",\n        \"userNickname\": \"@natoqu\",\n        \"userFullName\": \"Reggie Ayala\",\n        \"userAvatar\": \"file:///android_asset/avatars/143.jpg\",\n        \"text\": \"Percipitsale contentiones quod petentium sed orci pulvinar vero adhuc volutpat oporteat facilis malesuada ancillae rhoncus vidisse sagittis.  Suscipianturcu convenire maecenas propriae ne luptatum aenean veniam mandamus ubique invenire volumus placerat cetero reformidans causae.  Vivendomolestie netus conubia iudicabit.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"7a07b862-ba30-4be1-b448-7f8237afc5ca\",\n        \"userNickname\": \"@alique\",\n        \"userFullName\": \"Lakeisha Acevedo\",\n        \"userAvatar\": \"file:///android_asset/avatars/22.jpg\",\n        \"text\": \"voluptatum discere  singulis \\n✅🚮 🍐🎑  pharetra \\n🌎🖨 voluptatum nostra  ridens \\n🏯 🐠☦️  idque \\n🍼 evertitur facilisis  an \\n📭 🙋‍♂🔵  fames  ☀️🐠 definiebas  inani  🈳 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"37f3c683-cd0c-44d9-b6b5-f36f9ec68a8b\",\n        \"userNickname\": \"@mei\",\n        \"userFullName\": \"Mandy Cash\",\n        \"userAvatar\": \"file:///android_asset/avatars/126.jpg\",\n        \"text\": \"🏔🖼  impetus \\n🍸 nibh pericula  assueverit  🕯🈺 urna  phasellus \\n📻✍️ 🍵🙈  brute \\n🌿🎊 curabitur adversarium  maximus \\n👂 vitae tellus  labores  🎶 🥝🐲  nullam \\n🚤🍘 🐖🥥  intellegat \\n🔌👢 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"04fe0558-32cc-4dac-b427-da11df8f15ad\",\n        \"userNickname\": \"@laudem\",\n        \"userFullName\": \"Zane Buck\",\n        \"userAvatar\": \"file:///android_asset/avatars/182.jpg\",\n        \"text\": \"Posteaquam ex natum.  Nonumesinteger nostra impetus dui eirmod.  Quemeius contentiones sit phasellus natum aperiri pretium possit lacus ne velit ludus noluisse curae referrentur.  Torquentlatine constituam torquent netus atqui euripidis consectetur gloriatur falli vidisse torquent eruditi falli tractatos phasellus.  Feugiatelectram consectetur constituto definitiones appareat.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d5117f35-fd71-4346-b708-3b990914ebd1\",\n        \"userNickname\": \"@ligula\",\n        \"userFullName\": \"Guy Pope\",\n        \"userAvatar\": \"file:///android_asset/avatars/82.jpg\",\n        \"text\": \"🔨🍖♏️\\nappetere platea proin graeco🥤\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174141,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"741f6bc1-af3a-4dfb-8747-44ab5945dec9\",\n        \"userNickname\": \"@possit\",\n        \"userFullName\": \"Sandy McConnell\",\n        \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n        \"text\": \"📔 et ne minim🛬🍡\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574141,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636979574141,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"d4ec2883-db79-4938-b1d0-7e2e52dc4c6b\",\n    \"userNickname\": \"@posuer\",\n    \"userFullName\": \"Santos Casey\",\n    \"userAvatar\": \"file:///android_asset/avatars/6.jpg\",\n    \"text\": \"💠🍿  gloriatur  ⚖️ \",\n    \"images\": [],\n    \"likes\": 28,\n    \"replyCount\": 93,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974141,\n    \"reply\": {\n      \"id\": \"e53b1e35-a636-46ee-afdf-6718281847d1\",\n      \"userNickname\": \"@tale\",\n      \"userFullName\": \"Virgie Orr\",\n      \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n      \"text\": \"📒🔓  tacimates \\n🚶👩‍👧‍👦 sed  curae \\n📃🍴 😹🚿  fastidii  👡🚚 nihil  ad  ❗️🥐 mus natoque  prodesset \\n🦁🚥 aliquid fastidii  mediocritatem  🐹 porro dictum  parturient  🍜 ocurreret dolor  brute  🍟⛑ 🖥🚜  putent  🦉🌻 \",\n      \"images\": [\n        \"file:///android_asset/post_images/39.jpg\",\n        \"file:///android_asset/post_images/84.jpg\"\n      ],\n      \"likes\": 969,\n      \"replyCount\": 492,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"1a30fa9f-1782-4788-a15f-35d12b14a356\",\n          \"userNickname\": \"@facili\",\n          \"userFullName\": \"Maritza Wade\",\n          \"userAvatar\": \"file:///android_asset/avatars/97.jpg\",\n          \"text\": \"▫️4️⃣  feugait \\n🥨 🏍🎛  luptatum  👩‍🎓🎠 ornare  sagittis  🦉 velit eros  pharetra  🌌 🐇🥃  vituperata \\n🍰🕤 dis viris  theophrastus \\n🌱 gubergren scripta  dictumst \\n😈 usu rutrum  nisl  🖇 placerat vehicula  fuisset \\n🦇 🏷🗺  maximus \\n💫👡 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"da81674d-99e0-4063-9ce8-9cad3e5d6873\",\n          \"userNickname\": \"@fames\",\n          \"userFullName\": \"Estelle Serrano\",\n          \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n          \"text\": \"volumus\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"ae56d4e7-92db-4e95-a10c-d1c544cf0828\",\n          \"userNickname\": \"@iuvare\",\n          \"userFullName\": \"Ophelia Cain\",\n          \"userAvatar\": \"file:///android_asset/avatars/178.jpg\",\n          \"text\": \"movet nostra urna\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636986774142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"1500eb3d-3a1b-48af-86bc-f6c9ca47b3d6\",\n          \"userNickname\": \"@ante\",\n          \"userFullName\": \"Claude Miles\",\n          \"userAvatar\": \"file:///android_asset/avatars/158.jpg\",\n          \"text\": \"Inan ligula quaerendum ornatus error epicuri.  Efficiturconsetetur erat graecis.  Vivendotation habitant iuvaret magna has nonumes aptent offendit pertinacia finibus lacinia inceptos agam.  Delectusinteger vocibus verear fermentum nostra suavitate volutpat.  Ultriciesmaecenas ponderum etiam doming singulis offendit audire elit comprehensam dolorem dicat nihil quisque.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174142,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637011974141,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"9025145c-b0a3-4512-b945-9388214e2f5d\",\n    \"userNickname\": \"@ridicu\",\n    \"userFullName\": \"Lamar Dejesus\",\n    \"userAvatar\": \"file:///android_asset/avatars/41.jpg\",\n    \"text\": \"🍓🌭  voluptatum  🔍📩 \",\n    \"images\": [],\n    \"likes\": 32,\n    \"replyCount\": 183,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636950774142,\n    \"reply\": {\n      \"id\": \"ccb24402-503b-4b80-9311-9eb219c66036\",\n      \"userNickname\": \"@libris\",\n      \"userFullName\": \"Sue Kim\",\n      \"userAvatar\": \"file:///android_asset/avatars/45.jpg\",\n      \"text\": \"🚷🤳\\nhac doctus aeque cu👩‍🚀\\n\",\n      \"images\": [],\n      \"likes\": 600,\n      \"replyCount\": 395,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"02ebc5eb-37de-4048-ab73-f37072508835\",\n          \"userNickname\": \"@tristi\",\n          \"userFullName\": \"Lolita Buchanan\",\n          \"userAvatar\": \"file:///android_asset/avatars/63.jpg\",\n          \"text\": \"🦓🥫🚑 diam🛳\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"62992f59-2cef-4efc-bb43-27aa46763ef0\",\n          \"userNickname\": \"@massa\",\n          \"userFullName\": \"Bryce Pratt\",\n          \"userAvatar\": \"file:///android_asset/avatars/184.jpg\",\n          \"text\": \"feugait electram vitae\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574142,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636943574142,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 818,\n        \"positions\": [\n          {\n            \"text\": \"principes\",\n            \"voted\": 22\n          },\n          {\n            \"text\": \"erroribus solet\",\n            \"voted\": 349\n          },\n          {\n            \"text\": \"melius dicunt\",\n            \"voted\": 292\n          },\n          {\n            \"text\": \"torquent\",\n            \"voted\": 155\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"04566780-7349-4744-8962-09b0e55581b1\",\n    \"userNickname\": \"@condim\",\n    \"userFullName\": \"Marquis Fernandez\",\n    \"userAvatar\": \"file:///android_asset/avatars/101.jpg\",\n    \"text\": \"Cubiliaatqui doming scripta deserunt petentium pertinacia eget suas dicat iriure an nullam taciti legimus omnesque mandamus.  Nibhalienum ea referrentur.  Mediocritatemalia sagittis ligula eius aliquid dicta.\",\n    \"images\": [\n      \"file:///android_asset/post_images/35.jpg\",\n      \"file:///android_asset/post_images/87.jpg\"\n    ],\n    \"likes\": 310,\n    \"replyCount\": 250,\n    \"messages\": 4,\n    \"comments\": [\n      {\n        \"id\": \"e871ba39-be6f-4a66-a684-bb5046ac381e\",\n        \"userNickname\": \"@eripui\",\n        \"userFullName\": \"Antwan Rocha\",\n        \"userAvatar\": \"file:///android_asset/avatars/169.jpg\",\n        \"text\": \"intellegat integer\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574142,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"44ace42b-1ed3-4c57-9525-b52cf78ceca8\",\n        \"userNickname\": \"@erat\",\n        \"userFullName\": \"Dewayne Taylor\",\n        \"userAvatar\": \"file:///android_asset/avatars/147.jpg\",\n        \"text\": \"Tantasin tempus propriae sententiae metus ludus.  Suasagam vel singulis tantas morbi vestibulum postulant elit egestas.  Sollicitudindignissim definiebas class conubia idque labores tamquam ac mus.  Definitionemequidem sodales luctus idque.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374142,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"88150b01-5086-4040-b148-17ff7e4e0b74\",\n        \"userNickname\": \"@tristi\",\n        \"userFullName\": \"Flossie Beach\",\n        \"userAvatar\": \"file:///android_asset/avatars/53.jpg\",\n        \"text\": \"debet posse  doming \\n🐉 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774142,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"903c2871-7de4-4624-aa7e-7b2425c11491\",\n        \"userNickname\": \"@platon\",\n        \"userFullName\": \"Faye Dickerson\",\n        \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n        \"text\": \"🏬🌃\\nubique populo audire falli😄\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774142,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636993974142,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"ec6c461e-389a-4478-92b7-4da8a1c5abc8\",\n    \"userNickname\": \"@maiest\",\n    \"userFullName\": \"Fred Sherman\",\n    \"userAvatar\": \"file:///android_asset/avatars/55.jpg\",\n    \"text\": \"🙅‍♂ wisi consequat numquam affert altera🌶\\n\",\n    \"images\": [],\n    \"likes\": 71,\n    \"replyCount\": 18,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374142,\n    \"reply\": {\n      \"id\": \"e0c7fb71-3bc0-4c45-80cd-595af01bd0e9\",\n      \"userNickname\": \"@possit\",\n      \"userFullName\": \"Chelsea Ray\",\n      \"userAvatar\": \"file:///android_asset/avatars/90.jpg\",\n      \"text\": \"Rhoncusurbanitas mnesarchum vituperatoribus theophrastus.  Principesverterem euripidis vel scelerisque errem tempus viris sapien vituperatoribus graeco graeco nostrum.  Iaculisinimicus ei lectus augue invidunt hendrerit.  Perpetuasapien euismod magna docendi.  Fuscealiquid delenit maiorum aeque.  Eosex sociosqu duo ex magnis contentiones meliore urbanitas utinam eu consectetur a recteque fermentum solet ut electram tortor luctus.\",\n      \"images\": [\n        \"file:///android_asset/post_images/52.jpg\",\n        \"file:///android_asset/post_images/83.jpg\",\n        \"file:///android_asset/post_images/67.jpg\"\n      ],\n      \"likes\": 635,\n      \"replyCount\": 369,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"9301ac2b-0c24-4ed5-8df1-c727bab35b29\",\n          \"userNickname\": \"@posse\",\n          \"userFullName\": \"Darrin Pittman\",\n          \"userAvatar\": \"file:///android_asset/avatars/165.jpg\",\n          \"text\": \"Euismodreferrentur penatibus purus ridens utinam efficiantur ludus expetenda nulla deserunt definiebas animal semper vehicula brute disputationi graecis.  Erremprimis cum meliore malorum ullamcorper maluisset fastidii rhoncus an.  Tinciduntpostea deterruisset splendide nonumes penatibus senserit cetero tristique mauris affert sociosqu maluisset cu luptatum efficitur nihil alienum aenean.  Mollismucius ancillae epicuri duo malesuada reque possit conclusionemque ferri dolore postea quod volumus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"402009de-c5ac-4626-bd54-c8d0e228e4db\",\n          \"userNickname\": \"@quaere\",\n          \"userFullName\": \"Mercedes Gutierrez\",\n          \"userAvatar\": \"file:///android_asset/avatars/26.jpg\",\n          \"text\": \"♐️ Deterruissetmontes vivendo iuvaret an risus liber aeque suscipit suscipit an quot elementum diam fugit noster tortor parturient imperdiet delectus.  Moderatiusdis quaeque cetero lorem aliquam ancillae esse quod dissentiunt utroque affert alterum dolorum mi fusce possit vituperata consequat aliquid.🍬 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f38ea4d3-9d1b-4e96-b357-912a99cef2d3\",\n          \"userNickname\": \"@offend\",\n          \"userFullName\": \"Ramiro Todd\",\n          \"userAvatar\": \"file:///android_asset/avatars/97.jpg\",\n          \"text\": \"🛣🥞  donec \\n🐃 deterruisset  nulla \\n⛪️✖️ praesent  quam  📸👽 eget fastidii  fermentum \\n🚇 morbi equidem  putent  🎵 ㊗️⬆️  vivendo  ☪️🥐 🅱️⛴  intellegat \\n🖨 omittantur  agam \\n🥦🛒 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374142,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"69842a69-0ec6-4cab-ac16-8ca0810c62fd\",\n          \"userNickname\": \"@per\",\n          \"userFullName\": \"Edwardo Carver\",\n          \"userAvatar\": \"file:///android_asset/avatars/189.jpg\",\n          \"text\": \"repudiare te intellegat\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636997574142,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636979574142,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"bca5332c-0f82-4bdb-b434-941fbe0841b5\",\n    \"userNickname\": \"@graeci\",\n    \"userFullName\": \"Laurence Prince\",\n    \"userAvatar\": \"file:///android_asset/avatars/26.jpg\",\n    \"text\": \"no\",\n    \"images\": [],\n    \"likes\": 64,\n    \"replyCount\": 94,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636936374143,\n    \"reply\": {\n      \"id\": \"7cad5fe5-e364-4e42-af0c-8d8d712d3d0a\",\n      \"userNickname\": \"@legimu\",\n      \"userFullName\": \"Darrin Solis\",\n      \"userAvatar\": \"file:///android_asset/avatars/104.jpg\",\n      \"text\": \"Ubiquetristique ludus ad atqui expetendis consectetur tractatos velit fastidii aperiri.  Sapientemsuspendisse feugait eget amet prodesset quot vulputate partiendo sententiae nec felis nihil curae reque cras eruditi potenti tacimates malorum.  Doctusdoctus platea urna donec.  Corrumpitconstituto contentiones cum natoque id contentiones ferri feugiat egestas quem netus scripta vis corrumpit auctor vehicula.\",\n      \"images\": [\n        \"file:///android_asset/post_images/18.jpg\"\n      ],\n      \"likes\": 225,\n      \"replyCount\": 248,\n      \"messages\": 1,\n      \"comments\": [\n        {\n          \"id\": \"f6764021-8426-4557-87aa-be05fcc9e16d\",\n          \"userNickname\": \"@mnesar\",\n          \"userFullName\": \"Jesse Mendoza\",\n          \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n          \"text\": \"Scelerisquevituperata prodesset interpretaris risus qualisque necessitatibus liber velit offendit has eos placerat.  Commodofacilisi pericula solum facilis simul adolescens.  Nullamutroque ultricies persecuti singulis repudiare similique dicam liber.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174143,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636932774143,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"4be4ca2c-0112-4837-adae-c2f06bdb793a\",\n    \"userNickname\": \"@populo\",\n    \"userFullName\": \"Tania Battle\",\n    \"userAvatar\": \"file:///android_asset/avatars/55.jpg\",\n    \"text\": \"tempus feugiat aperiri docendi at\",\n    \"images\": [],\n    \"likes\": 58,\n    \"replyCount\": 142,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636957974143,\n    \"reply\": {\n      \"id\": \"dbad831a-5fad-4b0c-8a78-5ce98bcf3dde\",\n      \"userNickname\": \"@effici\",\n      \"userFullName\": \"Guy Emerson\",\n      \"userAvatar\": \"file:///android_asset/avatars/144.jpg\",\n      \"text\": \"Eiusornare phasellus fabulas error dictas nisi minim montes malesuada netus doctus morbi nominavi suas mentitum epicuri.  Tortoreuripidis tristique veri numquam ocurreret malesuada definitiones.\",\n      \"images\": [],\n      \"likes\": 504,\n      \"replyCount\": 309,\n      \"messages\": 9,\n      \"comments\": [\n        {\n          \"id\": \"41ba3916-9e59-4db5-8e72-68569a93444f\",\n          \"userNickname\": \"@vix\",\n          \"userFullName\": \"Marcy Cleveland\",\n          \"userAvatar\": \"file:///android_asset/avatars/8.jpg\",\n          \"text\": \"Efficiturquam felis.  Suavitatecetero ante.  Scriptaadipiscing offendit qualisque latine interpretaris.  Fallidolor litora indoctum quisque autem reformidans iusto faucibus tota noster ad vis.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"21aa63e0-39aa-467e-86f6-beaa57a0e8c5\",\n          \"userNickname\": \"@accums\",\n          \"userFullName\": \"Anita Matthews\",\n          \"userAvatar\": \"file:///android_asset/avatars/41.jpg\",\n          \"text\": \"🔵🥣🐪 cum🥘🗳🚓 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636986774143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"67ada7b5-d1b2-478c-8165-b7d5ff6ee69a\",\n          \"userNickname\": \"@class\",\n          \"userFullName\": \"Guy McPherson\",\n          \"userAvatar\": \"file:///android_asset/avatars/162.jpg\",\n          \"text\": \"🏤🐾👩‍👩‍👧‍👧\\nquam perpetua🛎🚗 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"3a5679dc-a94a-4e9c-9c00-a25fb92bae84\",\n          \"userNickname\": \"@postul\",\n          \"userFullName\": \"Jimmie Miranda\",\n          \"userAvatar\": \"file:///android_asset/avatars/111.jpg\",\n          \"text\": \"arcu libris principes autem tritani\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"ea31d01c-0fc7-4358-a5cc-3fa8090e8a8e\",\n          \"userNickname\": \"@scrips\",\n          \"userFullName\": \"Angelia Wolf\",\n          \"userAvatar\": \"file:///android_asset/avatars/70.jpg\",\n          \"text\": \"Singulisfacilisis cras.  Melioreposse vidisse urna iaculis facilisi autem leo voluptaria class mus justo prompta convenire turpis.  Saperetconvenire quam nisi maximus.  Consetetursem ei theophrastus omnesque enim epicuri curae graeci inani posse pulvinar has.  Honestatisludus oratio curabitur qualisque enim cetero cetero habeo ferri sea elit adhuc viris.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f0ffb859-d478-43fa-aaa7-92da88b65af2\",\n          \"userNickname\": \"@taciti\",\n          \"userFullName\": \"Francesca Mitchell\",\n          \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n          \"text\": \"🗿\\nConceptammagnis maluisset gravida facilisi dis vix autem sanctus consul.  Pretiumea augue periculis nisl montes quas corrumpit himenaeos comprehensam consectetuer et gravida graeci utinam numquam liber eripuit persius.  Plateaerroribus constituam constituto dicta ornatus tibique deterruisset quaestio singulis nihil.  Perpetuavero nascetur quis.  Fabulashendrerit detraxit volutpat animal facilis netus mattis in iaculis qui tempor sonet vix platea aperiri invenire.🌪 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"b165abda-a96c-45c7-9d6f-1b9749711acf\",\n          \"userNickname\": \"@suscip\",\n          \"userFullName\": \"Ed Young\",\n          \"userAvatar\": \"file:///android_asset/avatars/127.jpg\",\n          \"text\": \"persius conubia\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"0c134c8c-6a71-4b37-9e8b-08b07af706dd\",\n          \"userNickname\": \"@graeci\",\n          \"userFullName\": \"Alyssa Nelson\",\n          \"userAvatar\": \"file:///android_asset/avatars/28.jpg\",\n          \"text\": \"🚜🦄\\nVelitcausae leo atqui nonumes errem reprimique pri sea his.  Omittammagna blandit homero mollis graece taciti metus velit meliore porta vehicula hendrerit magnis lorem ferri convenire dictumst mandamus.  Dicatdonec delicata aliquip.  Pretiumei necessitatibus enim fabellas dissentiunt indoctum dictas natum interdum adversarium cras quod sea erroribus pri posuere vix accumsan.  Iusad adversarium mutat corrumpit consequat nunc altera donec scripserit dui persecuti ius egestas gravida ornatus fugit porro.  Quodcras pharetra ornare fuisset reprimique lacinia efficiantur vix consectetuer has decore suas ius molestiae iuvaret auctor duis sem.👩‍👧‍👦😷\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636957974143,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"cd8f54fc-87fc-4d4e-9f4b-4890a9810c1c\",\n          \"userNickname\": \"@alia\",\n          \"userFullName\": \"Louella Beck\",\n          \"userAvatar\": \"file:///android_asset/avatars/186.jpg\",\n          \"text\": \"reque vituperata sadipscing sed nullam\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174143,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636957974143,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"c467f4e7-d927-4c3c-8b74-dc865c55287d\",\n    \"userNickname\": \"@accomm\",\n    \"userFullName\": \"Marisa Harrison\",\n    \"userAvatar\": \"file:///android_asset/avatars/164.jpg\",\n    \"text\": \"Auguelabores detraxit contentiones fabulas enim lorem alterum delenit pro persecuti.  Vulputateinimicus interpretaris sit habeo ad option assueverit petentium maiorum consectetur porttitor no iaculis placerat nonumy.  Euripidisaccusata utinam suscipit option commodo conubia vivendo cubilia mei imperdiet habitant tation conclusionemque vero sapientem.  Minimaccumsan accusata suscipiantur.  Tortorcivibus persecuti.\",\n    \"images\": [],\n    \"likes\": 52,\n    \"replyCount\": 527,\n    \"messages\": 6,\n    \"comments\": [\n      {\n        \"id\": \"0385c0dc-fb3b-44f9-a8d0-5738ba59b46f\",\n        \"userNickname\": \"@homero\",\n        \"userFullName\": \"Allison Gentry\",\n        \"userAvatar\": \"file:///android_asset/avatars/128.jpg\",\n        \"text\": \"Deterruissetgravida falli efficitur moderatius oporteat tamquam.  Petentiumfeugiat sapientem hinc.  Pertinaciaultrices salutatus suscipiantur euripidis aenean sem adipisci.  Voluptariaconstituto percipit scelerisque neque praesent nisi splendide instructior possim menandri atqui intellegat sententiae dictumst noluisse adversarium.  Sitfalli natum platea dico aliquip ante nominavi dui constituam.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374143,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"feb802c1-ce65-4207-8de1-bb01af56c159\",\n        \"userNickname\": \"@nostra\",\n        \"userFullName\": \"Lou McIntyre\",\n        \"userAvatar\": \"file:///android_asset/avatars/83.jpg\",\n        \"text\": \"🏣🌲  maiorum  👨‍👨‍👦 ✊🐜  hinc \\n🎇🍔 per  omittantur \\n🕸 quidam  magnis  🎍 💦🎇  inani \\n🌗😷 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974143,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"52749d4b-1e0e-410e-bd71-347afa62dac0\",\n        \"userNickname\": \"@noster\",\n        \"userFullName\": \"Jolene Salinas\",\n        \"userAvatar\": \"file:///android_asset/avatars/145.jpg\",\n        \"text\": \"convenire antiopam postulant consul\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374143,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"fca41dd7-04d1-40d5-984a-001a73da2a96\",\n        \"userNickname\": \"@semper\",\n        \"userFullName\": \"Mervin Mills\",\n        \"userAvatar\": \"file:///android_asset/avatars/32.jpg\",\n        \"text\": \"🖥 Facilisicongue honestatis gloriatur adversarium meliore ludus malorum ancillae.  Tellusduo pulvinar eam ultrices meliore invenire meliore.  Sitsuscipit detraxit assueverit nascetur.  Meineglegentur iudicabit petentium invidunt definitionem cubilia amet.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974143,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"05fb1d92-ae9a-4907-8642-0ed8c1dd05aa\",\n        \"userNickname\": \"@eius\",\n        \"userFullName\": \"Joan Chang\",\n        \"userAvatar\": \"file:///android_asset/avatars/18.jpg\",\n        \"text\": \"👌🍱  fugit  ⛰😬 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374143,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c7567085-c54f-4069-be64-b6f8d8d8a0e3\",\n        \"userNickname\": \"@utinam\",\n        \"userFullName\": \"Luis Hines\",\n        \"userAvatar\": \"file:///android_asset/avatars/15.jpg\",\n        \"text\": \"🥃⏰ porttitor porro ubique🐷👈 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374143,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636965174143,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"cb44d9f4-3698-4a50-89f1-f3027524c82e\",\n    \"userNickname\": \"@percip\",\n    \"userFullName\": \"Cynthia Macias\",\n    \"userAvatar\": \"file:///android_asset/avatars/155.jpg\",\n    \"text\": \"Habitasseviderer vim prodesset tortor saperet appetere commodo moderatius agam euismod sonet.  Veniamalia qualisque mentitum mutat consequat urbanitas arcu nonumes.  Doctuscivibus bibendum rutrum nihil.  Nostrafinibus accommodare nascetur euismod indoctum principes error cursus pretium gloriatur cursus homero partiendo dico.  Ligulaiudicabit assueverit volumus adipisci.\",\n    \"images\": [],\n    \"likes\": 885,\n    \"replyCount\": 416,\n    \"messages\": 6,\n    \"comments\": [\n      {\n        \"id\": \"6f2616c3-89bb-4454-8060-76ccd987432d\",\n        \"userNickname\": \"@antiop\",\n        \"userFullName\": \"Josephine Maxwell\",\n        \"userAvatar\": \"file:///android_asset/avatars/33.jpg\",\n        \"text\": \"appareat class  equidem \\n🥗🍛 🗻🏭  nam \\n🍡🍅 🍤🐄  consequat  🐚 📷🌰  iuvaret \\n🐑 partiendo tale  sententiae \\n🎶 ⬇️🏟  mnesarchum \\n🚅↘️ ipsum  a \\n❓⏳ porttitor quaerendum  affert  👡🛡 📁🕟  dicant  🗾🚁 urna quaeque  consequat \\n5️⃣ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9865b65c-6017-442a-ae4b-5740f9956f4a\",\n        \"userNickname\": \"@nonume\",\n        \"userFullName\": \"Angelique Morgan\",\n        \"userAvatar\": \"file:///android_asset/avatars/42.jpg\",\n        \"text\": \"Utamurmovet sociosqu menandri congue menandri iusto nullam definitionem eius sententiae latine sapientem mattis elementum accommodare utroque.  Quammenandri utamur maecenas non qui sodales delectus delenit reprehendunt explicari habeo sapientem ultrices.  Suscipitdebet eirmod comprehensam suscipiantur esse dicant doming urna errem vel elit consetetur leo ad ignota quem impetus neque.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1f333469-19aa-4599-ad93-1317235bc2c5\",\n        \"userNickname\": \"@vis\",\n        \"userFullName\": \"Polly Pacheco\",\n        \"userAvatar\": \"file:///android_asset/avatars/64.jpg\",\n        \"text\": \"♏️🕑  augue \\n✴️ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"34fe26be-c4fd-4082-8017-fb9b181fda61\",\n        \"userNickname\": \"@percip\",\n        \"userFullName\": \"Ariel Fitzgerald\",\n        \"userAvatar\": \"file:///android_asset/avatars/193.jpg\",\n        \"text\": \"lacinia porttitor convenire non alia\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"5f4290ed-0a0d-4f49-9fe0-561f82c66a2f\",\n        \"userNickname\": \"@delica\",\n        \"userFullName\": \"Vicky Simon\",\n        \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n        \"text\": \"tamquam cetero\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"105bbf98-a227-44c0-9d4c-34ad3aa63853\",\n        \"userNickname\": \"@nascet\",\n        \"userFullName\": \"Vonda Watson\",\n        \"userAvatar\": \"file:///android_asset/avatars/55.jpg\",\n        \"text\": \"🎥🗑🗝\\nMeliusdoming postulant possit veri nonumy efficiantur accusata venenatis putent pharetra adipisci postulant offendit dicta ridens nostrum delenit.  Etnihil noluisse simul inani per sale epicuri constituto pretium etiam ius solet graecis simul adipisci ipsum partiendo inceptos.🗿 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574144,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636957974143,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"686043d1-b572-4ed3-8044-c2ad8a93a0c9\",\n    \"userNickname\": \"@est\",\n    \"userFullName\": \"Johnie Morris\",\n    \"userAvatar\": \"file:///android_asset/avatars/165.jpg\",\n    \"text\": \"regione convenire phasellus platea cu\",\n    \"images\": [],\n    \"likes\": 75,\n    \"replyCount\": 45,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374144,\n    \"reply\": {\n      \"id\": \"4e3d12b8-a4c6-4f70-828e-820108e52aa9\",\n      \"userNickname\": \"@mei\",\n      \"userFullName\": \"Hector Dillon\",\n      \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n      \"text\": \"Explicariintellegebat quis interesset atomorum saperet turpis viverra.  Ultricesancillae velit dolorum splendide neglegentur definitiones velit faucibus.  Voluptatibusorci accommodare principes phasellus eu fastidii aliquam eros doctus ligula consetetur tincidunt ancillae tota.  Purusfeugiat dicunt vocent a lorem nisl facilisi ocurreret inani iuvaret senserit patrioque voluptatum mauris aliquet vituperatoribus equidem.  Talecurabitur feugiat leo liber his aliquid sed sodales elementum labores aptent electram ocurreret epicurei volutpat sale sociosqu.  Ancillaevitae harum vivendo altera.\",\n      \"images\": [\n        \"file:///android_asset/post_images/93.jpg\",\n        \"file:///android_asset/post_images/46.jpg\",\n        \"file:///android_asset/post_images/40.jpg\",\n        \"file:///android_asset/post_images/15.jpg\",\n        \"file:///android_asset/post_images/44.jpg\"\n      ],\n      \"likes\": 919,\n      \"replyCount\": 528,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"5509e881-10a2-4f5e-bf15-c9fe1157854d\",\n          \"userNickname\": \"@nibh\",\n          \"userFullName\": \"Jordan Kline\",\n          \"userAvatar\": \"file:///android_asset/avatars/171.jpg\",\n          \"text\": \"sonet aliquet\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174144,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"bc0b9758-1b71-4fd3-8b84-3d66eb5f8c13\",\n          \"userNickname\": \"@facili\",\n          \"userFullName\": \"Javier Hutchinson\",\n          \"userAvatar\": \"file:///android_asset/avatars/80.jpg\",\n          \"text\": \"Nullasalutatus dicunt reprimique suscipiantur tacimates et eros felis ludus.  Consulnihil porta senserit faucibus decore facilis ornare mandamus dolor suspendisse latine dicant justo eruditi non mentitum fugit nominavi moderatius.  Nostrainvenire lorem nominavi saperet signiferumque.  Pellentesquenon mei mediocritatem voluptatum quaerendum perpetua quaeque oporteat.  Dicoac an enim interdum ligula iriure purus evertitur interesset.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374144,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"864ce00e-3bbc-4380-a833-cede87d47967\",\n          \"userNickname\": \"@utamur\",\n          \"userFullName\": \"Randolph Lyons\",\n          \"userAvatar\": \"file:///android_asset/avatars/42.jpg\",\n          \"text\": \"Movetmeliore morbi voluptaria.  Menandrilatine nam fugit pertinax sit dis platea potenti epicuri rutrum cursus appareat aliquip ut corrumpit cu agam vis.  Interpretarisaccumsan mattis definiebas taciti erat assueverit aliquid tritani.  Conveniredelenit non volutpat magnis wisi autem quam maecenas detracto.  Pulvinartempus offendit vivendo gloriatur habemus accusata civibus neglegentur phasellus.  Dignissimdolor diam similique sem intellegebat docendi harum propriae convenire sed vis quod gravida.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174144,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"64f91889-bc7d-4d48-86ab-fff32d98e898\",\n          \"userNickname\": \"@antiop\",\n          \"userFullName\": \"Angelita Guerra\",\n          \"userAvatar\": \"file:///android_asset/avatars/16.jpg\",\n          \"text\": \"Volutpathabitasse postea leo molestiae iriure fusce.  Namfacilisi ludus tristique quam.  Sumoeget rhoncus molestie integer urbanitas solet non voluptatibus interpretaris nostrum malesuada potenti.  Vestibulumplacerat dolores graeco propriae nominavi ornare falli augue utinam tantas.  Ancillaeei adversarium morbi accommodare invenire errem noluisse utamur aliquid felis tellus putent cras vis feugait.  Audiresuscipit eloquentiam referrentur et dictas adipiscing mattis saperet sem ut natum reque.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774144,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636983174144,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"9588fbdb-3f6f-4552-b6c3-db2a2714cc21\",\n    \"userNickname\": \"@commod\",\n    \"userFullName\": \"Lesley Blair\",\n    \"userAvatar\": \"file:///android_asset/avatars/38.jpg\",\n    \"text\": \"verterem detraxit ultricies definitiones euismod\",\n    \"images\": [],\n    \"likes\": 496,\n    \"replyCount\": 197,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"ee540a80-0370-4500-863d-6869397c2c8a\",\n        \"userNickname\": \"@audire\",\n        \"userFullName\": \"Betty Fields\",\n        \"userAvatar\": \"file:///android_asset/avatars/186.jpg\",\n        \"text\": \"minim invenire morbi\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"95bc963e-24e3-4eae-ad90-578a9e4b3835\",\n        \"userNickname\": \"@eripui\",\n        \"userFullName\": \"Lauren Tran\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"Auguealiquip inceptos electram docendi iuvaret eu quas efficiantur cubilia oporteat morbi melius tempor parturient mi ante pertinacia suavitate consetetur.  Adfabellas posuere tristique possim.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f2ef55bd-b9ba-4f33-b1a7-766b8aebb12d\",\n        \"userNickname\": \"@massa\",\n        \"userFullName\": \"Pam Britt\",\n        \"userAvatar\": \"file:///android_asset/avatars/30.jpg\",\n        \"text\": \"Fabellassociosqu explicari appareat hendrerit eius option.  Singulisdissentiunt neque aliquet delenit varius saperet doming platonem quam cu cras interdum aeque.  Dicuntlaudem penatibus equidem repudiandae sapientem arcu facilisi congue gubergren himenaeos novum ante tale iudicabit hendrerit.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"016408d7-a3a6-460d-89b4-e452af292426\",\n        \"userNickname\": \"@dissen\",\n        \"userFullName\": \"Terence York\",\n        \"userAvatar\": \"file:///android_asset/avatars/110.jpg\",\n        \"text\": \"alia epicurei  sonet \\n⏱ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3ab78c99-f778-4ae8-87d5-6ea77535bfeb\",\n        \"userNickname\": \"@hinc\",\n        \"userFullName\": \"Lilly Montgomery\",\n        \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n        \"text\": \"Andonec vituperatoribus tortor principes tantas egestas aliquam cu lorem mei ponderum detracto laudem.  Feugaitsaperet ridens sociis.  Errortempus ne vero euripidis a latine aeque ultricies interpretaris postulant libero urbanitas voluptatum scripta qui adolescens dis vituperata.  Omittanturmorbi quot morbi aliquam.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9e6ce3ae-e50c-487f-ae7b-798867906a55\",\n        \"userNickname\": \"@legimu\",\n        \"userFullName\": \"Kelsey Bean\",\n        \"userAvatar\": \"file:///android_asset/avatars/188.jpg\",\n        \"text\": \"ac  libris \\n🐔 🚙🥕  omnesque \\n🌆🦃 🏷⏳  brute \\n✈️ 📤🗯  populo \\n🏭🛵 🍢🛠  persecuti  🍍 🍐🚲  sodales  🎁🐠 sit  praesent  👞 oratio  conubia  🍞 purus  nec  3️⃣ penatibus  viverra  🥢🌕 🙎⚓️  gloriatur  😅 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e4f5958c-2277-4327-a743-88fe03564090\",\n        \"userNickname\": \"@minim\",\n        \"userFullName\": \"Tina Davidson\",\n        \"userAvatar\": \"file:///android_asset/avatars/123.jpg\",\n        \"text\": \"🌉🐫\\nnoster mel equidem0️⃣ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ba5e6935-bd1c-44de-90c7-52e31dc3bdc6\",\n        \"userNickname\": \"@omitta\",\n        \"userFullName\": \"Latoya Blackburn\",\n        \"userAvatar\": \"file:///android_asset/avatars/132.jpg\",\n        \"text\": \"Erosnullam sodales graeci accumsan tritani posse at delenit netus repudiare consectetuer.  Fermentumsonet expetenda simul tortor dignissim dignissim habitasse suas molestie vix pertinacia fusce mus.  Detractodis eros ius habeo tincidunt ancillae habitant habemus vituperatoribus.  Dignissimappareat habemus amet augue quaerendum ultrices pharetra mediocritatem sale eros pertinax explicari malorum civibus enim civibus repudiandae efficitur.  Inaniamet lacinia sale potenti antiopam sea habitasse montes tempor iudicabit adversarium dictumst nihil no vituperatoribus leo.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"683fd21d-4c21-4f4e-89cc-c3b9457f5acf\",\n        \"userNickname\": \"@mucius\",\n        \"userFullName\": \"Tommy Hines\",\n        \"userAvatar\": \"file:///android_asset/avatars/35.jpg\",\n        \"text\": \"Sumomutat pharetra feugait iudicabit at mediocritatem definitionem malesuada pretium a justo ponderum brute magna nec pro iriure usu.  Delectuscivibus cursus.  Arcudolorem maluisset vel pertinacia vituperatoribus erat enim.  Numquamusu percipit lobortis massa an tempus appetere.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174144,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636990374144,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1131,\n      \"positions\": [\n        {\n          \"text\": \"ornare\",\n          \"voted\": 383\n        },\n        {\n          \"text\": \"dignissim consectetur voluptatibus facilis\",\n          \"voted\": 411\n        },\n        {\n          \"text\": \"taciti\",\n          \"voted\": 54\n        },\n        {\n          \"text\": \"eos erat\",\n          \"voted\": 283\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"bf1b9642-3112-4895-9579-afa0bd8df96e\",\n    \"userNickname\": \"@euismo\",\n    \"userFullName\": \"Brendan Bray\",\n    \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n    \"text\": \"🐶✅🍣\\npercipit platea dolor\\n\",\n    \"images\": [],\n    \"likes\": 648,\n    \"replyCount\": 478,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"f898662e-081f-458c-9ec3-c69f2377764e\",\n        \"userNickname\": \"@natoqu\",\n        \"userFullName\": \"Deidre Carlson\",\n        \"userAvatar\": \"file:///android_asset/avatars/77.jpg\",\n        \"text\": \"🎠\\nEloquentiammauris facilis lacinia mediocrem epicurei meliore constituto electram alienum corrumpit recteque ubique decore assueverit mediocrem tantas.  Nullacursus habemus noluisse honestatis vitae omittantur odio prodesset adolescens.  Dolorumerroribus morbi mea indoctum dicit.  Ususcripta iriure mucius vis repudiare volumus repudiare conceptam. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374144,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"269b2885-1e39-43bd-a79d-1c6bf86a86a3\",\n        \"userNickname\": \"@vivend\",\n        \"userFullName\": \"Grady Francis\",\n        \"userAvatar\": \"file:///android_asset/avatars/81.jpg\",\n        \"text\": \"Repudiarepostea consequat saepe vehicula definiebas prodesset veniam his quot mi a varius lacinia sententiae viderer novum integer nisl.  Platonemnoster neglegentur dolorem dui luptatum lacinia ubique in option scripserit expetendis aeque.  Platonemcurae maluisset causae perpetua contentiones malorum adhuc melius.  Aelaboraret doming pri quem verear tacimates luptatum epicuri.  Gloriaturtantas disputationi agam elit numquam ludus splendide recteque eleifend vim recteque ferri mnesarchum inceptos legere iudicabit tota.  Volumusnoster metus sapientem tristique repudiare brute natoque elitr graeci quis an.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"36a0969f-b7e0-40ee-bae8-9ca57a07e627\",\n        \"userNickname\": \"@region\",\n        \"userFullName\": \"Bradley Beard\",\n        \"userAvatar\": \"file:///android_asset/avatars/92.jpg\",\n        \"text\": \"🍾🛩\\nrhoncus fabulas vulputate habeo ornatus🛩\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"66a9d5d6-d18a-4a5f-858a-dd0eb77f84fa\",\n        \"userNickname\": \"@aliqua\",\n        \"userFullName\": \"Sean Merrill\",\n        \"userAvatar\": \"file:///android_asset/avatars/99.jpg\",\n        \"text\": \"Odiovulputate donec faucibus postulant scripta suscipit eleifend dicta menandri nostrum viris vestibulum expetendis felis.  Debetpostea indoctum postea usu conclusionemque ignota maiorum cras.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2a5923ab-cf13-4687-bb72-803f9cab57f7\",\n        \"userNickname\": \"@deleni\",\n        \"userFullName\": \"Britney Casey\",\n        \"userAvatar\": \"file:///android_asset/avatars/93.jpg\",\n        \"text\": \"🖨🍨🥘\\nHabitassereferrentur vel quaestio sociosqu mucius veritus tritani faucibus facilisis laudem mucius.  Iudicabitquam suas aperiri scelerisque.  Verearnominavi convenire definiebas ante tantas cetero imperdiet nonumes suscipiantur posse fames quas ad hinc luptatum veritus causae hinc.  Viverradelectus dicant maximus.  Ceterosnec metus enim aliquam leo adhuc natum explicari netus est agam tritani sociosqu adipisci persius ac.🥝\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3804571c-8811-4592-81ee-a579856cc0a3\",\n        \"userNickname\": \"@amet\",\n        \"userFullName\": \"Tanisha Powers\",\n        \"userAvatar\": \"file:///android_asset/avatars/49.jpg\",\n        \"text\": \"nostrum accumsan\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"325c57d1-5ec9-4bfa-9582-5ad8ef81b754\",\n        \"userNickname\": \"@dapibu\",\n        \"userFullName\": \"Faye Suarez\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"doctus maluisset sed\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"486bb9bb-11d0-45df-8ed0-d5bf86de1ef7\",\n        \"userNickname\": \"@tellus\",\n        \"userFullName\": \"Caleb McFarland\",\n        \"userAvatar\": \"file:///android_asset/avatars/128.jpg\",\n        \"text\": \"Inimicussumo tellus dignissim sale dicit luptatum feugiat verear malorum accusata elaboraret maiorum vidisse suscipit congue theophrastus dui facilis.  Montessententiae pri convenire per aliquip posse ludus nostrum potenti detraxit ei periculis partiendo oporteat egestas eleifend turpis ullamcorper.  Natoquerecteque varius esse laudem decore eruditi fugit.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"6395eb0d-e838-483a-ba5f-7ff97eaf49b6\",\n        \"userNickname\": \"@tibiqu\",\n        \"userFullName\": \"Reinaldo Fuller\",\n        \"userAvatar\": \"file:///android_asset/avatars/149.jpg\",\n        \"text\": \"Ultriciesnibh fabellas euripidis.  Pharetrapersequeris pharetra dictas mel suscipit dicit ceteros fringilla eget volumus.  Menandrivitae a duis signiferumque discere risus dicant facilisi ancillae has per libris penatibus tation graece ubique nisl accusata.  Repudiareperpetua libero mediocrem etiam corrumpit expetendis qualisque interpretaris magna dignissim orci magna autem definitionem definitiones.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774145,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636968774144,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": false,\n      \"totalVotes\": 825,\n      \"positions\": [\n        {\n          \"text\": \"fusce\",\n          \"voted\": 305\n        },\n        {\n          \"text\": \"propriae faucibus vulputate tacimates\",\n          \"voted\": 356\n        },\n        {\n          \"text\": \"nisl nisl\",\n          \"voted\": 118\n        },\n        {\n          \"text\": \"meliore\",\n          \"voted\": 46\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"a15b5491-b0c4-4bbe-a615-3fbbd226aeeb\",\n    \"userNickname\": \"@mel\",\n    \"userFullName\": \"Althea McPherson\",\n    \"userAvatar\": \"file:///android_asset/avatars/23.jpg\",\n    \"text\": \"vulputate sumo causae\",\n    \"images\": [],\n    \"likes\": 79,\n    \"replyCount\": 145,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636961574145,\n    \"reply\": {\n      \"id\": \"49191a46-b973-4164-95ef-9e29f764fc51\",\n      \"userNickname\": \"@vocibu\",\n      \"userFullName\": \"Jessica Morrow\",\n      \"userAvatar\": \"file:///android_asset/avatars/194.jpg\",\n      \"text\": \"🌲🐥  lacinia  ♥️ 🚽🌠  gloriatur \\n🏜 🦒🤤  suscipiantur  🍬 🐲🌂  sonet  💬 🥦🦊  vim  😊 \",\n      \"images\": [],\n      \"likes\": 585,\n      \"replyCount\": 564,\n      \"messages\": 0,\n      \"comments\": [],\n      \"createdAt\": 1636961574145,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"3c419e86-f8c8-4e43-b68a-156539d6f878\",\n    \"userNickname\": \"@aptent\",\n    \"userFullName\": \"Christy Vargas\",\n    \"userAvatar\": \"file:///android_asset/avatars/142.jpg\",\n    \"text\": \"facilisis adipiscing  deseruisse  🐢 \",\n    \"images\": [],\n    \"likes\": 59,\n    \"replyCount\": 69,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636943574145,\n    \"reply\": {\n      \"id\": \"b1bb94de-8b27-4ebb-8ec3-1544183547d1\",\n      \"userNickname\": \"@quis\",\n      \"userFullName\": \"Maryann Contreras\",\n      \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n      \"text\": \"magna ad  invidunt \\n🥗 👨‍👨‍👧‍👦✉️  ponderum  🗯 \",\n      \"images\": [\n        \"file:///android_asset/post_images/36.jpg\"\n      ],\n      \"likes\": 254,\n      \"replyCount\": 497,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"96ce0fc3-a443-4138-910a-cd5d099c0541\",\n          \"userNickname\": \"@adipis\",\n          \"userFullName\": \"Bradford Taylor\",\n          \"userAvatar\": \"file:///android_asset/avatars/134.jpg\",\n          \"text\": \"🍟😳\\nmeliore qualisque verear penatibus libero🎄🔠\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974145,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"054bd3d9-dd5f-4c4f-84e8-dda9d067bcc1\",\n          \"userNickname\": \"@indoct\",\n          \"userFullName\": \"Osvaldo Sampson\",\n          \"userAvatar\": \"file:///android_asset/avatars/81.jpg\",\n          \"text\": \"Tritanifuisset eget mazim hendrerit mauris nihil tale graeco similique vituperata electram urna.  Adversariumnumquam odio placerat quidam splendide voluptatibus te noluisse maluisset platea neque placerat dicta dicunt nulla.  Auctornostrum esse.  Placeratnon his affert similique aenean hinc labores eleifend dolorum animal ceteros inimicus neglegentur affert dolore signiferumque graecis.  Hincquas euismod antiopam inimicus himenaeos adhuc consectetur has reformidans audire habemus quaestio omittam justo.  Detraxittantas at ut feugait assueverit tibique feugait at sed iudicabit.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636968774145,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4393d8fa-55f1-4df8-98db-558d228f8238\",\n          \"userNickname\": \"@postea\",\n          \"userFullName\": \"Kyle Atkinson\",\n          \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n          \"text\": \"👳📊  iudicabit  ♣️💜 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174145,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636929174145,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"a506fde2-9be3-47f0-930e-537ab3b1fa2e\",\n    \"userNickname\": \"@region\",\n    \"userFullName\": \"Lorraine Brock\",\n    \"userAvatar\": \"file:///android_asset/avatars/30.jpg\",\n    \"text\": \"Suscipianturanimal luctus egestas cum justo suscipit discere maximus necessitatibus fames malesuada disputationi mediocrem intellegebat habemus efficiantur numquam ornare.  Sociosqugloriatur liber numquam sonet mei fringilla sem libero convenire porro molestiae reprimique eius wisi.\",\n    \"images\": [],\n    \"likes\": 733,\n    \"replyCount\": 256,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"d7864d53-bd63-4d28-ac64-8d5d49baf6b4\",\n        \"userNickname\": \"@venena\",\n        \"userFullName\": \"Juliet Silva\",\n        \"userAvatar\": \"file:///android_asset/avatars/27.jpg\",\n        \"text\": \"🈸🌟🍕\\nMeliusprimis pretium.  Dissentiunterrem interpretaris mel minim maiestatis regione.  Inciderintadipisci option.  Novumconstituto appareat cras id maluisset senserit invenire pri menandri pulvinar elaboraret mi.  Duilabores vidisse eget platea evertitur movet luptatum molestiae. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1aedd383-da60-4a66-8cc9-745303badb02\",\n        \"userNickname\": \"@eam\",\n        \"userFullName\": \"Kyle Henderson\",\n        \"userAvatar\": \"file:///android_asset/avatars/101.jpg\",\n        \"text\": \"🥥🖕🏗 Euismodeirmod pertinax.  Dissentiuntvehicula habitant sadipscing his mel parturient dicam feugiat turpis maiorum cum maecenas pertinax movet ultrices.  Delenitfacilis veniam urbanitas atomorum urbanitas justo.  Similiqueinvidunt ornatus utamur moderatius mentitum mel reque honestatis sit similique velit eloquentiam.  Integerhomero dicunt corrumpit no viderer purus alia delenit dui consetetur electram pertinacia elementum ei delicata impetus eius ex.🍂🐨🔹 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"51383a48-9a01-40cd-b697-616e5849aafa\",\n        \"userNickname\": \"@volupt\",\n        \"userFullName\": \"Howard Rodriguez\",\n        \"userAvatar\": \"file:///android_asset/avatars/69.jpg\",\n        \"text\": \"🏩🦔🌁\\nTractatosperpetua vidisse verterem salutatus erroribus.  Eratporro libris utroque fermentum graecis.  Curabiturcursus possit detraxit laudem donec congue elaboraret persecuti reprimique quaerendum curabitur impetus arcu cras deserunt prompta eruditi tortor.🛰📁\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"41a31daa-2c19-49db-8bef-ac9f72e54c12\",\n        \"userNickname\": \"@dapibu\",\n        \"userFullName\": \"Cathryn Perez\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"veri debet malorum\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f4edc6a4-7df4-401a-9fb3-8056666e4724\",\n        \"userNickname\": \"@senect\",\n        \"userFullName\": \"Darla Kemp\",\n        \"userAvatar\": \"file:///android_asset/avatars/37.jpg\",\n        \"text\": \"Cumappareat postea adversarium dicam congue fabellas mei.  Nominavidapibus intellegat sapien duis mi pericula latine egestas natum decore.  Instructiorimpetus melius eum.  Eruditisadipscing facilisis vocibus enim id deterruisset prodesset arcu quas platea intellegebat sagittis dictumst consequat ac debet quo enim.  Vitaetincidunt finibus etiam vivamus solum cum id tempus malorum sociis volutpat natoque vehicula donec cubilia.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"269ad006-7194-4df7-9b9c-8131f8e0fcb1\",\n        \"userNickname\": \"@quaere\",\n        \"userFullName\": \"Johnny Carey\",\n        \"userAvatar\": \"file:///android_asset/avatars/174.jpg\",\n        \"text\": \"📄🆚🍨 Sententiaefacilisis wisi intellegebat accumsan nihil eros inimicus.  Nepersecuti volumus.  Nequeposse aperiri definiebas iuvaret vehicula omittantur sententiae graeci.  Aliquetcum saepe alienum periculis quem dictum brute varius deseruisse sapientem suas vitae auctor quot solet conclusionemque.  Ignotafringilla petentium sociosqu veniam.  Consulprompta periculis facilisi primis delenit odio proin petentium varius nominavi affert.😪 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174145,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"37e4f012-a31b-4200-9370-841e26b4e800\",\n        \"userNickname\": \"@mandam\",\n        \"userFullName\": \"Morgan Ellison\",\n        \"userAvatar\": \"file:///android_asset/avatars/26.jpg\",\n        \"text\": \"mediocritatem  meliore \\n🏕 💫🛷  sem  ◽️ 🐟🤖  cetero \\n🙇 intellegebat has  ullamcorper \\n🍛 vidisse facilisi  ubique  🦋🌙 blandit offendit  luptatum \\n🐚 dis referrentur  erroribus  🚆 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774145,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636950774145,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"f3b5400b-6ed5-4bb9-9e7f-270b3a9e9e43\",\n    \"userNickname\": \"@consec\",\n    \"userFullName\": \"Brandy Valenzuela\",\n    \"userAvatar\": \"file:///android_asset/avatars/132.jpg\",\n    \"text\": \"mauris\",\n    \"images\": [],\n    \"likes\": 20,\n    \"replyCount\": 187,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636957974146,\n    \"reply\": {\n      \"id\": \"164e8613-21d0-4388-a182-a1817812cc97\",\n      \"userNickname\": \"@labore\",\n      \"userFullName\": \"Tanisha Petty\",\n      \"userAvatar\": \"file:///android_asset/avatars/106.jpg\",\n      \"text\": \"Famesat ullamcorper turpis meliore meliore fastidii tractatos errem quis fames elit suscipiantur.  Vituperatadis expetenda sapientem neque congue nonumy platonem varius netus vivendo nascetur risus.  Intellegatconsectetuer cum alienum praesent magnis mutat conclusionemque sed sea disputationi.  Instructioreuismod nostrum habitant sollicitudin viverra lacus sollicitudin tritani dapibus suspendisse unum sonet interpretaris invenire natoque ultricies mus.\",\n      \"images\": [],\n      \"likes\": 165,\n      \"replyCount\": 563,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"d93f1576-75ff-4ca3-a304-44bb881852a0\",\n          \"userNickname\": \"@script\",\n          \"userFullName\": \"Kaye Whitaker\",\n          \"userAvatar\": \"file:///android_asset/avatars/94.jpg\",\n          \"text\": \"⛎\\nCurabiturvarius vocibus numquam quaestio nec cu tibique maiestatis ubique inciderint cu euripidis.  Sumodocendi esse molestie eius ancillae honestatis porta cras ex persequeris voluptaria posse ocurreret euismod utroque.  Dicuntvivamus dolorem expetenda ne mauris ignota turpis natoque eirmod fermentum veritus.  Possimqualisque errem accommodare laoreet eirmod et senectus.  Graecohabemus natum disputationi similique neque metus habitant alienum dis vehicula constituto lectus discere nullam enim viverra.  Possitutinam nec evertitur turpis pretium netus natoque perpetua utamur vulputate class altera et doctus altera feugait consul aptent contentiones.ℹ️\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574146,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"43af595c-0c0d-414d-905a-be9a7b225f79\",\n          \"userNickname\": \"@falli\",\n          \"userFullName\": \"Cassandra Rodriguez\",\n          \"userAvatar\": \"file:///android_asset/avatars/196.jpg\",\n          \"text\": \"libero quas singulis posse\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574146,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636943574146,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"0bd2aa15-46e0-4cf1-9ddd-6dd6a9b5973a\",\n    \"userNickname\": \"@doctus\",\n    \"userFullName\": \"Duncan Foster\",\n    \"userAvatar\": \"file:///android_asset/avatars/115.jpg\",\n    \"text\": \"🍅🌩🍵 liber platonem\\n\",\n    \"images\": [],\n    \"likes\": 44,\n    \"replyCount\": 26,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374146,\n    \"reply\": {\n      \"id\": \"264c773e-ba1f-469d-9fbb-b2fa90cd3699\",\n      \"userNickname\": \"@electr\",\n      \"userFullName\": \"Alberto Glover\",\n      \"userAvatar\": \"file:///android_asset/avatars/107.jpg\",\n      \"text\": \"🔦🐻🚈 Felisiusto vituperata sociosqu fringilla class mel gravida amet meliore commune lobortis meliore cras vocibus oratio adolescens reprehendunt tempus pretium.  Vidissedictas aeque assueverit nulla corrumpit feugait lacinia.  Litoraea reprehendunt ea invenire civibus vocibus libero dicit dicant an vitae vulputate brute sonet iisque definiebas feugiat penatibus adhuc.  Appareatmetus sagittis cetero saepe dolorum solum vidisse sale quot.  Risusmassa ludus omittantur debet ponderum feugait persius volumus sodales.  Elitdeterruisset accusata delicata ornare reque honestatis bibendum tibique conceptam dicunt amet dolorum convallis.🍲🦓🎥\\n\",\n      \"images\": [\n        \"file:///android_asset/post_images/98.jpg\",\n        \"file:///android_asset/post_images/23.jpg\"\n      ],\n      \"likes\": 612,\n      \"replyCount\": 48,\n      \"messages\": 0,\n      \"comments\": [],\n      \"createdAt\": 1636975974146,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"79abd8d2-5c05-494e-b82c-2c328ae4124e\",\n    \"userNickname\": \"@scrips\",\n    \"userFullName\": \"Parker Pope\",\n    \"userAvatar\": \"file:///android_asset/avatars/90.jpg\",\n    \"text\": \"nascetur\",\n    \"images\": [],\n    \"likes\": 28,\n    \"replyCount\": 78,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374146,\n    \"reply\": {\n      \"id\": \"88bde939-5a2f-4209-8ad1-4aa1faa011a9\",\n      \"userNickname\": \"@viris\",\n      \"userFullName\": \"Ruben Finley\",\n      \"userAvatar\": \"file:///android_asset/avatars/28.jpg\",\n      \"text\": \"🍭🐞 Nosteraltera vocent placerat error malorum iuvaret ne qualisque iuvaret ullamcorper possit.  Commodoalterum quaerendum interesset maluisset torquent dolore eius harum an pericula suscipit.  Detractodetracto graeci.  Tortorinceptos offendit blandit epicuri nostrum ultrices magna convallis te quo porttitor nibh contentiones taciti quaerendum legimus invenire.  Necessitatibusconclusionemque phasellus odio.  Soletdolorum referrentur definiebas oporteat sententiae purus mandamus maecenas gravida euripidis veritus repudiandae appetere.📈🛋🛴\\n\",\n      \"images\": [],\n      \"likes\": 447,\n      \"replyCount\": 691,\n      \"messages\": 5,\n      \"comments\": [\n        {\n          \"id\": \"9564f505-0c1b-4062-bcb6-d77b40d28b24\",\n          \"userNickname\": \"@cetero\",\n          \"userFullName\": \"Ilene De La Cruz\",\n          \"userAvatar\": \"file:///android_asset/avatars/123.jpg\",\n          \"text\": \"Eratrepudiare risus suscipiantur euismod senectus definitionem class dicit molestiae impetus fastidii eros maiestatis quot omnesque atqui.  Eloquentiamauctor adipisci graece idque sociis docendi nulla iudicabit delenit tibique esse.  Posuereexplicari eu curae autem definiebas.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174146,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8b758d19-ad34-4419-97a2-c1b3f8aee53a\",\n          \"userNickname\": \"@cras\",\n          \"userFullName\": \"Letitia Sampson\",\n          \"userAvatar\": \"file:///android_asset/avatars/20.jpg\",\n          \"text\": \"Nooporteat maximus amet ponderum.  Habeomagnis nunc ridens commodo sapientem signiferumque habeo habitant eleifend.  Saperetnovum vivamus dictum ornatus aeque delectus lacinia tibique non.  Detraxitmea tacimates auctor.  Ametmovet sapientem assueverit.  Saepeluptatum elitr appareat inciderint eam nihil pertinax partiendo vituperata comprehensam tota eos justo eius neque odio eripuit netus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374146,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f6140969-e0fb-458e-842a-5eb053096b94\",\n          \"userNickname\": \"@senect\",\n          \"userFullName\": \"Clifton Dickerson\",\n          \"userAvatar\": \"file:///android_asset/avatars/79.jpg\",\n          \"text\": \"molestiae molestiae quidam deseruisse deseruisse\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774146,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"98e28418-51d0-4bc7-9688-15a6f88edf80\",\n          \"userNickname\": \"@agam\",\n          \"userFullName\": \"Rusty Dennis\",\n          \"userAvatar\": \"file:///android_asset/avatars/146.jpg\",\n          \"text\": \"Nuncex nulla tibique dicunt cetero quem errem viverra persius explicari ocurreret mentitum atqui altera pretium.  Nostracu curabitur vulputate turpis tempor persecuti.  Conguetempus quas penatibus habitant quidam dictum nostrum rhoncus eget accumsan dicant lobortis graeci scripta docendi moderatius tale tamquam.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974146,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f748a68d-7182-4edf-a2e0-4deaf538b628\",\n          \"userNickname\": \"@possit\",\n          \"userFullName\": \"Norris Winters\",\n          \"userAvatar\": \"file:///android_asset/avatars/65.jpg\",\n          \"text\": \"Intellegatintellegebat noster habitasse definiebas neglegentur et audire dico massa habitasse vulputate saperet inimicus splendide ornatus tempor quo novum.  Dicuntvituperata minim affert cras litora convenire wisi mattis fugit blandit dis.  Fermentumcondimentum minim reformidans omittantur duis nonumes lacinia equidem conceptam definitiones dicit urna omittam.  Sedmauris habitasse expetendis inciderint vivendo mea habemus ligula purus.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637008374146,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636975974146,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"ed84dcbd-4e21-44c3-bd80-5d1f393819a4\",\n    \"userNickname\": \"@nonume\",\n    \"userFullName\": \"Reynaldo Morse\",\n    \"userAvatar\": \"file:///android_asset/avatars/17.jpg\",\n    \"text\": \"Bibendumreprimique inani duo ac fuisset minim est.  Repudiaretempor docendi id nostrum sodales suavitate interpretaris consectetur iusto ancillae te vituperatoribus numquam sed nominavi gubergren varius.\",\n    \"images\": [\n      \"file:///android_asset/post_images/69.jpg\",\n      \"file:///android_asset/post_images/64.jpg\",\n      \"file:///android_asset/post_images/78.jpg\"\n    ],\n    \"likes\": 372,\n    \"replyCount\": 7,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"2fffdf2a-3724-4d52-aa79-c8421dbc2525\",\n        \"userNickname\": \"@rhoncu\",\n        \"userFullName\": \"Nona Petersen\",\n        \"userAvatar\": \"file:///android_asset/avatars/185.jpg\",\n        \"text\": \"justo  pulvinar  📥🛒 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774146,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ef1b0f92-0d57-496f-a6b0-55f36ae1c9aa\",\n        \"userNickname\": \"@urbani\",\n        \"userFullName\": \"Yvette Trevino\",\n        \"userAvatar\": \"file:///android_asset/avatars/77.jpg\",\n        \"text\": \"mel\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774146,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"39dbc21b-d27b-4c40-92cb-8e6a388bc310\",\n        \"userNickname\": \"@nullam\",\n        \"userFullName\": \"Janet Wagner\",\n        \"userAvatar\": \"file:///android_asset/avatars/32.jpg\",\n        \"text\": \"orci  mi  🔎 etiam  elaboraret \\n🚋🍽 duo  litora \\n♌️🍌 nulla invidunt  verear \\n🔇 adhuc vestibulum  invenire \\n⏲ erat  omittam  🍐 cubilia  habeo  ☔️ detraxit  fames \\n🏃‍♀ regione eruditi  latine  🦎 🐄🎆  detraxit  👩‍🌾🥐 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e21adb94-d7fe-4c2f-9996-ded7e3e9c3be\",\n        \"userNickname\": \"@dicat\",\n        \"userFullName\": \"Phoebe Singleton\",\n        \"userAvatar\": \"file:///android_asset/avatars/0.jpg\",\n        \"text\": \"🐭 Aliaplatea nihil ullamcorper definiebas.  Dapibuseu salutatus pertinax suscipiantur mei fames.  Maurisqui doming altera dolor dapibus id.📿🕑👨‍🎓 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c56a098b-7e8c-41fa-987f-d8b56e0d9412\",\n        \"userNickname\": \"@omitta\",\n        \"userFullName\": \"Sammy Mills\",\n        \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n        \"text\": \"convenire natum risus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2d4f8f5f-ca6c-46ae-a6f5-e6e92fae20c5\",\n        \"userNickname\": \"@himena\",\n        \"userFullName\": \"Nestor Bentley\",\n        \"userAvatar\": \"file:///android_asset/avatars/86.jpg\",\n        \"text\": \"🌑🛒  neglegentur \\n🍱 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3e370033-974e-4586-8401-70412c3cdb95\",\n        \"userNickname\": \"@dicant\",\n        \"userFullName\": \"Colleen Shields\",\n        \"userAvatar\": \"file:///android_asset/avatars/20.jpg\",\n        \"text\": \"pretium discere ligula\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374147,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636947174146,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"ec33a467-4dcd-4219-9871-a34e43992ea8\",\n    \"userNickname\": \"@simul\",\n    \"userFullName\": \"Bernardo Snow\",\n    \"userAvatar\": \"file:///android_asset/avatars/105.jpg\",\n    \"text\": \"Ultricesmattis facilisi.  Novuminterpretaris duo lectus tota suas platea invidunt instructior sollicitudin ut affert nibh.  Graecoerat suspendisse.  Optiontincidunt vocibus ipsum dolore impetus reprimique dolorem nihil cum putent penatibus quot platonem quisque justo.  Tacitisingulis fabellas detracto porttitor postulant conubia fabellas persecuti venenatis splendide aperiri ferri felis suscipit brute iisque.\",\n    \"images\": [],\n    \"likes\": 583,\n    \"replyCount\": 598,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"56c4ca69-9a3c-4adb-a3fd-b9eb5dc6e60a\",\n        \"userNickname\": \"@effici\",\n        \"userFullName\": \"Winfred Rosales\",\n        \"userAvatar\": \"file:///android_asset/avatars/152.jpg\",\n        \"text\": \"🦖🏝 Cudolore equidem eripuit eruditi adipisci et eleifend an expetenda nisl homero.  Adversariumnibh pharetra torquent.  Veritusunum tractatos accumsan leo reque reformidans tantas nibh ubique sadipscing diam.  Maximussale harum persequeris error lacinia tellus suscipit altera equidem mutat idque explicari possim neque doctus delenit nonumy.  Iriurehabitasse voluptaria mediocrem ignota euripidis simul senectus vix decore.  Turpisconubia ullamcorper saepe.🐣🈂️📽\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e4f1e968-ca0e-4c62-8362-287d321bcff3\",\n        \"userNickname\": \"@sollic\",\n        \"userFullName\": \"Dewayne Holder\",\n        \"userAvatar\": \"file:///android_asset/avatars/51.jpg\",\n        \"text\": \"🎍👆8️⃣\\nVivendoregione justo eos doming urbanitas molestiae an minim habeo iusto dictum dignissim pretium nobis nam eruditi no.  Verolaoreet his laoreet habemus regione inani.  Dignissimefficiantur lacus bibendum hinc debet dicat class equidem taciti referrentur placerat etiam torquent nostra animal convenire potenti.  Persecutidictumst latine detraxit fuisset iuvaret explicari molestie invenire nisl erat.  Eiusiusto constituam aliquid melius scripta mutat alia omittantur adhuc ac honestatis finibus alterum montes expetenda.♣️🚔🌩 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c234f733-2434-4b43-a142-feafc412e00b\",\n        \"userNickname\": \"@graeco\",\n        \"userFullName\": \"Bennie Short\",\n        \"userAvatar\": \"file:///android_asset/avatars/177.jpg\",\n        \"text\": \"🌮😕\\npharetra tractatos iisque pertinacia aeque🕖🍷\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c4f05fbc-9c67-4d18-90d2-d78bac969cc4\",\n        \"userNickname\": \"@morbi\",\n        \"userFullName\": \"Tricia Landry\",\n        \"userAvatar\": \"file:///android_asset/avatars/174.jpg\",\n        \"text\": \"📘😋\\nDomingdeserunt deterruisset vero equidem parturient persequeris ridiculus voluptatibus inciderint detraxit graece tincidunt fugit graecis delenit.  Requeadhuc neglegentur sollicitudin lacinia orci graeco ius nascetur volumus montes ridiculus vim vituperatoribus eam suscipiantur mauris.  Persecutivoluptaria veniam ridiculus facilis audire suas suscipit euismod impetus posidonium potenti elitr mucius a lacus eirmod.🗺🥔🐗\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"74b0b7e2-1adb-4df5-8333-9a64baf737e8\",\n        \"userNickname\": \"@utroqu\",\n        \"userFullName\": \"Phil Glass\",\n        \"userAvatar\": \"file:///android_asset/avatars/17.jpg\",\n        \"text\": \"aliquet maiestatis ceteros sea\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d5982b7b-0abb-4ee6-b44f-12477b08de4f\",\n        \"userNickname\": \"@deleni\",\n        \"userFullName\": \"Major Vazquez\",\n        \"userAvatar\": \"file:///android_asset/avatars/53.jpg\",\n        \"text\": \"🍱👨‍👨‍👦  cum \\n🗳⬅️ a  quaestio  🌕 🚹📮  pericula  🙃🍅 🈹🍛  possim \\n🥫 🎥🐞  malesuada \\n🍁 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b5b70eb9-4a29-4647-8ff8-193e271825b5\",\n        \"userNickname\": \"@decore\",\n        \"userFullName\": \"Colette Rutledge\",\n        \"userAvatar\": \"file:///android_asset/avatars/19.jpg\",\n        \"text\": \"🐡🏣🍾 Mollisaccusata ceteros alia reformidans lacinia his maecenas voluptatibus class pretium pulvinar pertinacia conclusionemque.  Auguealia cu melius dicta prompta enim delicata movet ante populo nisl posuere dolorem rutrum ridiculus dui consetetur pretium nullam.💋🙍🍇\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574147,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636979574147,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"5a808b4e-ea77-42ef-84c4-486406b5ca4d\",\n    \"userNickname\": \"@omitta\",\n    \"userFullName\": \"Kyle Pate\",\n    \"userAvatar\": \"file:///android_asset/avatars/93.jpg\",\n    \"text\": \"ubique reprehendunt omnesque neque\",\n    \"images\": [],\n    \"likes\": 79,\n    \"replyCount\": 60,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636979574147,\n    \"reply\": {\n      \"id\": \"6a2e40bf-18c5-40d8-ba58-a50f4394dce6\",\n      \"userNickname\": \"@vestib\",\n      \"userFullName\": \"Greg Vaughn\",\n      \"userAvatar\": \"file:///android_asset/avatars/158.jpg\",\n      \"text\": \"aliquid luctus dolore\",\n      \"images\": [],\n      \"likes\": 951,\n      \"replyCount\": 461,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"73efb6fd-463c-4f9c-86ff-e4b588c6ad06\",\n          \"userNickname\": \"@region\",\n          \"userFullName\": \"Dennis Campbell\",\n          \"userAvatar\": \"file:///android_asset/avatars/186.jpg\",\n          \"text\": \"Solumlacinia scripta adhuc penatibus adversarium fuisset vix reque perpetua principes class.  Menandridelenit reque verear tacimates novum hinc pericula animal dolore vis aliquam intellegebat idque cetero debet eirmod fusce.  Scripseritcum lorem debet feugiat.  Senseritarcu ea accusata oporteat expetendis leo morbi nulla pri hendrerit molestie vis leo cursus et movet pertinacia.  Legimusassueverit malorum curabitur discere harum tale doctus mucius fuisset.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636979574147,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a6d1ede1-e81f-4b0d-843b-8ed1844d7ba1\",\n          \"userNickname\": \"@penati\",\n          \"userFullName\": \"Antoinette Kent\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"Utinamsenectus risus.  Metusligula indoctum postulant principes scripta luctus.  Accusatalaudem scripserit pellentesque auctor offendit quas sodales intellegebat nunc.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774147,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"1d03eeaa-5c28-4b14-aee1-f3ce11317161\",\n          \"userNickname\": \"@iudica\",\n          \"userFullName\": \"Darwin Vasquez\",\n          \"userAvatar\": \"file:///android_asset/avatars/130.jpg\",\n          \"text\": \"debet  wisi \\n⏸👝 vix neque  scelerisque \\n👈♥️ 💽🚨  eloquentiam \\n👤 🔭✏️  fermentum \\n🍰 orci  eum \\n🍲🏪 salutatus eu  qui \\n👨‍❤️‍💋‍👨😺 🌬🥡  adolescens  📋 alia molestie  verterem \\n🚟📕 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774147,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"61a85f22-5be8-4654-bc79-33d54b3e3a14\",\n          \"userNickname\": \"@nisl\",\n          \"userFullName\": \"Lois Roberts\",\n          \"userAvatar\": \"file:///android_asset/avatars/185.jpg\",\n          \"text\": \"appareat feugait  senectus \\n⛵️ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636997574147,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636968774147,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 902,\n        \"positions\": [\n          {\n            \"text\": \"necessitatibus rutrum interpretaris quisque\",\n            \"voted\": 373\n          },\n          {\n            \"text\": \"harum qui\",\n            \"voted\": 229\n          },\n          {\n            \"text\": \"doming aliquet\",\n            \"voted\": 300\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"b3144fba-c80c-4071-afa8-bcbc70e1c2d0\",\n    \"userNickname\": \"@quo\",\n    \"userFullName\": \"Merle Valencia\",\n    \"userAvatar\": \"file:///android_asset/avatars/33.jpg\",\n    \"text\": \"Molestiephasellus possim inimicus fuisset equidem similique fermentum sollicitudin auctor est adversarium.  Sociisnulla eu.\",\n    \"images\": [],\n    \"likes\": 530,\n    \"replyCount\": 132,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"e7884073-64ac-4032-9b9f-15fac7b1512a\",\n        \"userNickname\": \"@socios\",\n        \"userFullName\": \"Julia Hogan\",\n        \"userAvatar\": \"file:///android_asset/avatars/0.jpg\",\n        \"text\": \"🔧⛓🌮 sociosqu🌷\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"d05711f0-55b5-4bd0-aba8-43cfd16ff52f\",\n        \"userNickname\": \"@ubique\",\n        \"userFullName\": \"Dale Bonner\",\n        \"userAvatar\": \"file:///android_asset/avatars/190.jpg\",\n        \"text\": \"commodo docendi\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174147,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636997574147,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"397911dd-26cc-415b-b291-67216fda3be6\",\n    \"userNickname\": \"@possim\",\n    \"userFullName\": \"Aubrey Marsh\",\n    \"userAvatar\": \"file:///android_asset/avatars/157.jpg\",\n    \"text\": \"Posteasonet sumo tincidunt dolorem prompta propriae viderer periculis qualisque conclusionemque.  Nostraquas persius congue dicant postea laudem partiendo magna ultricies noluisse at quidam volumus sadipscing donec omnesque urna.  Reprimiquebibendum voluptatum harum verear sonet potenti dictumst pro alienum tibique electram volutpat.  Elitrnatoque sea ea docendi urbanitas epicurei gloriatur mnesarchum mediocritatem pri lacinia interdum habitasse pellentesque tincidunt populo eros.  Risusproin ferri posse.\",\n    \"images\": [\n      \"file:///android_asset/post_images/75.jpg\",\n      \"file:///android_asset/post_images/91.jpg\"\n    ],\n    \"likes\": 318,\n    \"replyCount\": 742,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"7302cc5b-3949-40e4-9bfa-557267bc0f5b\",\n        \"userNickname\": \"@consti\",\n        \"userFullName\": \"Nicholas Berry\",\n        \"userAvatar\": \"file:///android_asset/avatars/78.jpg\",\n        \"text\": \"🦉😧  omittam \\n👐 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637008374147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b6afb19a-ab4f-491b-a9f5-b51dcbc9c8c4\",\n        \"userNickname\": \"@omitta\",\n        \"userFullName\": \"Francesca Rosales\",\n        \"userAvatar\": \"file:///android_asset/avatars/122.jpg\",\n        \"text\": \"ubique eleifend dolor\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374147,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"8015b89e-198b-46a5-9a95-0020e34f09d1\",\n        \"userNickname\": \"@antiop\",\n        \"userFullName\": \"Kendra Sargent\",\n        \"userAvatar\": \"file:///android_asset/avatars/160.jpg\",\n        \"text\": \"💩🚉📞\\nLaudemperpetua malesuada errem sed vehicula neque nominavi adhuc amet invidunt sit petentium agam auctor.  Vericonsectetuer netus inani animal suavitate viderer ut facilisi commune populo expetendis non mi iisque te ad dicant.  Optionmalesuada suas orci animal detraxit justo mus vocibus fabellas convenire duis ignota.  Ridiculustellus efficitur inceptos eu faucibus periculis voluptaria erat consectetuer facilisis nulla platonem himenaeos dicit laudem dicta vis pulvinar ligula.  Inveniresolet percipit graeci convenire gloriatur senectus interesset splendide consul recteque.  Blanditintellegat enim nibh habeo esse omittam graece expetenda sociis nascetur platonem discere quo duis option dis nihil sem.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774148,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636957974147,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"bee5f509-c67e-4c35-ac86-67b53597ab05\",\n    \"userNickname\": \"@vero\",\n    \"userFullName\": \"Clayton Clark\",\n    \"userAvatar\": \"file:///android_asset/avatars/17.jpg\",\n    \"text\": \"👨‍👧‍👧🍣 tantas ante omnesque in\\n\",\n    \"images\": [],\n    \"likes\": 39,\n    \"replyCount\": 128,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636975974148,\n    \"reply\": {\n      \"id\": \"565c0d69-eb6e-4da1-891b-aeef16e85b2a\",\n      \"userNickname\": \"@detrax\",\n      \"userFullName\": \"Harriett Cherry\",\n      \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n      \"text\": \"🏃🐲\\nEgetancillae pertinax scripserit commodo postea neglegentur neque atomorum detraxit potenti aliquid.  Meaocurreret mentitum has alterum posse rutrum mus vituperatoribus porttitor adversarium.  Iriuresigniferumque delicata lacinia maiorum eleifend vel vix auctor dolores odio dicit persius finibus per.🚬🛑 \",\n      \"images\": [],\n      \"likes\": 548,\n      \"replyCount\": 27,\n      \"messages\": 7,\n      \"comments\": [\n        {\n          \"id\": \"c805dd88-ce6e-4db9-8c0f-100c3a61574f\",\n          \"userNickname\": \"@adoles\",\n          \"userFullName\": \"Jonathon Ortiz\",\n          \"userAvatar\": \"file:///android_asset/avatars/22.jpg\",\n          \"text\": \"👩‍❤️‍💋‍👩🍐🐑\\nappareat aliquam vidisse quod🥒🗄🚃 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d1469d07-498f-4ad0-80d3-30ed243a743b\",\n          \"userNickname\": \"@auctor\",\n          \"userFullName\": \"Nelson Rojas\",\n          \"userAvatar\": \"file:///android_asset/avatars/92.jpg\",\n          \"text\": \"sit deterruisset  veri  🐑🔖 maiestatis efficiantur  no \\n💨 mucius  congue \\n⁉️ 🎚↘️  nonumes \\n🍳👨‍🌾 error  conclusionemque  🐟 sociosqu semper  sem \\n🌮 👩‍👧‍👧🐉  pulvinar \\n🙅🚤 🌇🏙  quidam  🤣 🚀👨‍👨‍👦‍👦  principes \\n*️⃣🔣 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636993974148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"268687b0-d1be-47cb-a1c4-90c68a61d8a3\",\n          \"userNickname\": \"@inani\",\n          \"userFullName\": \"Margarito McClain\",\n          \"userAvatar\": \"file:///android_asset/avatars/141.jpg\",\n          \"text\": \"suavitate theophrastus  rutrum \\n🐟🐸 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636929174148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a81fd42b-94fd-4268-9b0b-7626d593e252\",\n          \"userNickname\": \"@impetu\",\n          \"userFullName\": \"Denny Blackwell\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"Ridiculusamet maecenas epicurei viderer delicata class disputationi solet suscipit nonumy accusata.  Dicitputent eius veritus epicuri eripuit repudiare postulant rutrum alterum repudiare eloquentiam prodesset adipisci consectetur.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"806aae03-0cc8-4642-9a1f-8388326626a9\",\n          \"userNickname\": \"@advers\",\n          \"userFullName\": \"Amy Richmond\",\n          \"userAvatar\": \"file:///android_asset/avatars/175.jpg\",\n          \"text\": \"💗\\ndicam luctus regione mediocrem🚈🗜\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"a14ea282-a976-4ae4-b9ca-b8f1859b96d1\",\n          \"userNickname\": \"@necess\",\n          \"userFullName\": \"Earlene Blanchard\",\n          \"userAvatar\": \"file:///android_asset/avatars/113.jpg\",\n          \"text\": \"enim  reprimique  🍔 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174148,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"ac6ace92-0e11-4b38-8be8-7beb56a5c47f\",\n          \"userNickname\": \"@reque\",\n          \"userFullName\": \"Cathy Reyes\",\n          \"userAvatar\": \"file:///android_asset/avatars/13.jpg\",\n          \"text\": \"🛸 ubique pri placerat persequeris quaerendum🍯🐍 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636950774148,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636968774148,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"f5300f4a-935c-45fa-a169-a68fe801e3ac\",\n    \"userNickname\": \"@intere\",\n    \"userFullName\": \"Dwight Patterson\",\n    \"userAvatar\": \"file:///android_asset/avatars/72.jpg\",\n    \"text\": \"Consectetuertristique accumsan.  Visinterdum mazim nihil facilisis delectus.  Pertinaciafinibus noster dictumst neglegentur omittantur mediocrem faucibus nulla.\",\n    \"images\": [\n      \"file:///android_asset/post_images/49.jpg\"\n    ],\n    \"likes\": 620,\n    \"replyCount\": 213,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"e578ce44-c5e3-460b-a9b3-7e92f5a95732\",\n        \"userNickname\": \"@purus\",\n        \"userFullName\": \"Kathy Walker\",\n        \"userAvatar\": \"file:///android_asset/avatars/92.jpg\",\n        \"text\": \"Tritanietiam te omnesque mentitum.  Alienummorbi adolescens cum posidonium instructior.  Noluissecivibus vim definitionem.  Singulisunum civibus cubilia feugiat magna evertitur mediocrem dolore mollis regione.  Imperdietvehicula partiendo errem reformidans error libero dictas harum aliquam.  Dicatconstituto ante atqui affert tacimates suscipiantur sadipscing mattis ubique graeci donec assueverit posidonium morbi.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"739b6f77-0d12-4c88-8c7b-6289685f2c06\",\n        \"userNickname\": \"@cum\",\n        \"userFullName\": \"Robert Christian\",\n        \"userAvatar\": \"file:///android_asset/avatars/142.jpg\",\n        \"text\": \"ocurreret pertinacia  in \\n📓🦕 🌽⭐️  deserunt \\n⛱ deserunt similique  qui  🎄🏩 dui  nisl  👘 equidem  lorem  🌨🚁 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9786b6bd-225f-490c-9262-0ff293c71bf7\",\n        \"userNickname\": \"@pellen\",\n        \"userFullName\": \"Christa Cole\",\n        \"userAvatar\": \"file:///android_asset/avatars/163.jpg\",\n        \"text\": \"Oporteathabitasse parturient vocibus has rhoncus interdum periculis sapientem adipiscing adhuc.  Petentiumiuvaret facilisis legere duo interdum sententiae verear altera.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e9788cc8-d2e0-4cab-a5a9-5aa278ea4240\",\n        \"userNickname\": \"@porta\",\n        \"userFullName\": \"Winifred Whitfield\",\n        \"userAvatar\": \"file:///android_asset/avatars/88.jpg\",\n        \"text\": \"expetenda purus  volumus  🚑🤣 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ab9e1884-1ebf-4df7-bdff-a22f986efaf0\",\n        \"userNickname\": \"@rhoncu\",\n        \"userFullName\": \"Ezra Berg\",\n        \"userAvatar\": \"file:///android_asset/avatars/46.jpg\",\n        \"text\": \"🐔\\ninterdum bibendum interdum sollicitudin tale♑️🍤🍥\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"033e6fd2-1696-4cb5-a69a-246494c7fca9\",\n        \"userNickname\": \"@fastid\",\n        \"userFullName\": \"Lakeisha Puckett\",\n        \"userAvatar\": \"file:///android_asset/avatars/120.jpg\",\n        \"text\": \"🛴🦒\\ninstructior🛵\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636932774148,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"77771810-85c8-4f98-b9c4-b915e11f3fb4\",\n        \"userNickname\": \"@senect\",\n        \"userFullName\": \"Cole Neal\",\n        \"userAvatar\": \"file:///android_asset/avatars/161.jpg\",\n        \"text\": \"🚝🚗  deterruisset \\n✉️🥨 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374149,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ebb40812-319a-4464-a3ab-6e15555b5d8e\",\n        \"userNickname\": \"@civibu\",\n        \"userFullName\": \"Claude Sweet\",\n        \"userAvatar\": \"file:///android_asset/avatars/4.jpg\",\n        \"text\": \"📀🐾🕙\\nte facilisis🍟🏧\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574149,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"bff77c6b-b42f-442b-85b7-e23c5bbc8a4d\",\n        \"userNickname\": \"@justo\",\n        \"userFullName\": \"Mildred Blankenship\",\n        \"userAvatar\": \"file:///android_asset/avatars/130.jpg\",\n        \"text\": \"etiam maiestatis  altera \\n🚍🥣 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574149,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636943574148,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"002d22e1-574b-4524-b5ee-8f312bfb8fc4\",\n    \"userNickname\": \"@errem\",\n    \"userFullName\": \"Dewayne Cook\",\n    \"userAvatar\": \"file:///android_asset/avatars/139.jpg\",\n    \"text\": \"ferri explicari nihil viris ea\",\n    \"images\": [],\n    \"likes\": 902,\n    \"replyCount\": 453,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"6e14d0c9-43c0-416a-9f41-c278033ce5a8\",\n        \"userNickname\": \"@te\",\n        \"userFullName\": \"Harold Cabrera\",\n        \"userAvatar\": \"file:///android_asset/avatars/118.jpg\",\n        \"text\": \"🍃🧀 Comprehensammovet viris graecis numquam dolor.  Feugiatmaecenas dignissim putent vocibus volumus eum pulvinar appetere percipit disputationi dictum dictumst cetero referrentur adhuc.  Ignotaelit imperdiet dolore sanctus wisi dissentiunt tellus dolorum antiopam vim dapibus detraxit sollicitudin id laoreet dico parturient maluisset tellus. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636975974149,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9b975255-3fe3-45c9-98f7-be3d5400ab14\",\n        \"userNickname\": \"@dicit\",\n        \"userFullName\": \"Salvatore Ayala\",\n        \"userAvatar\": \"file:///android_asset/avatars/131.jpg\",\n        \"text\": \"🥔🔠\\ndetracto🌖🏰💔 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574149,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637004774149,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 605,\n      \"positions\": [\n        {\n          \"text\": \"voluptaria eu noster habeo\",\n          \"voted\": 67\n        },\n        {\n          \"text\": \"repudiare urbanitas\",\n          \"voted\": 433\n        },\n        {\n          \"text\": \"finibus libero dicunt\",\n          \"voted\": 105\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"ba7dbba2-a411-4c83-9ebf-d577de836037\",\n    \"userNickname\": \"@option\",\n    \"userFullName\": \"Denver Crane\",\n    \"userAvatar\": \"file:///android_asset/avatars/155.jpg\",\n    \"text\": \"Diamdicant quaeque augue.  Quisquem tantas augue ut.  Vulputatevoluptaria altera ornare mutat nonumes mattis metus dictas vix tibique metus.  Eirmoderos periculis contentiones debet eam fabellas neglegentur utroque faucibus vero reque neglegentur tota finibus ei definiebas finibus appareat.  Proinsaepe risus.\",\n    \"images\": [\n      \"file:///android_asset/post_images/88.jpg\"\n    ],\n    \"likes\": 957,\n    \"replyCount\": 570,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636939974149,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"4626e506-f7b9-4dcd-b572-8eb934ef6868\",\n    \"userNickname\": \"@idque\",\n    \"userFullName\": \"Angel Hamilton\",\n    \"userAvatar\": \"file:///android_asset/avatars/114.jpg\",\n    \"text\": \"est turpis\",\n    \"images\": [],\n    \"likes\": 1,\n    \"replyCount\": 9,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974149,\n    \"reply\": {\n      \"id\": \"66915982-17b6-477a-8d4d-0486eab904b7\",\n      \"userNickname\": \"@compre\",\n      \"userFullName\": \"Adeline Simon\",\n      \"userAvatar\": \"file:///android_asset/avatars/29.jpg\",\n      \"text\": \"quo quem dicunt\",\n      \"images\": [],\n      \"likes\": 86,\n      \"replyCount\": 651,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"5d39d485-2070-4257-8324-b259c5be8a52\",\n          \"userNickname\": \"@commun\",\n          \"userFullName\": \"Lilia Barber\",\n          \"userAvatar\": \"file:///android_asset/avatars/174.jpg\",\n          \"text\": \"Deseruntvelit praesent mel maecenas aliquip urna pulvinar molestie option pri ludus duis sagittis.  Aptentomittantur simul sociosqu repudiare interdum ornatus volumus veniam invidunt.  Equidemtellus mus consul similique amet pellentesque integer quidam delicata vis sem.  Dolorconstituam expetendis feugait explicari tibique platonem tamquam dignissim libris adversarium atomorum finibus ocurreret penatibus saperet splendide ius et condimentum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174149,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d392d041-16a6-4121-a8fa-a117f247d610\",\n          \"userNickname\": \"@singul\",\n          \"userFullName\": \"Hollie Collins\",\n          \"userAvatar\": \"file:///android_asset/avatars/30.jpg\",\n          \"text\": \"Leofabulas sagittis ponderum persius.  Montesomnesque errem habitant litora atomorum quaeque convenire vulputate dicta patrioque discere consul oporteat aptent regione habeo.  Offenditut maluisset instructior detraxit interpretaris vituperatoribus efficiantur corrumpit impetus eleifend intellegat.  Usuinvidunt eruditi class vitae mucius fringilla fringilla tantas erroribus honestatis dicat.  Veniammandamus ex molestiae nullam nihil neglegentur unum rhoncus ex ius euripidis eruditi tale tincidunt porro.  Tractatoseget quaeque eius nihil justo appetere simul senserit conubia instructior mei ullamcorper periculis fermentum pretium diam prompta.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174149,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c8b32f15-34c9-4e13-a9f3-b291ced68733\",\n          \"userNickname\": \"@sociis\",\n          \"userFullName\": \"Fannie Gill\",\n          \"userAvatar\": \"file:///android_asset/avatars/8.jpg\",\n          \"text\": \"wisi mei quem aeque\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774149,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637004774149,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 937,\n        \"positions\": [\n          {\n            \"text\": \"sapientem fastidii eos\",\n            \"voted\": 334\n          },\n          {\n            \"text\": \"appetere ludus invidunt\",\n            \"voted\": 67\n          },\n          {\n            \"text\": \"necessitatibus vituperata te sem sonet\",\n            \"voted\": 107\n          },\n          {\n            \"text\": \"conceptam\",\n            \"voted\": 429\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"3fa3ad42-0d3d-4297-96ad-cb9fe50b1c8b\",\n    \"userNickname\": \"@conval\",\n    \"userFullName\": \"Rafael Sanford\",\n    \"userAvatar\": \"file:///android_asset/avatars/56.jpg\",\n    \"text\": \"epicurei lorem  referrentur \\n🍖 \",\n    \"images\": [],\n    \"likes\": 24,\n    \"replyCount\": 2,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637001174149,\n    \"reply\": {\n      \"id\": \"0ce8dde5-daf4-4a48-aa45-d29c20eb3af6\",\n      \"userNickname\": \"@venena\",\n      \"userFullName\": \"Valentin Crawford\",\n      \"userAvatar\": \"file:///android_asset/avatars/64.jpg\",\n      \"text\": \"💷🤷‍♂  vestibulum  🐔🎶 \",\n      \"images\": [],\n      \"likes\": 757,\n      \"replyCount\": 595,\n      \"messages\": 1,\n      \"comments\": [\n        {\n          \"id\": \"d5831221-b46b-468b-9b9f-ec61dc2c2618\",\n          \"userNickname\": \"@perpet\",\n          \"userFullName\": \"Francesca Keller\",\n          \"userAvatar\": \"file:///android_asset/avatars/169.jpg\",\n          \"text\": \"Netusepicurei explicari an conceptam vidisse moderatius habitant eget fuisset ponderum interdum tritani theophrastus alia.  Dicatrhoncus vituperata salutatus tractatos metus eros possit saepe homero putent no mazim quaerendum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636954374149,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1637001174149,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": false,\n        \"totalVotes\": 1047,\n        \"positions\": [\n          {\n            \"text\": \"eloquentiam affert sapien\",\n            \"voted\": 86\n          },\n          {\n            \"text\": \"reprimique elit ridiculus\",\n            \"voted\": 470\n          },\n          {\n            \"text\": \"sea mea habemus bibendum consetetur\",\n            \"voted\": 491\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"ec886536-2887-4e63-961f-0d8282559ba5\",\n    \"userNickname\": \"@et\",\n    \"userFullName\": \"Eli Robbins\",\n    \"userAvatar\": \"file:///android_asset/avatars/4.jpg\",\n    \"text\": \"🏝💡\\nmaiorum delicata adversarium\\n\",\n    \"images\": [],\n    \"likes\": 40,\n    \"replyCount\": 96,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637011974149,\n    \"reply\": {\n      \"id\": \"8d4a370f-df9f-44db-b838-4139f6c635fb\",\n      \"userNickname\": \"@etiam\",\n      \"userFullName\": \"Chris Alvarez\",\n      \"userAvatar\": \"file:///android_asset/avatars/126.jpg\",\n      \"text\": \"🕜🍡👖 Augueatomorum consectetuer altera laudem delectus mus nonumy sollicitudin eum dapibus viverra volutpat viris feugiat.  Quascubilia autem recteque elementum urna per deterruisset utamur adhuc nobis sapientem persequeris cubilia.  Pertinaciadelicata lacus.  Autemfastidii suavitate pulvinar parturient adipiscing deseruisse taciti simul brute decore eleifend invidunt aliquam dolore.🍪🚖 \",\n      \"images\": [],\n      \"likes\": 485,\n      \"replyCount\": 621,\n      \"messages\": 0,\n      \"comments\": [],\n      \"createdAt\": 1637008374149,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"6c3fef87-b01b-447e-9628-148a7791e7d9\",\n    \"userNickname\": \"@nihil\",\n    \"userFullName\": \"Kristi Walter\",\n    \"userAvatar\": \"file:///android_asset/avatars/98.jpg\",\n    \"text\": \"♨️📈  class \\n🚓💳 \",\n    \"images\": [],\n    \"likes\": 10,\n    \"replyCount\": 56,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636997574149,\n    \"reply\": {\n      \"id\": \"4c665a2f-280e-457b-b78b-ae56f508665f\",\n      \"userNickname\": \"@cum\",\n      \"userFullName\": \"Delbert Harrell\",\n      \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n      \"text\": \"😣\\nSuscipianturauctor magna efficitur ridens cu erroribus volutpat erroribus utamur sadipscing constituto donec brute adolescens.  Reprimiquequod praesent porro blandit varius periculis senectus aliquip vix alia numquam.  Sedpersecuti pretium varius iusto nunc malesuada scelerisque iusto nibh.  Graeceomnesque tacimates iudicabit consul libero discere eius eam voluptatum dolores ac sumo lobortis alia nostra interdum graeco.🎁🌿🚈\\n\",\n      \"images\": [\n        \"file:///android_asset/post_images/22.jpg\"\n      ],\n      \"likes\": 460,\n      \"replyCount\": 163,\n      \"messages\": 2,\n      \"comments\": [\n        {\n          \"id\": \"62333c1b-a067-4ebe-a221-523b9a74300c\",\n          \"userNickname\": \"@necess\",\n          \"userFullName\": \"Stanley Kennedy\",\n          \"userAvatar\": \"file:///android_asset/avatars/101.jpg\",\n          \"text\": \"Turpisjusto vituperata netus vis latine wisi esse quis.  Maluissetvidisse perpetua quis luptatum recteque potenti ei sodales saepe quisque eu electram mediocrem ignota tritani.  Cuorci urna solum mandamus.  Vocibusinceptos sit parturient repudiandae posuere natum lectus vel fames libris tellus qualisque.  Graecemandamus adversarium mea referrentur indoctum nostrum.  Fugitlaoreet voluptatibus dolores ut condimentum wisi sale expetenda voluptaria posse aptent eos ponderum.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174149,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6e3216b1-0204-40fc-972b-c5ed19503caa\",\n          \"userNickname\": \"@vero\",\n          \"userFullName\": \"Alexis Morton\",\n          \"userAvatar\": \"file:///android_asset/avatars/64.jpg\",\n          \"text\": \"mauris aliquid reprimique eleifend\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636990374149,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636990374149,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"5952c49a-1af6-4ffa-b365-ffe1971b2fd6\",\n    \"userNickname\": \"@quisqu\",\n    \"userFullName\": \"Dominick Nielsen\",\n    \"userAvatar\": \"file:///android_asset/avatars/54.jpg\",\n    \"text\": \"🏨 ferri scelerisque ligula🦈🐸 \",\n    \"images\": [],\n    \"likes\": 46,\n    \"replyCount\": 117,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636961574150,\n    \"reply\": {\n      \"id\": \"fb5f346f-ffc8-48f7-ab1a-fab07b706d9c\",\n      \"userNickname\": \"@alienu\",\n      \"userFullName\": \"Nannie Simmons\",\n      \"userAvatar\": \"file:///android_asset/avatars/136.jpg\",\n      \"text\": \"Legeremenandri numquam detracto postulant eu fugit novum feugiat scripta solet utamur.  Maiestatisassueverit impetus urbanitas.  Fuissetoratio molestie sed reque consul mucius noluisse mauris harum natoque mediocrem doming ridiculus invenire.  Mediocritatemquis possit accusata integer ceteros donec consequat contentiones efficiantur alienum dicunt phasellus taciti efficitur animal vituperata.  Suspendisseenim habitant falli expetenda consequat velit utinam saperet efficitur ultrices oratio esse noster scripserit mollis dolorem parturient vix.\",\n      \"images\": [],\n      \"likes\": 995,\n      \"replyCount\": 238,\n      \"messages\": 7,\n      \"comments\": [\n        {\n          \"id\": \"e698d6be-9846-4f7a-87f4-d64a3ebabf6e\",\n          \"userNickname\": \"@ius\",\n          \"userFullName\": \"Laverne Glass\",\n          \"userAvatar\": \"file:///android_asset/avatars/4.jpg\",\n          \"text\": \"constituam  pertinax  😤🏷 maiestatis  intellegat \\n🚥 ridiculus  maluisset \\n🆕😲 splendide  constituto  🚅 turpis audire  tristique \\n🎑⛓ 🛫🍕  mutat  🌸 🍈👱  sententiae  ⭐️ curabitur sociosqu  salutatus \\n📷🤳 deseruisse  sit  💛 suspendisse  id  ❗️🍓 vidisse  tritani  🌅 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"9baf5408-cac1-4ea5-bc70-0c74d61dc090\",\n          \"userNickname\": \"@antiop\",\n          \"userFullName\": \"Jenny Becker\",\n          \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n          \"text\": \"Mutatelit senserit adversarium platonem minim principes sumo labores dicat noster magnis.  Ponderumplatonem pharetra mazim mucius risus.  Etiamtorquent fuisset graeco morbi delectus patrioque dictas pharetra saperet sententiae quem pri.  Dignissimpossim civibus quo luctus detracto scripserit dolores convallis cubilia senserit tota eos elit elementum postulant numquam.  Meliorealtera qui euripidis class ultricies lorem adipisci pharetra epicurei atomorum iisque quo discere condimentum sapientem wisi odio.  Quaestiointeger detraxit himenaeos inimicus splendide perpetua inani velit maiestatis sapien quo lorem commune elaboraret nobis finibus tantas.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c14ae551-679d-42a4-867a-566e34509ddd\",\n          \"userNickname\": \"@inimic\",\n          \"userFullName\": \"Candy Levine\",\n          \"userAvatar\": \"file:///android_asset/avatars/147.jpg\",\n          \"text\": \"moderatius ipsum\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636972374150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8bdd7a20-f256-49c7-9ecb-bd0d65458882\",\n          \"userNickname\": \"@cu\",\n          \"userFullName\": \"Emmanuel Ware\",\n          \"userAvatar\": \"file:///android_asset/avatars/149.jpg\",\n          \"text\": \"▫️📝🗓 minim🏣\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636990374150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d2ce4590-8771-4b66-a659-5be7a22ac528\",\n          \"userNickname\": \"@urbani\",\n          \"userFullName\": \"Rickie Beach\",\n          \"userAvatar\": \"file:///android_asset/avatars/38.jpg\",\n          \"text\": \"aliquam bibendum euripidis\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"aa1c85f7-3915-4811-b417-5678ee15d3a8\",\n          \"userNickname\": \"@splend\",\n          \"userFullName\": \"Arthur Payne\",\n          \"userAvatar\": \"file:///android_asset/avatars/29.jpg\",\n          \"text\": \"Dapibuspertinacia curae ius intellegat scelerisque ubique gravida facilis pro inciderint maecenas tantas luptatum viris facilisi id sapien augue.  Sagittisexpetendis fabellas mandamus lobortis definitionem indoctum persecuti est epicurei eirmod litora voluptaria phasellus feugait cu partiendo donec agam.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974150,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d81ae8e4-c8e8-4c07-ae51-17b2439cbeb9\",\n          \"userNickname\": \"@luctus\",\n          \"userFullName\": \"Elma Harding\",\n          \"userAvatar\": \"file:///android_asset/avatars/12.jpg\",\n          \"text\": \"Sollicitudindeseruisse veritus dolore ridiculus interdum comprehensam agam dicat ex auctor similique lacinia et wisi.  Eiusverear eos hinc mus perpetua necessitatibus meliore porta iuvaret rutrum sodales ei tempus ornare.  Dolordolore fabulas nostra fugit doctus ignota pertinax constituto habitant ridens deterruisset sale pri.  Maluissetduis curae assueverit consetetur iisque cetero eget option finibus.  Facilisilaudem libris disputationi vitae ancillae idque turpis risus nullam utinam utamur nibh inimicus quisque gravida eruditi.  Assueverittempor discere falli accumsan propriae persequeris vivamus eu hac sapientem mentitum scripta aliquip interesset scripta latine hac augue tale.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174150,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636947174150,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"3426ed85-bf64-45aa-97e4-a365c09ef474\",\n    \"userNickname\": \"@nisl\",\n    \"userFullName\": \"Jeannette Benton\",\n    \"userAvatar\": \"file:///android_asset/avatars/112.jpg\",\n    \"text\": \"ridiculus  nihil \\n🍂🐕 🌒🍜  mollis \\n🍞 🈁♒️  conubia \\n📙🚈 🗂🐋  inciderint  ⌚📯 👧➕  fermentum \\n🍧⛏ ⛎🔕  constituam  ⛓🔚 \",\n    \"images\": [],\n    \"likes\": 9,\n    \"replyCount\": 651,\n    \"messages\": 4,\n    \"comments\": [\n      {\n        \"id\": \"003ff973-370a-413b-b14c-25cc71f8503b\",\n        \"userNickname\": \"@unum\",\n        \"userFullName\": \"Pete Ruiz\",\n        \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n        \"text\": \"🥡⚛️🍙 Duispulvinar constituam regione platonem delectus persius mutat primis dicam.  Eatamquam vivamus in noluisse omnesque in harum gloriatur novum vehicula aeque has iudicabit wisi labores ornare platea reprehendunt.  Auguequem molestie cubilia fames dapibus inimicus.👩‍👧‍👧🎛 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974150,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"4368a1ae-33ab-4cd2-8f1b-fede45544e91\",\n        \"userNickname\": \"@euripi\",\n        \"userFullName\": \"Hilda Woods\",\n        \"userAvatar\": \"file:///android_asset/avatars/108.jpg\",\n        \"text\": \"🍭‼️ Malorummoderatius postea lacinia eloquentiam petentium ne facilis imperdiet iusto ceteros quis risus sit novum malorum petentium sagittis.  Idqueunum vituperata eam eget vidisse posidonium tortor egestas nonumes.  Habeodicunt regione moderatius graecis impetus pro facilis eos ponderum prompta pertinax blandit a tale dignissim.  Auguequas evertitur duo eros erat.  Ansenectus mediocritatem libris laudem cum nostrum ante ornatus audire urbanitas at civibus volutpat tempor felis.\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374150,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"24599e9c-cdf3-49e8-99ab-242c4d45bab9\",\n        \"userNickname\": \"@autem\",\n        \"userFullName\": \"Christie Bennett\",\n        \"userAvatar\": \"file:///android_asset/avatars/28.jpg\",\n        \"text\": \"senserit  an  🚒🎆 🥠🚞  vis  🍜 🥒🗼  harum  🖥 dolorum vituperata  nascetur \\n🦗 quot  sodales \\n👨😍 civibus  nobis \\n🌷 🚅🥡  delenit \\n🗑🎩 🦃📊  nam \\n🈹 🥠🚢  venenatis  🦅🗼 veri liber  tempus  🔎⛄️ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574150,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"2e229a6d-ce36-49fc-97e7-c17c9ed8a72c\",\n        \"userNickname\": \"@vocibu\",\n        \"userFullName\": \"Corey Sykes\",\n        \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n        \"text\": \"Arcubibendum vulputate risus pretium mel imperdiet vitae taciti quaeque ne consul autem nibh pertinax.  Electramsententiae definitiones mei interpretaris.  Hachomero civibus inani discere decore dissentiunt idque.  Quodmeliore ante inceptos vulputate aperiri platea aperiri blandit mus patrioque.  Deterruissetdetraxit perpetua expetenda inceptos aliquid euismod persecuti netus scripserit convallis nonumy potenti curabitur mnesarchum.  Turpisvituperata cubilia dicam praesent fabellas egestas eirmod.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774150,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636983174150,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"cca6ff89-1cde-4a04-808b-229af0be1157\",\n    \"userNickname\": \"@usu\",\n    \"userFullName\": \"Anastasia Lang\",\n    \"userAvatar\": \"file:///android_asset/avatars/135.jpg\",\n    \"text\": \"📛🦋🛋 Sollicitudinerat quisque turpis a legimus equidem.  Nibhpri nihil altera populo porro docendi decore maecenas assueverit postulant maiestatis nec vestibulum delenit.  Dicuntnulla nominavi graeci graeco usu voluptaria enim condimentum possit detraxit turpis habitasse unum scripta tempor lobortis taciti pellentesque suspendisse.  Antecurae dui conubia senserit urbanitas vocent interdum solet pharetra.  Sapienmnesarchum quot.  Tamquamlegimus eius error scripta dicit eleifend hinc autem laudem maluisset vim aliquet dicta inani legere sea velit ei.👬💠🏨\\n\",\n    \"images\": [\n      \"file:///android_asset/post_images/77.jpg\"\n    ],\n    \"likes\": 667,\n    \"replyCount\": 103,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"6b349d2a-0520-4a05-b913-463102042b43\",\n        \"userNickname\": \"@vocent\",\n        \"userFullName\": \"Dollie Ashley\",\n        \"userAvatar\": \"file:///android_asset/avatars/178.jpg\",\n        \"text\": \"🔐🈂️🗽\\niusto dicant🏰🐫 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"262814bf-4007-49c5-a6b5-6a8d1022e060\",\n        \"userNickname\": \"@honest\",\n        \"userFullName\": \"Brandie Salinas\",\n        \"userAvatar\": \"file:///android_asset/avatars/60.jpg\",\n        \"text\": \"proin tellus pretium euismod elitr\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174151,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636939974150,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"750d51cf-ff30-408b-8630-bf8d404e159d\",\n    \"userNickname\": \"@vocent\",\n    \"userFullName\": \"Bernadine Everett\",\n    \"userAvatar\": \"file:///android_asset/avatars/152.jpg\",\n    \"text\": \"nonumy  nonumy  😎 \",\n    \"images\": [],\n    \"likes\": 7,\n    \"replyCount\": 17,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636947174151,\n    \"reply\": {\n      \"id\": \"c2fe178e-cb80-4c87-88d9-c8d912d67e27\",\n      \"userNickname\": \"@possit\",\n      \"userFullName\": \"Ivy Olson\",\n      \"userAvatar\": \"file:///android_asset/avatars/113.jpg\",\n      \"text\": \"adversarium dolorem  porttitor  🌌🏦 🔗🕯  atqui \\n👣🕒 \",\n      \"images\": [\n        \"file:///android_asset/post_images/8.jpg\",\n        \"file:///android_asset/post_images/0.jpg\"\n      ],\n      \"likes\": 873,\n      \"replyCount\": 309,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"746c4ecd-4c17-44b8-af26-564bd09e0ed6\",\n          \"userNickname\": \"@electr\",\n          \"userFullName\": \"Morgan Riggs\",\n          \"userAvatar\": \"file:///android_asset/avatars/74.jpg\",\n          \"text\": \"corrumpit primis  indoctum \\n🔴 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974151,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"5594c4d3-cd3c-44d5-8e03-4be16019124e\",\n          \"userNickname\": \"@dicam\",\n          \"userFullName\": \"Ola Griffin\",\n          \"userAvatar\": \"file:///android_asset/avatars/80.jpg\",\n          \"text\": \"🥣📱  phasellus  🐥🐺 fabellas saperet  sententiae \\n⏰🌻 🍖🚚  massa  🥤 gubergren cetero  maluisset \\n🚱🍋 🍡👎  dapibus \\n🖲👁‍🗨 💃🌠  contentiones \\n🍢🍽 🧦🎙  omittantur  🐡🥗 molestie alterum  ligula  🍞 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174151,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"d1977208-08aa-44c7-aa9d-9509c54dd6c3\",\n          \"userNickname\": \"@verter\",\n          \"userFullName\": \"Michel Lott\",\n          \"userAvatar\": \"file:///android_asset/avatars/150.jpg\",\n          \"text\": \"🥘💽\\nsaepe deserunt sadipscing🖨\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174151,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636943574151,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"f9ff15fc-c768-415f-b751-f64002c50e99\",\n    \"userNickname\": \"@at\",\n    \"userFullName\": \"Amy Noble\",\n    \"userAvatar\": \"file:///android_asset/avatars/7.jpg\",\n    \"text\": \"Persiusquot cu comprehensam.  Liberpropriae contentiones mus eum platonem affert dolore velit aliquip dis nisl.  Orcimauris dolorem movet augue aliquet facilisi.  Vitaeelementum taciti antiopam splendide aliquip sem discere doming urbanitas usu signiferumque cursus nihil purus corrumpit.  Suspendissewisi malorum iriure nominavi prodesset platonem expetenda idque netus convallis.  Theophrastuslectus potenti placerat cetero harum labores nec agam nonumy sententiae quam necessitatibus hinc senserit eu viverra potenti.\",\n    \"images\": [],\n    \"likes\": 583,\n    \"replyCount\": 401,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"1f60e1ce-f1e6-467a-86cd-60c2ba7b5860\",\n        \"userNickname\": \"@ut\",\n        \"userFullName\": \"Meredith Arnold\",\n        \"userAvatar\": \"file:///android_asset/avatars/32.jpg\",\n        \"text\": \"Suscipianturfelis maecenas adversarium justo lacinia pellentesque scelerisque ridens phasellus aliquam.  Comprehensamnetus aliquid omnesque dis urna possim fastidii usu felis faucibus luctus neque audire vidisse fusce option arcu.  Possedocendi mea sem fusce constituto persecuti aperiri adversarium quisque elitr viderer taciti parturient.  Blanditbibendum docendi lacus altera habemus dolor integer.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"bfdb474d-e472-480e-88cd-51f318ef727a\",\n        \"userNickname\": \"@conclu\",\n        \"userFullName\": \"Chadwick Bush\",\n        \"userAvatar\": \"file:///android_asset/avatars/61.jpg\",\n        \"text\": \"🚧🗼  leo  🕵 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174151,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636939974151,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"6a808537-26b0-4f13-a70e-39b9edb0cfe2\",\n    \"userNickname\": \"@litora\",\n    \"userFullName\": \"Mary Arnold\",\n    \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n    \"text\": \"nostrum  proin  🈂️ dictum  decore  🎋 inani  ocurreret  🏫😁 👒💂  iaculis \\n🎉⏱ sanctus phasellus  doctus  📤 noluisse  alterum  ⏸🔼 delectus  nascetur \\n🍹 purus  tractatos \\n😡✊ pharetra laudem  maluisset \\n🚡🚫 \",\n    \"images\": [],\n    \"likes\": 625,\n    \"replyCount\": 365,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"94363a81-4e98-4e32-be6e-e6c97b009a52\",\n        \"userNickname\": \"@nulla\",\n        \"userFullName\": \"Ignacio Kidd\",\n        \"userAvatar\": \"file:///android_asset/avatars/117.jpg\",\n        \"text\": \"movet\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636943574151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"449c6a04-4403-46e9-9776-0a522f89ea2c\",\n        \"userNickname\": \"@cetero\",\n        \"userFullName\": \"Jasmine Perez\",\n        \"userAvatar\": \"file:///android_asset/avatars/60.jpg\",\n        \"text\": \"Atquicetero primis tortor qui sadipscing felis tristique ridiculus consectetur repudiandae salutatus regione tota detracto ea omittam velit.  Meipostea graece discere suscipiantur labores inciderint eros.  Ridiculusdelenit noster iuvaret graece platonem referrentur pellentesque habitant appareat appetere.  Quinisi massa ornatus ex noster vim.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636972374151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b3e3ae8f-2229-4bbd-b42d-41f34a83ae0b\",\n        \"userNickname\": \"@repudi\",\n        \"userFullName\": \"Florine Finch\",\n        \"userAvatar\": \"file:///android_asset/avatars/127.jpg\",\n        \"text\": \"habitant latine convallis discere mauris\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"dfedcb88-d1a9-4d53-a29a-270559876779\",\n        \"userNickname\": \"@medioc\",\n        \"userFullName\": \"Delbert Forbes\",\n        \"userAvatar\": \"file:///android_asset/avatars/169.jpg\",\n        \"text\": \"placerat urbanitas metus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e81e481c-5e1e-436b-8daa-66803314cefb\",\n        \"userNickname\": \"@phasel\",\n        \"userFullName\": \"Lynette Grant\",\n        \"userAvatar\": \"file:///android_asset/avatars/25.jpg\",\n        \"text\": \"Adolescensveritus consetetur veri dapibus proin delenit theophrastus impetus theophrastus cursus deterruisset.  Periculismus his dissentiunt conceptam urbanitas theophrastus dictas at metus consequat simul nullam.  Salutatusphasellus luctus curae mentitum evertitur pro eum quis dictum dicunt possim pericula tacimates alia eruditi luctus.  Dictassonet antiopam doming ludus esse vestibulum sagittis sadipscing falli audire habitant.  Sagittisveritus brute iudicabit atqui vero curae.  Utroquenoster omittam.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"901cef8b-423d-44e2-8d98-e3a01cc30eb8\",\n        \"userNickname\": \"@enim\",\n        \"userFullName\": \"Elinor Strong\",\n        \"userAvatar\": \"file:///android_asset/avatars/24.jpg\",\n        \"text\": \"Estrisus persequeris sit qualisque ad amet tristique tritani iriure commune senectus suscipiantur fabellas utamur cu liber aliquip viverra.  Putentdicta movet reformidans scelerisque detracto utamur utroque dis laudem ut an maiestatis atomorum rutrum mel nam impetus.  Fermentumerroribus legere lorem aenean delenit vix primis leo mollis aliquet dicat populo eos ante dolore prompta vix.  Dictassanctus an neglegentur theophrastus quaestio deterruisset tristique epicuri facilisi mauris faucibus autem morbi patrioque.  Reformidansutamur lobortis dico nam fugit ridens volumus potenti causae omittantur posidonium dicam docendi commodo habeo elitr.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374151,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"35411339-5abf-45fd-9a10-b585f0a306ee\",\n        \"userNickname\": \"@medioc\",\n        \"userFullName\": \"Sonny Walker\",\n        \"userAvatar\": \"file:///android_asset/avatars/152.jpg\",\n        \"text\": \"🍼 Dolorummaiestatis definitiones ante adversarium appareat ante vocent repudiandae ultricies dictas nonumes vituperata.  Populoquem nam nostra mentitum interdum antiopam convenire ridens vitae epicurei metus nihil civibus.  Hasius persecuti vulputate mollis volumus equidem prompta luptatum dolorum assueverit possit.  Justomaluisset cu sociosqu agam elit tincidunt debet noluisse alienum facilis.  Duifacilisis ridiculus antiopam rhoncus fuisset postea nec eruditi. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174152,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637001174151,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"2f393e6b-9a06-4c7a-8119-389c9d514aa7\",\n    \"userNickname\": \"@sea\",\n    \"userFullName\": \"Margo Carpenter\",\n    \"userAvatar\": \"file:///android_asset/avatars/81.jpg\",\n    \"text\": \"Posidoniumligula a.  Sitludus maximus tale iisque ac.\",\n    \"images\": [\n      \"file:///android_asset/post_images/9.jpg\",\n      \"file:///android_asset/post_images/20.jpg\"\n    ],\n    \"likes\": 433,\n    \"replyCount\": 430,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"fac5c998-719e-4273-9172-d71744f97c62\",\n        \"userNickname\": \"@verear\",\n        \"userFullName\": \"Ingrid Alvarez\",\n        \"userAvatar\": \"file:///android_asset/avatars/40.jpg\",\n        \"text\": \"🎩 scripta mucius pellentesque nam🕐🛶😎 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e00f833f-a594-44d9-9ec2-6d1190cb259c\",\n        \"userNickname\": \"@movet\",\n        \"userFullName\": \"Vaughn Quinn\",\n        \"userAvatar\": \"file:///android_asset/avatars/119.jpg\",\n        \"text\": \"Nasceturfinibus ultricies laoreet deserunt iudicabit tristique labores convallis affert tristique dictum mauris dis sonet vulputate nobis senectus tristique egestas.  Debetantiopam quis.  Inanimollis dictumst menandri inani cu quod elitr lectus dolorum fugit ceteros audire magna imperdiet.  Voluptatumcum ocurreret dolore ocurreret vidisse quaerendum explicari pri potenti nam periculis fabellas curabitur nulla voluptatum nullam nostra numquam dolorem.  Nonporta quaestio utroque.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774152,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636947174152,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"b48bb561-7b18-479e-86ef-e5a11b8ff288\",\n    \"userNickname\": \"@mutat\",\n    \"userFullName\": \"Staci Guerrero\",\n    \"userAvatar\": \"file:///android_asset/avatars/55.jpg\",\n    \"text\": \"veri  est  ☣️ \",\n    \"images\": [],\n    \"likes\": 43,\n    \"replyCount\": 198,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636975974152,\n    \"reply\": {\n      \"id\": \"b98be86c-60c3-4468-96d2-3ef2e8a2543b\",\n      \"userNickname\": \"@aeque\",\n      \"userFullName\": \"Dylan Moses\",\n      \"userAvatar\": \"file:///android_asset/avatars/76.jpg\",\n      \"text\": \"fusce melius habeo\",\n      \"images\": [],\n      \"likes\": 786,\n      \"replyCount\": 684,\n      \"messages\": 0,\n      \"comments\": [],\n      \"createdAt\": 1636961574152,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": false,\n        \"totalVotes\": 683,\n        \"positions\": [\n          {\n            \"text\": \"a atqui ocurreret\",\n            \"voted\": 179\n          },\n          {\n            \"text\": \"cras tristique cu eam\",\n            \"voted\": 152\n          },\n          {\n            \"text\": \"explicari turpis suavitate\",\n            \"voted\": 26\n          },\n          {\n            \"text\": \"atqui mandamus ea volutpat epicurei\",\n            \"voted\": 326\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"adda6e7c-997a-4a96-a7b2-f2bbeb995f55\",\n    \"userNickname\": \"@possit\",\n    \"userFullName\": \"Julius Dean\",\n    \"userAvatar\": \"file:///android_asset/avatars/125.jpg\",\n    \"text\": \"graeci\",\n    \"images\": [],\n    \"likes\": 49,\n    \"replyCount\": 461,\n    \"messages\": 1,\n    \"comments\": [\n      {\n        \"id\": \"dc6e89e2-a0b8-4bbb-9141-d2009afaa810\",\n        \"userNickname\": \"@tale\",\n        \"userFullName\": \"Sammie Wooten\",\n        \"userAvatar\": \"file:///android_asset/avatars/38.jpg\",\n        \"text\": \"pericula\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974152,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636968774152,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 1316,\n      \"positions\": [\n        {\n          \"text\": \"mi aptent voluptatibus nullam aliquip\",\n          \"voted\": 207\n        },\n        {\n          \"text\": \"tristique dapibus homero nunc tation\",\n          \"voted\": 412\n        },\n        {\n          \"text\": \"augue graece dignissim idque torquent\",\n          \"voted\": 253\n        },\n        {\n          \"text\": \"quod mei sociis intellegebat ornare\",\n          \"voted\": 309\n        },\n        {\n          \"text\": \"graeci eam curabitur\",\n          \"voted\": 135\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"ceb37ce9-751c-4332-bf31-725cf41bbbc5\",\n    \"userNickname\": \"@senser\",\n    \"userFullName\": \"Gayle Francis\",\n    \"userAvatar\": \"file:///android_asset/avatars/76.jpg\",\n    \"text\": \"🛄 Postulantmontes iudicabit numquam verterem reprimique meliore offendit bibendum possit sociosqu.  Senseritalia sumo necessitatibus moderatius melius sociis detracto iaculis maecenas nonumes luptatum nostra sale fugit maximus fastidii.  Erosnostrum risus amet animal eripuit sanctus sea intellegat intellegebat ius latine platonem deserunt. \",\n    \"images\": [],\n    \"likes\": 122,\n    \"replyCount\": 662,\n    \"messages\": 7,\n    \"comments\": [\n      {\n        \"id\": \"cd31c18b-28b5-4226-a72b-25991a22f613\",\n        \"userNickname\": \"@propri\",\n        \"userFullName\": \"Marlin Daniels\",\n        \"userAvatar\": \"file:///android_asset/avatars/172.jpg\",\n        \"text\": \"Magnamei efficitur fermentum ferri repudiandae lobortis sagittis mus quisque vituperatoribus.  Suavitateusu persecuti ius adipiscing.  Senseritsanctus legere efficitur eos consul nihil curabitur pretium propriae nominavi diam dicunt.  Vituperatanetus melius contentiones singulis eum postulant graecis nibh voluptatum neque hac.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"ea6ef001-a154-4890-9ff4-4115853b459b\",\n        \"userNickname\": \"@quaest\",\n        \"userFullName\": \"Josef McMahon\",\n        \"userAvatar\": \"file:///android_asset/avatars/84.jpg\",\n        \"text\": \"repudiandae  dolores \\n👨‍✈ consectetur autem  arcu \\n🎠💒 similique  quas \\n🥙 eius suscipit  eu  🥙 quem  accusata \\n🔹🕋 inimicus  oporteat \\n🔒 ac splendide  nonumy \\n🐕 🥙▫️  ancillae \\n🕡 ⛈👅  nec  🍫 laoreet augue  gravida \\n🐳🌺 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b142ef5e-9e60-4467-8039-da227f2b3a38\",\n        \"userNickname\": \"@princi\",\n        \"userFullName\": \"Aline Espinoza\",\n        \"userAvatar\": \"file:///android_asset/avatars/144.jpg\",\n        \"text\": \"legere fames\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"9867c9ff-f5ea-4952-bc50-4ddd83b029a5\",\n        \"userNickname\": \"@nunc\",\n        \"userFullName\": \"Deloris McMahon\",\n        \"userAvatar\": \"file:///android_asset/avatars/114.jpg\",\n        \"text\": \"🔦\\nMelsemper meliore voluptatum numquam nulla sonet possit civibus eos.  Egetaenean saperet suas felis ludus fusce conclusionemque taciti dicant diam urna maluisset utamur alia rutrum quo sadipscing.  Elementumviverra nonumes solum nisl turpis his suas mandamus feugait.  Conguemauris tation nisi dissentiunt dolorum populo menandri rutrum quo eget pulvinar.  Massalibero consectetuer dolore magnis habemus natoque. \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"1f3be7fa-d4d6-4aae-89f3-8d0ba42f2ad8\",\n        \"userNickname\": \"@feugai\",\n        \"userFullName\": \"Mitchell Cantrell\",\n        \"userAvatar\": \"file:///android_asset/avatars/18.jpg\",\n        \"text\": \"🌩📧\\nPostulantveri tempus.  Appareatnibh dolore movet minim ceteros duo propriae augue aeque magna prompta tempor mediocrem potenti accusata gubergren idque.  Donecdiam posuere rutrum cu pellentesque ad habitasse putent graeco blandit animal posuere.  Nostraexplicari elitr populo dolore adipiscing vivamus feugiat.  Pellentesquemalorum quaeque.  Ornarelaudem malesuada libris dictum lectus dicit quaerendum electram curae ponderum efficitur eum numquam consul doctus delicata graecis convenire.🐵🥟\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"cac9454f-c0b6-448a-bd01-368c4c742b29\",\n        \"userNickname\": \"@sollic\",\n        \"userFullName\": \"Cornelius Cunningham\",\n        \"userAvatar\": \"file:///android_asset/avatars/139.jpg\",\n        \"text\": \"🦂🍀🌧 antiopam patrioque tellus fermentum🌬✋ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e979e7d2-8846-40e7-ade3-bfef6835d9ad\",\n        \"userNickname\": \"@pericu\",\n        \"userFullName\": \"Cindy Christian\",\n        \"userAvatar\": \"file:///android_asset/avatars/68.jpg\",\n        \"text\": \"🏪\\ndebet atqui autem🖨💠\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774152,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636947174152,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"a715f45c-0ba5-42c8-9658-46623c4c7f64\",\n    \"userNickname\": \"@movet\",\n    \"userFullName\": \"Rachelle Bonner\",\n    \"userAvatar\": \"file:///android_asset/avatars/91.jpg\",\n    \"text\": \"lectus verterem libris\",\n    \"images\": [],\n    \"likes\": 769,\n    \"replyCount\": 471,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"3f8b96ec-0311-4ad7-87f7-90b1315662ef\",\n        \"userNickname\": \"@maiest\",\n        \"userFullName\": \"Felix Vaughn\",\n        \"userAvatar\": \"file:///android_asset/avatars/31.jpg\",\n        \"text\": \"🦏☂️ Voluptariaiuvaret homero adversarium persius simul lobortis sagittis varius omnesque simul senectus equidem tempus senserit.  Inceptosmelius suavitate molestie.  Magnisindoctum similique omittam latine.👍 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174152,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"666f0e59-8c47-4baa-9805-01931991401f\",\n        \"userNickname\": \"@utroqu\",\n        \"userFullName\": \"Yvette Hammond\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"Audirenisl brute metus convallis maiorum suas honestatis tation pri risus.  Mediocremest convenire diam persecuti eam an verterem ferri.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174152,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636986774152,\n    \"reply\": null,\n    \"poll\": {\n      \"isCompleted\": true,\n      \"totalVotes\": 974,\n      \"positions\": [\n        {\n          \"text\": \"partiendo sapien\",\n          \"voted\": 187\n        },\n        {\n          \"text\": \"odio discere\",\n          \"voted\": 152\n        },\n        {\n          \"text\": \"placerat eos ignota adhuc aliquid\",\n          \"voted\": 228\n        },\n        {\n          \"text\": \"liber ancillae consetetur neque an\",\n          \"voted\": 284\n        },\n        {\n          \"text\": \"solum postea gloriatur\",\n          \"voted\": 123\n        }\n      ]\n    }\n  },\n  {\n    \"id\": \"19f819fd-86df-4391-874e-d5d73f92622e\",\n    \"userNickname\": \"@inveni\",\n    \"userFullName\": \"Mona Cardenas\",\n    \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n    \"text\": \"🍌🌊  deserunt \\n🚚 \",\n    \"images\": [],\n    \"likes\": 45,\n    \"replyCount\": 87,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636968774153,\n    \"reply\": {\n      \"id\": \"3ea2bea3-bcfd-45df-96ba-4297f703bc99\",\n      \"userNickname\": \"@primis\",\n      \"userFullName\": \"Pauline Pruitt\",\n      \"userAvatar\": \"file:///android_asset/avatars/188.jpg\",\n      \"text\": \"🗻🌠  natum  ⛓🚒 🏪🐹  labores  ☔️ bibendum primis  tristique  🕢⏲ \",\n      \"images\": [],\n      \"likes\": 901,\n      \"replyCount\": 239,\n      \"messages\": 6,\n      \"comments\": [\n        {\n          \"id\": \"dfd9fd06-6f3e-4944-a129-6753353782be\",\n          \"userNickname\": \"@graece\",\n          \"userFullName\": \"Emmanuel Gilliam\",\n          \"userAvatar\": \"file:///android_asset/avatars/7.jpg\",\n          \"text\": \"😊 Haspostea dolore maiestatis adolescens dissentiunt tincidunt tale definiebas nisi referrentur tamquam pulvinar sociosqu ceteros cursus solum ultrices suas.  Elaboraretdictas aliquam eius mnesarchum consetetur efficiantur graeco eam arcu mel duis unum accusata tation.  Magnisviris quaerendum.  Fugithonestatis a adipisci senserit singulis vidisse constituam principes.  Doloremassa mea vulputate nonumes omnesque lorem accommodare signiferumque ancillae epicurei mediocrem labores intellegebat solet. \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"640d88a8-98ed-45de-9e82-0f3d26bb6fbe\",\n          \"userNickname\": \"@sociis\",\n          \"userFullName\": \"Eleanor Rowe\",\n          \"userAvatar\": \"file:///android_asset/avatars/78.jpg\",\n          \"text\": \"aliquid voluptatum\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636957974153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"0d90f3ed-0171-4656-9a0e-51cc73f80ab1\",\n          \"userNickname\": \"@dicat\",\n          \"userFullName\": \"Earline Callahan\",\n          \"userAvatar\": \"file:///android_asset/avatars/25.jpg\",\n          \"text\": \"Appareatmentitum sagittis partiendo voluptatum epicuri brute debet velit consequat penatibus expetenda graeci sanctus legere posse vulputate inimicus noster.  Egestascausae offendit dissentiunt lacus posse movet mnesarchum urna velit donec mucius lorem periculis dicta vivendo.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636936374153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"fd875296-0b70-49b9-b078-ccb92e00be7f\",\n          \"userNickname\": \"@nonume\",\n          \"userFullName\": \"Kristen Booker\",\n          \"userAvatar\": \"file:///android_asset/avatars/31.jpg\",\n          \"text\": \"🎎🍓\\nvocibus tibique🚓 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8201c846-e329-4c82-b2f1-57e9f7fede3a\",\n          \"userNickname\": \"@porta\",\n          \"userFullName\": \"Aisha Banks\",\n          \"userAvatar\": \"file:///android_asset/avatars/17.jpg\",\n          \"text\": \"🦒🍣  minim  🖌🎶 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636947174153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"8685d0ed-6bba-4f72-8974-04afbd5ef32a\",\n          \"userNickname\": \"@sumo\",\n          \"userFullName\": \"Jack Fuentes\",\n          \"userAvatar\": \"file:///android_asset/avatars/198.jpg\",\n          \"text\": \"venenatis phasellus  lacus \\n↘️㊙️ elaboraret  referrentur  🌕 👧✊  laudem \\n🏜 💅🌌  aliquet  😉 🔔🍘  nonumes  ⛽️ 🈺🐏  labores \\n🐊🚲 🦒🤵  debet  🌿 explicari natoque  viverra \\n🛒🐸 libris magna  cum \\n🍠 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636957974153,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636965174153,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"1e754094-65f0-4c37-ab82-ba63a6ebee5e\",\n    \"userNickname\": \"@latine\",\n    \"userFullName\": \"Antonia Church\",\n    \"userAvatar\": \"file:///android_asset/avatars/116.jpg\",\n    \"text\": \"populo quod  aptent  🍀 \",\n    \"images\": [],\n    \"likes\": 46,\n    \"replyCount\": 194,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636957974153,\n    \"reply\": {\n      \"id\": \"7055de32-2d8a-4b99-99af-ed781ddc9366\",\n      \"userNickname\": \"@nonume\",\n      \"userFullName\": \"Wendy Sutton\",\n      \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n      \"text\": \"🏟🛵🕌 Quireque ocurreret quem urbanitas feugait libris mentitum reprehendunt simul dico.  Adolescenslitora ferri dignissim senserit.  Nullamadversarium inciderint ancillae reformidans gravida facilisis iusto efficitur ponderum tincidunt ut.  Integerantiopam adolescens commune tempor facilisi neglegentur senectus ludus iaculis utinam ocurreret.  Fuissetdefiniebas penatibus consectetuer et mandamus suavitate eirmod laudem ac has hac.  Convallisquo libris mentitum putent splendide voluptaria ferri dis accusata nulla tritani molestiae primis vocibus integer saepe. \",\n      \"images\": [\n        \"file:///android_asset/post_images/58.jpg\"\n      ],\n      \"likes\": 48,\n      \"replyCount\": 250,\n      \"messages\": 3,\n      \"comments\": [\n        {\n          \"id\": \"9b1b7c53-d7f9-44cc-98e8-385c96d3850a\",\n          \"userNickname\": \"@dicit\",\n          \"userFullName\": \"Seymour Peck\",\n          \"userAvatar\": \"file:///android_asset/avatars/36.jpg\",\n          \"text\": \"interesset inimicus  volumus \\n😓👨‍🎤 ⏰🤶  elitr \\n🔥🛢 signiferumque cetero  platea \\n🚒‼️ 🚄🍝  utinam  🐕 rutrum lacus  ius  🚔 bibendum  tation  🥢🌘 mus maluisset  quas  🍦🤑 💏📅  morbi  🙌 🚏🚕  praesent \\n🍺👩‍❤️‍👩 🍝👨‍🔬  suas  🌶🤵 🖼🙂  ubique  🆓🍭 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636932774153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6d033cfd-262b-42f3-95b4-092d4a343375\",\n          \"userNickname\": \"@placer\",\n          \"userFullName\": \"Casey Frazier\",\n          \"userAvatar\": \"file:///android_asset/avatars/176.jpg\",\n          \"text\": \"eum\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636983174153,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"41043a62-42bc-45d7-9e1b-03fc554af3e3\",\n          \"userNickname\": \"@dignis\",\n          \"userFullName\": \"Collin Walsh\",\n          \"userAvatar\": \"file:///android_asset/avatars/166.jpg\",\n          \"text\": \"🚜⛵️🌳 Erosetiam evertitur ad viris rhoncus ius electram wisi pri errem velit ad sapientem parturient.  Dolorumpostulant quem ius inani ius sem.  Periculafabellas mediocritatem cetero convenire euismod minim mea possim omnesque agam ancillae tristique honestatis noster no utinam mediocritatem quaerendum sonet.🍥🔦🕔\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636965174153,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636950774153,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"3fc364bf-9045-4c08-945f-de0a5e6209da\",\n    \"userNickname\": \"@tantas\",\n    \"userFullName\": \"Roy Fletcher\",\n    \"userAvatar\": \"file:///android_asset/avatars/187.jpg\",\n    \"text\": \"vituperata omittantur  assueverit  📈 🀄️🍴  ad  ↩️ tempor  neglegentur  ⚰️ 🐙🎎  ligula  💢🚖 etiam malorum  repudiare \\n⤵️ \",\n    \"images\": [],\n    \"likes\": 888,\n    \"replyCount\": 39,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"bf612145-c66f-4630-b68d-06f585185cac\",\n        \"userNickname\": \"@condim\",\n        \"userFullName\": \"Dominic Craft\",\n        \"userAvatar\": \"file:///android_asset/avatars/123.jpg\",\n        \"text\": \"🐢🤳🤔\\nFusceerat utinam ferri morbi invidunt vero aliquet mollis vituperata pellentesque ad ligula orci consectetur pulvinar.  Delectuscontentiones natum.📍🐪\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637004774153,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"8de1df99-5730-4931-86f2-e72f552383c7\",\n        \"userNickname\": \"@consti\",\n        \"userFullName\": \"Lazaro Sims\",\n        \"userAvatar\": \"file:///android_asset/avatars/64.jpg\",\n        \"text\": \"pro dictas mi\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a821bda2-cd7e-4e37-90ab-cad6a7d7128b\",\n        \"userNickname\": \"@doloru\",\n        \"userFullName\": \"Truman Kane\",\n        \"userAvatar\": \"file:///android_asset/avatars/13.jpg\",\n        \"text\": \"alia iudicabit graeci\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636954374154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a7531f08-0782-4e21-91a4-1949d123e695\",\n        \"userNickname\": \"@montes\",\n        \"userFullName\": \"Marianne Hammond\",\n        \"userAvatar\": \"file:///android_asset/avatars/89.jpg\",\n        \"text\": \"mollis contentiones aperiri\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"7d943997-6cc2-4587-9a8b-6083b83842c0\",\n        \"userNickname\": \"@adipis\",\n        \"userFullName\": \"Vivian Fulton\",\n        \"userAvatar\": \"file:///android_asset/avatars/85.jpg\",\n        \"text\": \"Dignissimeruditi pertinacia natoque sit.  Principesdicam elaboraret vulputate deseruisse cursus molestiae hinc fuisset oratio bibendum consequat ubique pertinacia possit graeco laudem habitant sonet ei.  Fallihabeo suscipit vero error habemus ancillae similique lacinia ancillae.  Definitionesinvenire vero tractatos.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"3d02e7fa-ed66-4b50-9238-b212da493132\",\n        \"userNickname\": \"@eirmod\",\n        \"userFullName\": \"Roderick Bray\",\n        \"userAvatar\": \"file:///android_asset/avatars/4.jpg\",\n        \"text\": \"Domingsenserit consetetur in pulvinar suscipit.  Decoresenectus unum saperet assueverit solum invenire euripidis brute referrentur inceptos.  Verituslaoreet definitiones posidonium delenit purus expetenda.  Soletlorem reformidans erroribus omnesque venenatis.  Sodaleslaudem luptatum eam hinc referrentur mus corrumpit persecuti no fabulas malorum lectus doctus pri errem.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"695d944a-7298-45df-9fbb-e884fcc4bf97\",\n        \"userNickname\": \"@eloque\",\n        \"userFullName\": \"Lewis Mitchell\",\n        \"userAvatar\": \"file:///android_asset/avatars/191.jpg\",\n        \"text\": \"doctus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a29bb97a-190c-44e9-a356-b46e198780df\",\n        \"userNickname\": \"@qui\",\n        \"userFullName\": \"Riley McNeil\",\n        \"userAvatar\": \"file:///android_asset/avatars/161.jpg\",\n        \"text\": \"Expetendapetentium ante erat ac laoreet feugait vis inani curae maiestatis ex justo.  Communevocent oratio maiorum sagittis lacinia.  Muciusaliquet dicit pertinacia quaestio ante turpis.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637001174154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f1640728-949f-481d-a2b7-623eee40a5bc\",\n        \"userNickname\": \"@deleni\",\n        \"userFullName\": \"Cecelia Keller\",\n        \"userAvatar\": \"file:///android_asset/avatars/167.jpg\",\n        \"text\": \"🌟🛋  ius \\n🎁🔢 🚔🛒  volumus  👨‍🎓 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374154,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636968774153,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"f0f03bf4-0438-43cb-9c8e-587845078956\",\n    \"userNickname\": \"@facili\",\n    \"userFullName\": \"Annie Frank\",\n    \"userAvatar\": \"file:///android_asset/avatars/184.jpg\",\n    \"text\": \"Evertiturquam erat class at quam detracto petentium habeo graece singulis.  Electramnihil fuisset vitae atomorum habitasse diam mi deseruisse suspendisse platonem labores.  Venenatisscripta nonumes causae tempor doming.  Aliquetblandit interesset ea utamur tota lectus habeo.  Graecenonumy vocibus nibh.  Quodmediocrem maximus lorem mucius.\",\n    \"images\": [],\n    \"likes\": 547,\n    \"replyCount\": 652,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"09c59ad2-c23b-4aa7-b077-058bb283c088\",\n        \"userNickname\": \"@perpet\",\n        \"userFullName\": \"Magdalena Horton\",\n        \"userAvatar\": \"file:///android_asset/avatars/132.jpg\",\n        \"text\": \"Loremmnesarchum persius iriure congue erat altera sea electram inciderint magnis nominavi neque autem risus numquam fames feugait melius veri.  Sociosqudis mattis recteque mucius libris ius iudicabit eget aliquid interesset percipit.  Temporaffert magnis tation ipsum sollicitudin conceptam solum quaeque.  Sanctushabitasse sanctus constituam indoctum numquam posse vehicula nam mazim tritani praesent necessitatibus nostra maiorum quaerendum erroribus.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636936374154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"7857f69a-8e1c-430e-9f0c-4f80f14733ca\",\n        \"userNickname\": \"@idque\",\n        \"userFullName\": \"Ray McMahon\",\n        \"userAvatar\": \"file:///android_asset/avatars/140.jpg\",\n        \"text\": \"Porttitorridiculus possim doctus adolescens neglegentur verterem mandamus his dicant molestie curae erroribus viris graeco eius dicam quaestio phasellus.  Vituperataconceptam nisl ferri massa lacinia massa pertinax equidem iusto adolescens tale principes dicunt dicant lobortis non sanctus sollicitudin cubilia.  Pellentesquevoluptaria dolor signiferumque.  Mediocremluptatum class has dictum risus sale dissentiunt similique mazim nullam facilisis signiferumque option.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"73f69612-2e68-4384-a36b-3429b586b8a8\",\n        \"userNickname\": \"@prompt\",\n        \"userFullName\": \"Kelly Mitchell\",\n        \"userAvatar\": \"file:///android_asset/avatars/110.jpg\",\n        \"text\": \"Volumussapientem congue eius evertitur integer.  Felisancillae causae vituperata graeco antiopam oratio vis morbi legere invidunt utroque cursus magna.  Urnarepudiare feugait expetenda faucibus scripserit nisi persecuti sumo tota molestie malesuada.  Evertiturconsetetur ancillae iisque noluisse labores accumsan pericula curabitur cras contentiones parturient nibh leo honestatis.  Posidoniumauctor quis ea definitionem.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636939974154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e48656da-966d-411d-be1f-1c0663236f05\",\n        \"userNickname\": \"@graeci\",\n        \"userFullName\": \"Judy Edwards\",\n        \"userAvatar\": \"file:///android_asset/avatars/92.jpg\",\n        \"text\": \"Soletex appetere patrioque qui suas fusce eirmod scripserit sodales facilisis.  Adversariumsale ad interpretaris elementum splendide penatibus possim nonumy oratio nascetur deterruisset conclusionemque.  Sententiaeurbanitas vel porro quaerendum gubergren consequat augue id varius.  Esseverterem tincidunt simul reformidans dico.  Docendimi nominavi quis quod mei.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"34fe7ce2-9edb-4b73-98f8-9dae4cfa6fc5\",\n        \"userNickname\": \"@errori\",\n        \"userFullName\": \"Roy Jennings\",\n        \"userAvatar\": \"file:///android_asset/avatars/175.jpg\",\n        \"text\": \"😍🍿👩‍❤️‍👩 ornatus alia docendi dissentiunt💯🐁 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"938e14f1-87f5-4d64-8d13-debcf849b0ee\",\n        \"userNickname\": \"@dignis\",\n        \"userFullName\": \"Maryann York\",\n        \"userAvatar\": \"file:///android_asset/avatars/38.jpg\",\n        \"text\": \"🗜🛤  suscipiantur  🎚🥐 himenaeos  ornare  💡 has viverra  elaboraret  🚍 persequeris  ancillae  ⏲🍣 convallis graeco  petentium \\n⚒ \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"4594d5da-acb2-4a93-a7b4-9f4bd77bfe1d\",\n        \"userNickname\": \"@mea\",\n        \"userFullName\": \"Daren Webster\",\n        \"userAvatar\": \"file:///android_asset/avatars/105.jpg\",\n        \"text\": \"legimus singulis fusce fabulas\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"bf584316-adab-4ee4-b338-8fe92ed67ee7\",\n        \"userNickname\": \"@habita\",\n        \"userFullName\": \"Shelby Ashley\",\n        \"userAvatar\": \"file:///android_asset/avatars/128.jpg\",\n        \"text\": \"Ceterosaliquip an consul aliquam causae sociosqu ponderum petentium phasellus sed has alterum liber.  Disceredoming amet lectus repudiandae natum cum pri.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636929174154,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"51d82112-f1a1-4a21-b0aa-b8da31a807be\",\n        \"userNickname\": \"@viverr\",\n        \"userFullName\": \"Evan Estrada\",\n        \"userAvatar\": \"file:///android_asset/avatars/179.jpg\",\n        \"text\": \"⏰🍒\\nexplicari deseruisse laudem saperet pertinacia6️⃣🦋📗\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636950774154,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636968774154,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"b08c5820-3d48-4cfd-a754-a5f46eb4dcfd\",\n    \"userNickname\": \"@erat\",\n    \"userFullName\": \"Cheri Porter\",\n    \"userAvatar\": \"file:///android_asset/avatars/86.jpg\",\n    \"text\": \"posuere et homero\",\n    \"images\": [],\n    \"likes\": 94,\n    \"replyCount\": 137,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636932774154,\n    \"reply\": {\n      \"id\": \"1235d02b-648b-4477-9420-9dc9eff59ae1\",\n      \"userNickname\": \"@ei\",\n      \"userFullName\": \"Jaime Heath\",\n      \"userAvatar\": \"file:///android_asset/avatars/162.jpg\",\n      \"text\": \"🚧\\nlaudem dolor🥥\\n\",\n      \"images\": [],\n      \"likes\": 821,\n      \"replyCount\": 312,\n      \"messages\": 4,\n      \"comments\": [\n        {\n          \"id\": \"0e6fb3d1-a0a6-410a-95c0-55b130aeea8a\",\n          \"userNickname\": \"@mel\",\n          \"userFullName\": \"Kellie Chase\",\n          \"userAvatar\": \"file:///android_asset/avatars/65.jpg\",\n          \"text\": \"🐖🗼👀\\nmoderatius hac tortor platea ad🥙🥗 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636975974154,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"2a81024e-be78-46b3-b530-ba3692bb37ab\",\n          \"userNickname\": \"@solum\",\n          \"userFullName\": \"Maryellen Marshall\",\n          \"userAvatar\": \"file:///android_asset/avatars/40.jpg\",\n          \"text\": \"🕋👲\\nQuotpetentium nihil contentiones vehicula posse vim eripuit utroque feugait vitae viverra suspendisse mazim duo iudicabit utamur.  Dissentiunttaciti reprehendunt risus ornare similique.🚳🌖🧥\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636990374154,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"3985940a-0bfb-42cd-979b-10247ebbf1eb\",\n          \"userNickname\": \"@accusa\",\n          \"userFullName\": \"Socorro Sellers\",\n          \"userAvatar\": \"file:///android_asset/avatars/81.jpg\",\n          \"text\": \"erroribus  no  🍇🤷‍♀ \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636961574154,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"7cb39020-15b5-42ef-97f1-476882090e18\",\n          \"userNickname\": \"@dolor\",\n          \"userFullName\": \"Cruz Chan\",\n          \"userAvatar\": \"file:///android_asset/avatars/2.jpg\",\n          \"text\": \"🗒㊗️🌚\\noporteat tractatos🍬😼🅾️\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637001174154,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636932774154,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 936,\n        \"positions\": [\n          {\n            \"text\": \"docendi delicata fringilla\",\n            \"voted\": 326\n          },\n          {\n            \"text\": \"saperet consul hac\",\n            \"voted\": 405\n          },\n          {\n            \"text\": \"nulla prompta\",\n            \"voted\": 205\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"ffa9f22b-c2bb-4576-84b2-c061b32daaa0\",\n    \"userNickname\": \"@indoct\",\n    \"userFullName\": \"Stewart Boyer\",\n    \"userAvatar\": \"file:///android_asset/avatars/136.jpg\",\n    \"text\": \"graeco maecenas facilis verear\",\n    \"images\": [],\n    \"likes\": 78,\n    \"replyCount\": 50,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1637008374154,\n    \"reply\": {\n      \"id\": \"da9348d5-9c22-4b83-b2d9-5cf898aeb739\",\n      \"userNickname\": \"@maluis\",\n      \"userFullName\": \"Scott Russo\",\n      \"userAvatar\": \"file:///android_asset/avatars/71.jpg\",\n      \"text\": \"Feugaitludus graeci adolescens numquam dicit liber repudiandae voluptatum platea dicunt gubergren neque omittam iudicabit tritani urna scelerisque suspendisse inceptos.  Diamaltera tincidunt interdum contentiones mediocritatem eripuit.  Verisimilique scripserit.  Harumpossit ne ancillae sale sed quod principes necessitatibus ornare dissentiunt utroque errem appetere ipsum autem pulvinar scelerisque prompta.  Hasexpetenda imperdiet invenire corrumpit recteque.  Instructiorleo interpretaris vituperata dicant solet postulant consectetuer.\",\n      \"images\": [\n        \"file:///android_asset/post_images/68.jpg\",\n        \"file:///android_asset/post_images/24.jpg\"\n      ],\n      \"likes\": 422,\n      \"replyCount\": 275,\n      \"messages\": 8,\n      \"comments\": [\n        {\n          \"id\": \"aee2d5a0-a4e6-42ee-a772-161ae6ed1961\",\n          \"userNickname\": \"@medioc\",\n          \"userFullName\": \"Seth Mitchell\",\n          \"userAvatar\": \"file:///android_asset/avatars/126.jpg\",\n          \"text\": \"Habeovoluptatibus dictum.  Dictumstluptatum natum porta dicam elitr epicuri habitant melius elitr esse consequat.  Antecontentiones discere causae ferri natum instructior dignissim gubergren dicam arcu.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"f9800b3f-9c61-4949-a688-6804eb534460\",\n          \"userNickname\": \"@detrax\",\n          \"userFullName\": \"Kate Williams\",\n          \"userAvatar\": \"file:///android_asset/avatars/112.jpg\",\n          \"text\": \"🖐🎠🍰 Felisgraece turpis aliquet postea omnesque populo ipsum doctus eros fastidii quaerendum fringilla alterum.  Eloquentiameloquentiam unum ornatus aliquam eruditi erroribus eu taciti eget odio aliquid tellus convenire invidunt constituam.🚶‍♀\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636990374155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"c699f155-3330-4e65-b7e0-9f22fd92b214\",\n          \"userNickname\": \"@nascet\",\n          \"userFullName\": \"Isaac Perry\",\n          \"userAvatar\": \"file:///android_asset/avatars/18.jpg\",\n          \"text\": \"💴\\nreque evertitur per repudiare iriure🌘 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"77812b8f-e877-40df-bdcb-36fdbdf4d183\",\n          \"userNickname\": \"@himena\",\n          \"userFullName\": \"Armando Valenzuela\",\n          \"userAvatar\": \"file:///android_asset/avatars/86.jpg\",\n          \"text\": \"a faucibus\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636943574155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"070ea41d-d3c9-46d5-8beb-a805b155722e\",\n          \"userNickname\": \"@finibu\",\n          \"userFullName\": \"Deanne Carr\",\n          \"userAvatar\": \"file:///android_asset/avatars/139.jpg\",\n          \"text\": \"Laudemfusce ei assueverit ludus moderatius habemus cursus dictum latine vehicula epicuri.  Exqualisque proin scripserit varius quem cu hac et semper alia impetus partiendo hinc dictas.  Delenitsalutatus class homero enim senserit facilis hinc honestatis eleifend malesuada ornatus evertitur utroque tacimates.  Reformidanssolet salutatus propriae condimentum iisque efficiantur aperiri posse discere viris autem.  Eloquentiamporttitor nonumy his massa ius constituto consul tempor commodo posidonium qualisque tempor iriure dolores dis eros nec bibendum.  Vimcommune evertitur vestibulum torquent dicam fringilla malorum erat varius metus quot adipiscing in laudem diam feugiat suscipiantur cursus laudem.\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"4034c3bd-6980-4487-a4e8-3e1c331f309e\",\n          \"userNickname\": \"@dis\",\n          \"userFullName\": \"Bianca Carney\",\n          \"userAvatar\": \"file:///android_asset/avatars/150.jpg\",\n          \"text\": \"🏮🛋\\nat✨🚎🐤\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"6dc799cc-9c05-46a9-bd48-123211c5cbb3\",\n          \"userNickname\": \"@his\",\n          \"userFullName\": \"Lionel McIntosh\",\n          \"userAvatar\": \"file:///android_asset/avatars/186.jpg\",\n          \"text\": \"♊️\\nputent ei quo qualisque gubergren🔟🕟\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637011974155,\n          \"reply\": null,\n          \"poll\": null\n        },\n        {\n          \"id\": \"047f8c4e-ee93-460a-9f3f-1b63314f066e\",\n          \"userNickname\": \"@ponder\",\n          \"userFullName\": \"Kristina Keller\",\n          \"userAvatar\": \"file:///android_asset/avatars/2.jpg\",\n          \"text\": \"📍🉐🕥\\nAppetereinteresset dissentiunt solet velit sumo salutatus aliquip vestibulum solum sapien semper pharetra reprehendunt.  Praesentantiopam omittam neque sociosqu splendide congue posse vero accumsan ultricies fames dolor bibendum nibh viverra tacimates definitiones interdum.  Latineminim accumsan intellegat feugiat vocibus ante interpretaris quo inceptos omittam.  Nosterno efficiantur feugiat regione conclusionemque.\\n\",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1636939974155,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636993974154,\n      \"reply\": null,\n      \"poll\": null\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"5a0d9cab-1b8c-48b0-96c7-7e662d71b3e7\",\n    \"userNickname\": \"@sapien\",\n    \"userFullName\": \"Maynard Hull\",\n    \"userAvatar\": \"file:///android_asset/avatars/196.jpg\",\n    \"text\": \"Adlacus gloriatur.  Ligulasalutatus reque quod omnesque eruditi.\",\n    \"images\": [\n      \"file:///android_asset/post_images/61.jpg\",\n      \"file:///android_asset/post_images/57.jpg\"\n    ],\n    \"likes\": 394,\n    \"replyCount\": 392,\n    \"messages\": 2,\n    \"comments\": [\n      {\n        \"id\": \"f965a3d3-2bb1-4338-bfb7-60974dd824a0\",\n        \"userNickname\": \"@posuer\",\n        \"userFullName\": \"Gretchen Woodward\",\n        \"userAvatar\": \"file:///android_asset/avatars/27.jpg\",\n        \"text\": \"🚕🚞  sumo  🛶 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"734a9ddc-5ade-4bbd-9422-513a7298fc37\",\n        \"userNickname\": \"@ludus\",\n        \"userFullName\": \"Roger Carver\",\n        \"userAvatar\": \"file:///android_asset/avatars/23.jpg\",\n        \"text\": \"Habitasseputent ultrices suavitate maximus imperdiet signiferumque omnesque tation elitr affert mauris suscipit brute deseruisse conclusionemque erat pretium.  Fabellaspulvinar legimus moderatius curabitur saperet suscipiantur quis fuisset has platea dui.  Porropertinacia veniam viderer sapien ancillae persecuti sed errem civibus.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574155,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1637001174155,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"701a8fdb-dbe9-4b9e-9d97-0d1e5c80b252\",\n    \"userNickname\": \"@duo\",\n    \"userFullName\": \"Donna Potter\",\n    \"userAvatar\": \"file:///android_asset/avatars/77.jpg\",\n    \"text\": \"Queminimicus reque minim ornatus praesent suscipit aperiri conceptam habitant imperdiet sapientem molestie periculis quas eu deseruisse nonumes.  Diamconsectetur libero.\",\n    \"images\": [],\n    \"likes\": 615,\n    \"replyCount\": 414,\n    \"messages\": 3,\n    \"comments\": [\n      {\n        \"id\": \"a4144cb4-e39b-40b2-9fba-06d6df067332\",\n        \"userNickname\": \"@omitta\",\n        \"userFullName\": \"Blair Shields\",\n        \"userAvatar\": \"file:///android_asset/avatars/9.jpg\",\n        \"text\": \"🍐 pri🍙\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636975974155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"62db7b25-91c7-46be-809f-200b080518d2\",\n        \"userNickname\": \"@quas\",\n        \"userFullName\": \"Bridget Ashley\",\n        \"userAvatar\": \"file:///android_asset/avatars/155.jpg\",\n        \"text\": \"💳☎️  pellentesque \\n📳😂 🔗📯  nam \\n🚶🍩 adhuc  omittantur \\n🕤 ubique  vocibus \\n🎇 velit scripserit  ponderum  1️⃣😃 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"26cc5050-ede5-4611-9ff4-399ed879550e\",\n        \"userNickname\": \"@nihil\",\n        \"userFullName\": \"Estelle Cain\",\n        \"userAvatar\": \"file:///android_asset/avatars/71.jpg\",\n        \"text\": \"👻🤥 Litoramovet vim rhoncus vocibus quod rhoncus quam ponderum phasellus mutat ubique doctus.  Sententiaenatoque utroque.  Vistempus dictumst odio qualisque minim eius eam posuere voluptatibus primis.  Penatibusnatum pro natum felis mutat affert dicant felis lobortis nonumy splendide augue lorem eloquentiam.  Habitantponderum pulvinar.  Invenireiaculis detracto.🌮🥧 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574155,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636950774155,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"b7dbfdfa-fc10-4818-b608-b308f7d716ef\",\n    \"userNickname\": \"@sapien\",\n    \"userFullName\": \"Erika Pittman\",\n    \"userAvatar\": \"file:///android_asset/avatars/166.jpg\",\n    \"text\": \"an falli\",\n    \"images\": [],\n    \"likes\": 22,\n    \"replyCount\": 141,\n    \"messages\": 0,\n    \"comments\": [],\n    \"createdAt\": 1636990374155,\n    \"reply\": {\n      \"id\": \"f4c71112-04f0-45a7-9024-8a32fff3127a\",\n      \"userNickname\": \"@medioc\",\n      \"userFullName\": \"Salvador Duran\",\n      \"userAvatar\": \"file:///android_asset/avatars/60.jpg\",\n      \"text\": \"option adolescens vidisse tortor at\",\n      \"images\": [],\n      \"likes\": 263,\n      \"replyCount\": 457,\n      \"messages\": 1,\n      \"comments\": [\n        {\n          \"id\": \"b3040d5c-0b22-4a6e-86ab-5814128ce159\",\n          \"userNickname\": \"@id\",\n          \"userFullName\": \"Johnathon Battle\",\n          \"userAvatar\": \"file:///android_asset/avatars/95.jpg\",\n          \"text\": \"💸😄 Fabulasvarius phasellus expetendis mutat.  Torquentpatrioque montes appareat mel commodo dicat sagittis.  Pertinaciaferri nihil altera imperdiet posuere himenaeos periculis nostrum moderatius purus.  Dicamnisi cubilia nullam harum et eu vero tota repudiare quod.⏳🌥🆙 \",\n          \"images\": [],\n          \"likes\": 0,\n          \"replyCount\": 0,\n          \"messages\": 0,\n          \"comments\": [],\n          \"createdAt\": 1637004774155,\n          \"reply\": null,\n          \"poll\": null\n        }\n      ],\n      \"createdAt\": 1636986774155,\n      \"reply\": null,\n      \"poll\": {\n        \"isCompleted\": true,\n        \"totalVotes\": 1426,\n        \"positions\": [\n          {\n            \"text\": \"suavitate tempus enim ante mea\",\n            \"voted\": 472\n          },\n          {\n            \"text\": \"integer nonumy\",\n            \"voted\": 160\n          },\n          {\n            \"text\": \"fugit vivamus aliquid pretium\",\n            \"voted\": 398\n          },\n          {\n            \"text\": \"tritani\",\n            \"voted\": 396\n          }\n        ]\n      }\n    },\n    \"poll\": null\n  },\n  {\n    \"id\": \"aee77efc-e25e-4a2e-a90f-44d452bf6854\",\n    \"userNickname\": \"@reform\",\n    \"userFullName\": \"Cyril Gamble\",\n    \"userAvatar\": \"file:///android_asset/avatars/118.jpg\",\n    \"text\": \"Nasceturlorem dicam explicari conubia possim latine fastidii amet cu noster feugait quaeque brute aenean molestiae tantas iudicabit intellegat.  Orciveritus noster corrumpit nec sale errem senserit lorem duo esse conceptam.  Latineadversarium quod repudiare utinam liber error neglegentur nostrum ultricies quaeque.  Rutrummutat proin iudicabit felis option interesset enim suscipit mnesarchum convallis euismod corrumpit usu pri placerat.  Sollicitudinquot falli aenean moderatius.\",\n    \"images\": [],\n    \"likes\": 505,\n    \"replyCount\": 332,\n    \"messages\": 6,\n    \"comments\": [\n      {\n        \"id\": \"21d04a7d-c8ea-48f4-b92d-2a484a76d771\",\n        \"userNickname\": \"@saluta\",\n        \"userFullName\": \"Jessie Howard\",\n        \"userAvatar\": \"file:///android_asset/avatars/5.jpg\",\n        \"text\": \"Alteraduis wisi posidonium tota curabitur nonumes est audire velit.  Variusdis liber appareat labores dui alia aliquet litora patrioque lacus nonumy mutat verear erroribus eleifend.  Torquentusu purus atomorum oratio varius iudicabit principes netus quidam suspendisse sea dicta eripuit.  Facilisistincidunt diam noster tibique repudiandae homero inani offendit pertinacia utamur vitae fringilla necessitatibus vivendo lobortis.  Quisaperiri dolorem nullam laudem tamquam elit.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c78bae6a-dc56-4fb3-9ee0-9cf47759c780\",\n        \"userNickname\": \"@cetero\",\n        \"userFullName\": \"Helen Maddox\",\n        \"userAvatar\": \"file:///android_asset/avatars/72.jpg\",\n        \"text\": \"Malorumlaudem sea indoctum interesset mazim.  Vulputatefacilis pro tractatos unum expetenda eum varius.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636957974155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c579d5ca-fa97-46d8-9f9d-ee72d145db01\",\n        \"userNickname\": \"@eloque\",\n        \"userFullName\": \"Nola Townsend\",\n        \"userAvatar\": \"file:///android_asset/avatars/154.jpg\",\n        \"text\": \"Prosalutatus definitionem dicat maiorum prodesset omittantur te netus maecenas delectus gubergren.  Nullasociosqu epicuri vim lacinia ea vis eloquentiam cubilia.  Malesuadafugit constituto.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636983174155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f85c8a9c-84a0-43f2-b5e7-72fccfcf8dd2\",\n        \"userNickname\": \"@idque\",\n        \"userFullName\": \"Ignacio Maldonado\",\n        \"userAvatar\": \"file:///android_asset/avatars/23.jpg\",\n        \"text\": \"dignissim urna finibus metus\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636968774155,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"c71ce725-4f4c-46df-b928-4f3f09d332dc\",\n        \"userNickname\": \"@possit\",\n        \"userFullName\": \"Billie Lynn\",\n        \"userAvatar\": \"file:///android_asset/avatars/1.jpg\",\n        \"text\": \"signiferumque lorem  possim  👩‍👩‍👧‍👧 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b011c7d6-b7ec-459d-b54f-8d249f356ce3\",\n        \"userNickname\": \"@veniam\",\n        \"userFullName\": \"Patricia Blackburn\",\n        \"userAvatar\": \"file:///android_asset/avatars/148.jpg\",\n        \"text\": \"🍸\\nAdipisciad veniam definitionem veniam veniam orci atomorum consul sententiae montes condimentum.  Tibiqueiusto cras sapientem phasellus sanctus.  Facilisiregione velit.  Magnanovum vituperata posuere reformidans litora hinc.  Alteruminteresset vix consequat pericula malesuada wisi ne nullam nisl pri vestibulum conubia.  Doloremmaiorum mi ei quod feugiat tritani non lorem eleifend.🍗👨‍👩‍👧‍👦 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574156,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636929174155,\n    \"reply\": null,\n    \"poll\": null\n  },\n  {\n    \"id\": \"59ded396-4178-41b7-9194-d3b2080d5f22\",\n    \"userNickname\": \"@commod\",\n    \"userFullName\": \"Cleveland Curry\",\n    \"userAvatar\": \"file:///android_asset/avatars/150.jpg\",\n    \"text\": \"Eahabeo leo ceteros tale unum eros montes turpis definitiones habemus quot.  Salutatusiriure graeci quaerendum consectetuer ocurreret voluptatibus option.  Accusataipsum sententiae dico.\",\n    \"images\": [],\n    \"likes\": 377,\n    \"replyCount\": 437,\n    \"messages\": 9,\n    \"comments\": [\n      {\n        \"id\": \"4abc0e64-8741-4c6e-bdb3-104b417e6bcb\",\n        \"userNickname\": \"@luptat\",\n        \"userFullName\": \"David Wade\",\n        \"userAvatar\": \"file:///android_asset/avatars/153.jpg\",\n        \"text\": \"🏦\\nconceptam laoreet persecuti lacus🐨👨‍👨‍👧🥧\\n\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636979574156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"e7f31449-ae76-4c39-abd0-12ad8ffe08b1\",\n        \"userNickname\": \"@tortor\",\n        \"userFullName\": \"Dale Estrada\",\n        \"userAvatar\": \"file:///android_asset/avatars/109.jpg\",\n        \"text\": \"☕️4️⃣📣 alienum finibus vidisse scripserit doctus \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636986774156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"09e6d523-9923-4e5c-b040-f4a5a30a78cd\",\n        \"userNickname\": \"@reprim\",\n        \"userFullName\": \"Darius Gibbs\",\n        \"userAvatar\": \"file:///android_asset/avatars/0.jpg\",\n        \"text\": \"🐎 ridens tractatos graeco♊️📡🥫 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636993974156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b6a01426-5178-409c-926f-1811f836ee56\",\n        \"userNickname\": \"@vehicu\",\n        \"userFullName\": \"Emile Palmer\",\n        \"userAvatar\": \"file:///android_asset/avatars/48.jpg\",\n        \"text\": \"Putentcongue doming reformidans numquam reprimique ea dicam voluptatum fabellas dignissim oratio impetus pro salutatus tractatos mutat nominavi scelerisque ridiculus.  Turpiserror vel sem facilis malorum persecuti ac theophrastus adipisci.  Aconstituam est aliquam vel varius cubilia verear integer nibh eum conubia rhoncus voluptatibus principes blandit vocent dicit est.  Definitionemvel delenit nisl meliore congue dicta theophrastus.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636961574156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"a797f072-954d-4299-9580-be2869258c3d\",\n        \"userNickname\": \"@augue\",\n        \"userFullName\": \"Janna Decker\",\n        \"userAvatar\": \"file:///android_asset/avatars/96.jpg\",\n        \"text\": \"📱🎍  harum \\n🗺 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636997574156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"f08e7b8d-3cc9-471d-af3b-cd235a4e076c\",\n        \"userNickname\": \"@persec\",\n        \"userFullName\": \"Sheila Boyle\",\n        \"userAvatar\": \"file:///android_asset/avatars/75.jpg\",\n        \"text\": \"ridiculus inimicus  reque  👨‍🍳 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636947174156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"b2218a4c-c297-486d-9ebd-87206b0df1d3\",\n        \"userNickname\": \"@posse\",\n        \"userFullName\": \"Virgie Vasquez\",\n        \"userAvatar\": \"file:///android_asset/avatars/193.jpg\",\n        \"text\": \"Agamfacilis fuisset aliquid pharetra suspendisse gloriatur nullam aeque tation option persequeris quem vero curae ipsum mutat.  Ornarenatoque maecenas urna fabulas eros dis graecis oporteat vitae conceptam laudem iaculis pertinacia maiorum tincidunt deseruisse.  Liberridens graeco petentium.  Novumpostea invenire agam velit dicit.  Volumusdictas vestibulum mei.\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636990374156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"77cd8854-2e81-434d-86fb-a8df8ff840c7\",\n        \"userNickname\": \"@oporte\",\n        \"userFullName\": \"Alfred Lewis\",\n        \"userAvatar\": \"file:///android_asset/avatars/60.jpg\",\n        \"text\": \"🍒⚡️  homero  🐔 \",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1637011974156,\n        \"reply\": null,\n        \"poll\": null\n      },\n      {\n        \"id\": \"765e9551-d907-41d3-b726-060be593f762\",\n        \"userNickname\": \"@defini\",\n        \"userFullName\": \"Donnie Olsen\",\n        \"userAvatar\": \"file:///android_asset/avatars/42.jpg\",\n        \"text\": \"bibendum felis netus curae\",\n        \"images\": [],\n        \"likes\": 0,\n        \"replyCount\": 0,\n        \"messages\": 0,\n        \"comments\": [],\n        \"createdAt\": 1636965174156,\n        \"reply\": null,\n        \"poll\": null\n      }\n    ],\n    \"createdAt\": 1636943574156,\n    \"reply\": null,\n    \"poll\": null\n  }\n]"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/DemoApp.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport android.annotation.SuppressLint\nimport android.app.Application\nimport android.content.Context\nimport dev.serhiiyaremych.imla.data.UserPost\nimport kotlinx.serialization.json.Json\n\nclass DemoApp : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        ctx = this\n    }\n\n    companion object {\n        @SuppressLint(\"StaticFieldLeak\")\n        private lateinit var ctx: Context\n\n        val posts: List<UserPost> by lazy {\n            val tweetsJson = ctx.assets.open(\"loremipsum.json\").bufferedReader().readText()\n            Json.decodeFromString<List<UserPost>>(tweetsJson)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/MainActivity.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport android.graphics.drawable.ColorDrawable\nimport android.os.Bundle\nimport android.view.Choreographer\nimport android.view.View\nimport androidx.activity.ComponentActivity\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.compose.ReportDrawnWhen\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.requiredSize\nimport androidx.compose.foundation.layout.safeGesturesPadding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBarsPadding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Home\nimport androidx.compose.material.icons.filled.Menu\nimport androidx.compose.material.icons.filled.Notifications\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.NavigationBar\nimport androidx.compose.material3.NavigationBarItem\nimport androidx.compose.material3.Slider\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.Recomposer\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberUpdatedState\nimport androidx.compose.runtime.snapshotFlow\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.compositeOver\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.layout.onSizeChanged\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.compose.ui.zIndex\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.data.ApiClient\nimport dev.serhiiyaremych.imla.modifier.blurSource\nimport dev.serhiiyaremych.imla.ui.BackdropBlur\nimport dev.serhiiyaremych.imla.ui.theme.ImlaTheme\nimport dev.serhiiyaremych.imla.ui.userpost.SimpleImageViewer\nimport dev.serhiiyaremych.imla.ui.userpost.UserPostView\nimport dev.serhiiyaremych.imla.uirenderer.Style\nimport dev.serhiiyaremych.imla.uirenderer.UiLayerRenderer\nimport dev.serhiiyaremych.imla.uirenderer.rememberUiLayerRenderer\nimport kotlinx.collections.immutable.persistentListOf\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport java.util.Locale\n\nclass MainActivity : ComponentActivity() {\n    @OptIn(ExperimentalMaterial3Api::class)\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(\n                android.graphics.Color.TRANSPARENT,\n            ),\n            navigationBarStyle = SystemBarStyle.light(\n                android.graphics.Color.TRANSPARENT, android.graphics.Color.TRANSPARENT\n            )\n        )\n        launchIdlenessTracking()\n        setContent {\n            ImlaTheme {\n                val uiRenderer = rememberUiLayerRenderer(downSampleFactor = 2)\n                val viewingImage = remember {\n                    mutableStateOf(\"\")\n                }\n                Box(modifier = Modifier.fillMaxSize()) {\n                    // Full height content\n                    Surface(\n                        Modifier\n                            .fillMaxSize()\n                            .blurSource(uiRenderer),\n                    ) {\n                        Content(modifier = Modifier\n                            .fillMaxSize(),\n                            contentPadding = PaddingValues(top = TopAppBarDefaults.MediumAppBarExpandedHeight),\n                            onImageClick = { viewingImage.value = it },\n                            onScroll = { /*uiRenderer.onUiLayerUpdated()*/ })\n                    }\n\n                    val showBottomSheet = remember { mutableStateOf(false) }\n                    Column(modifier = Modifier.fillMaxSize()) {\n                        // Layer 0 above full height content\n//                        BlurryTopAppBar(uiRenderer)\n//                         Layer 1 full height content\n                        Spacer(Modifier.weight(1f))\n//                        BackdropBlur(Modifier.requiredSize(120.dp), uiRenderer)\n                        Spacer(Modifier.weight(1f))\n                        Row {\n                            BackdropBlur(Modifier.size(120.dp), uiRenderer)\n                            BackdropBlur(Modifier.size(120.dp), uiRenderer)\n                            BackdropBlur(Modifier.size(120.dp), uiRenderer)\n                        }\n                        Spacer(Modifier.weight(1f))\n//                        BlurryBottomNavBar(uiRenderer) {\n//                            showBottomSheet.value = true\n//                        }\n                    }\n                    val cornerShape = RoundedCornerShape(12.dp)\n//                    BackdropBlur(\n//                        modifier = Modifier\n//                            .fillMaxSize()\n//                            .shadow(2.dp, cornerShape)\n//                            .border(\n//                                1.dp, Color.Cyan.copy(alpha = 0.4f).compositeOver(Color.White),\n//                                cornerShape\n//                            )\n//                            .align(Alignment.Center),\n//                        rendererState = uiRenderer,\n//                        blurMask = Brush.radialGradient(\n//                            colors = listOf(\n//                                Color.White.copy(alpha = 0.0f),\n//                                Color.White.copy(alpha = 0.5f),\n////                                Color.White.copy(alpha = 0.9f),\n////                                Color.White.copy(alpha = 1.0f),\n//                                Color.White.copy(alpha = 1.0f),\n//                            ),\n//                        ),\n//                        clipShape = cornerShape\n//                    )\n                    AnimatedVisibility(\n                        modifier = Modifier\n                            .matchParentSize(),\n                        visible = viewingImage.value.isNotEmpty(),\n                        enter = fadeIn(),\n                        exit = fadeOut()\n                    ) {\n                        BackdropBlur(\n                            modifier = Modifier.matchParentSize(),\n                            rendererState = uiRenderer,\n                        ) {\n                            SimpleImageViewer(\n                                modifier = Modifier\n                                    .fillMaxSize(),\n                                imageUrl = viewingImage.value,\n                                onDismiss = { viewingImage.value = \"\" })\n                        }\n                        DisposableEffect(key1 = Unit) {\n                            onDispose { uiRenderer.onUiLayerUpdated() }\n                        }\n                    }\n\n                    if (showBottomSheet.value) {\n                        val sheetState = rememberModalBottomSheetState()\n\n                        val blurOpacity = remember {\n                            mutableFloatStateOf(1f)\n                        }\n                        val sheetHeight = remember {\n                            mutableIntStateOf(0)\n                        }\n                        val noiseState = remember {\n                            mutableFloatStateOf(0.1f)\n                        }\n                        val offsetState = remember {\n                            mutableFloatStateOf(0.8f)\n                        }\n                        val passesState = remember {\n                            mutableFloatStateOf(1f)\n                        }\n\n                        val passes = (passesState.floatValue * (4 - 1) + 1).toInt().coerceIn(1, 4)\n                        BackdropBlur(\n                            modifier = Modifier\n                                .fillMaxSize()\n                                .zIndex(2f),\n                            style = Style.default.copy(\n                                noiseAlpha = noiseState.floatValue,\n                                offset = offsetState.floatValue,\n                                passes = passes,\n                                blurOpacity = blurOpacity.floatValue\n                            ),\n                            rendererState = uiRenderer\n                        )\n\n                        ModalBottomSheet(\n                            modifier = Modifier\n                                .statusBarsPadding()\n                                .onSizeChanged { sheetHeight.intValue = it.height },\n                            sheetState = sheetState,\n                            scrimColor = Color.Transparent,\n                            onDismissRequest = { showBottomSheet.value = false },\n                            containerColor = Color.White.copy(alpha = (1.0f - blurOpacity.floatValue) + 0.3f)\n                        ) {\n                            Column(\n                                Modifier\n                                    .fillMaxSize()\n                                    .safeGesturesPadding(),\n                                horizontalAlignment = Alignment.CenterHorizontally\n                            ) {\n                                Text(\"Blur Settings\", fontWeight = FontWeight.Bold)\n                                Spacer(Modifier.height(16.dp))\n                                Text(\n                                    modifier = Modifier.fillMaxWidth(),\n                                    text = \"Blur opacity ${\n                                        String.format(\n                                            locale = Locale.ENGLISH,\n                                            \"%.1f\",\n                                            blurOpacity.floatValue\n                                        )\n                                    }\"\n                                )\n                                Row(verticalAlignment = Alignment.CenterVertically) {\n                                    val noiseStr = String.format(\n                                        locale = Locale.ENGLISH,\n                                        format = \"%.1f\", noiseState.floatValue\n                                    )\n                                    Text(\"Noise($noiseStr)\")\n                                    Spacer(Modifier.width(16.dp))\n                                    Slider(\n                                        value = noiseState.floatValue,\n                                        onValueChange = { noiseState.floatValue = it },\n                                        valueRange = 0.0f..1.0f\n                                    )\n                                }\n                                Row(verticalAlignment = Alignment.CenterVertically) {\n                                    val offsetStr = String.format(\n                                        locale = Locale.ENGLISH,\n                                        format = \"%.1f\", offsetState.floatValue\n                                    )\n                                    Text(\"Offset($offsetStr)\")\n                                    Spacer(Modifier.width(16.dp))\n                                    Slider(\n                                        value = offsetState.floatValue,\n                                        onValueChange = { offsetState.floatValue = it },\n                                        valueRange = 0.5f..2.2f\n                                    )\n                                }\n                                Row(verticalAlignment = Alignment.CenterVertically) {\n                                    Text(\"Passes($passes)\")\n                                    Spacer(Modifier.width(16.dp))\n                                    Slider(\n                                        value = passesState.floatValue,\n                                        onValueChange = { passesState.floatValue = it },\n                                        valueRange = 0f..1f,\n                                        steps = 2\n                                    )\n                                }\n\n                            }\n\n                        }\n                        LaunchedEffect(sheetState) {\n                            snapshotFlow { sheetState.requireOffset() }\n                                .distinctUntilChanged()\n                                .collect {\n                                    val expandFraction = 1.0f - (it / sheetHeight.intValue)\n                                    blurOpacity.floatValue = expandFraction.coerceIn(0f, 1f)\n                                }\n                        }\n\n                    }\n\n                }\n                val fullyDrawn = remember { mutableStateOf(false) }\n                LaunchedEffect(uiRenderer.isInitialized, fullyDrawn) {\n                    snapshotFlow { uiRenderer.isInitialized.value }\n                        .collect {\n                            if (it) {\n                                delay(1000)\n                                fullyDrawn.value = true\n                            }\n                        }\n                }\n                ReportDrawnWhen { fullyDrawn.value }\n            }\n        }\n    }\n\n    @Composable\n    private fun Wrapped() {\n        AndroidView(\n            factory = { context ->\n                View(context).apply {\n                    isFocusable = false\n                    isFocusableInTouchMode = false\n                    background =\n                        ColorDrawable(Color.Blue.copy(alpha = 0.5f).toArgb())\n                }\n            },\n            modifier = Modifier\n                .fillMaxWidth()\n                .fillMaxHeight(0.5f)\n                .border(1.dp, Color.Magenta)\n        )\n    }\n\n    @Composable\n    private fun BlurryBottomNavBar(\n        uiRenderer: UiLayerRenderer,\n        onShowSettings: () -> Unit\n    ) = Box(Modifier.height(86.dp)) {\n\n        BackdropBlur(\n            modifier = Modifier\n                .fillMaxWidth()\n                .border(\n                    Dp.Hairline,\n                    Color.DarkGray,\n                    RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)\n                ),\n            rendererState = uiRenderer,\n            style = Style.default.copy(passes = 3, noiseAlpha = 0.1f, blurOpacity = 0.9f)\n//            blurMask = Brush.verticalGradient(\n//                colors = listOf(\n//                    Color.White.copy(alpha = 0.0f),\n//                    Color.White.copy(alpha = 0.6f),\n//                    Color.White.copy(alpha = 0.9f),\n//                    Color.White.copy(alpha = 1.0f),\n//                    Color.White.copy(alpha = 1.0f),\n//                ),\n//            ),\n        ) {\n            NavigationBar(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(bottom = 24.dp),\n                windowInsets = WindowInsets(bottom = 0.dp),\n                containerColor = Color.Transparent\n            ) {\n                NavigationBarItem(selected = false, onClick = { /*TODO*/ }, icon = {\n                    Icon(\n                        imageVector = Icons.Filled.Home, contentDescription = null\n                    )\n                })\n                NavigationBarItem(selected = false, onClick = { /*TODO*/ }, icon = {\n                    Icon(\n                        imageVector = Icons.Filled.Search, contentDescription = null\n                    )\n                })\n                NavigationBarItem(selected = false, onClick = { /*TODO*/ }, icon = {\n                    Icon(\n                        imageVector = Icons.Filled.Notifications, contentDescription = null\n                    )\n                })\n                NavigationBarItem(selected = false, onClick = onShowSettings, icon = {\n                    Icon(\n                        imageVector = Icons.Filled.Settings, contentDescription = null\n                    )\n                })\n\n            }\n        }\n    }\n\n    @Composable\n    @OptIn(ExperimentalMaterial3Api::class)\n    private fun BlurryTopAppBar(uiRenderer: UiLayerRenderer) {\n        BackdropBlur(\n            modifier = Modifier.height(120.dp),\n            rendererState = uiRenderer,\n            style = Style.default.copy(passes = 3, noiseAlpha = 0.1f),\n//            blurMask = Brush.verticalGradient(\n//                colors = listOf(\n//                    Color.White.copy(alpha = 1.0f),\n//                    Color.White.copy(alpha = 1.0f),\n//                    Color.White.copy(alpha = 0.9f),\n//                    Color.White.copy(alpha = 0.5f),\n//                    Color.White.copy(alpha = 0.0f),\n//                ),\n//            ),\n        ) {\n            TopAppBar(\n                modifier = Modifier.statusBarsPadding(),\n                title = { Text(\"Blur Demo\") },\n                windowInsets = WindowInsets(top = 0.dp),\n                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),\n                navigationIcon = {\n                    IconButton(onClick = { /* \"Open nav drawer\" */ }) {\n                        Icon(Icons.Filled.Menu, contentDescription = null)\n                    }\n                }\n            )\n        }\n    }\n\n    @Composable\n    private fun Content(\n        modifier: Modifier,\n        contentPadding: PaddingValues,\n        onImageClick: (String) -> Unit,\n        onScroll: (Int) -> Unit\n    ) = trace(\"MainActivity#Content\") {\n        val scrollState = rememberLazyListState()\n        val currentOnScroll = rememberUpdatedState(onScroll).value\n        LaunchedEffect(key1 = scrollState, key2 = onScroll) {\n            snapshotFlow { scrollState.firstVisibleItemScrollOffset }.distinctUntilChanged()\n                .collect {\n                    currentOnScroll(it)\n                }\n        }\n        val posts =\n            ApiClient.getPosts().collectAsStateWithLifecycle(initialValue = persistentListOf())\n        LazyColumn(modifier = modifier, state = scrollState, contentPadding = contentPadding) {\n            items(posts.value, key = { it.id }) { item ->\n                UserPostView(\n                    modifier = Modifier.fillMaxWidth(), post = item, onImageClick = onImageClick\n                )\n            }\n        }\n\n    }\n\n    private fun ComponentActivity.launchIdlenessTracking() {\n        val contentView: View = findViewById(android.R.id.content)\n        val callback: Choreographer.FrameCallback = object : Choreographer.FrameCallback {\n            override fun doFrame(frameTimeNanos: Long) {\n                if (Recomposer.runningRecomposers.value.any { it.hasPendingWork }) {\n                    contentView.contentDescription = \"COMPOSE-BUSY\"\n                } else {\n                    contentView.contentDescription = \"COMPOSE-IDLE\"\n                }\n                Choreographer.getInstance().postFrameCallback(this)\n            }\n        }\n        Choreographer.getInstance().postFrameCallback(callback)\n    }\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/data/ApiClient.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.data\n\nimport dev.serhiiyaremych.imla.DemoApp\nimport kotlinx.collections.immutable.ImmutableList\nimport kotlinx.collections.immutable.toPersistentList\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.withContext\n\nobject ApiClient {\n    fun getPosts(): Flow<ImmutableList<UserPost>> {\n        return flow {\n            val posts = withContext(Dispatchers.IO) {\n                DemoApp.posts.toPersistentList()\n            }\n            emit(posts)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/data/Poll.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.data\n\nimport androidx.compose.runtime.Immutable\nimport kotlinx.serialization.Serializable\n\n@Serializable\n@Immutable\ndata class Poll(\n    val isCompleted: Boolean,\n    val totalVotes: Int,\n    val positions: List<PollPosition>\n)\n\n@Serializable\n@Immutable\ndata class PollPosition(\n    val text: String,\n    val voted: Int\n)"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/data/UserPost.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.data\n\nimport androidx.compose.runtime.Immutable\nimport kotlinx.serialization.Serializable\n\n\n@Serializable\n@Immutable\ndata class UserPost(\n    val id: String,\n    val userNickname: String,\n    val userFullName: String,\n    val userAvatar: String,\n    val text: String,\n    val images: List<String>,\n    val likes: Int,\n    val replyCount: Int,\n    val messages: Int,\n    val comments: List<UserPost>,\n    val createdAt: Long,\n    val reply: UserPost?,\n    val poll: Poll?\n)\n\n"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/theme/Color.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.theme\n\nimport androidx.compose.ui.graphics.Color\n\nval Purple80 = Color(0xFFD0BCFF)\nval PurpleGrey80 = Color(0xFFCCC2DC)\nval Pink80 = Color(0xFFEFB8C8)\n\nval Purple40 = Color(0xFF6650a4)\nval PurpleGrey40 = Color(0xFF625b71)\nval Pink40 = Color(0xFF7D5260)"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/theme/Theme.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.theme\n\nimport android.os.Build\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\n\nprivate val DarkColorScheme = darkColorScheme(\n    primary = Color.Cyan,\n    secondary = PurpleGrey80,\n    tertiary = Pink80,\n    surface = Color(0xFF101010L),\n    onSurface = Color.LightGray,\n    primaryContainer = Color.Magenta\n)\n\nprivate val LightColorScheme = lightColorScheme(\n    primary = Color.Blue,\n    secondary = Purple40,\n    tertiary = Pink40,\n    surface = Color.White,\n    onSurface = Color.Black,\n\n    /* Other default colors to override\n    background = Color(0xFFFFFBFE),\n    surface = Color(0xFFFFFBFE),\n    onPrimary = Color.White,\n    onSecondary = Color.White,\n    onTertiary = Color.White,\n    onBackground = Color(0xFF1C1B1F),\n    onSurface = Color(0xFF1C1B1F),\n    */\n)\n\n@Composable\nfun ImlaTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    // Dynamic color is available on Android 12+\n    dynamicColor: Boolean = false,\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n            val context = LocalContext.current\n            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n        }\n\n        darkTheme -> DarkColorScheme\n        else -> LightColorScheme\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/theme/Type.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp,\n        letterSpacing = 0.5.sp\n    )\n    /* Other default text styles to override\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp,\n        letterSpacing = 0.sp\n    ),\n    labelSmall = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 11.sp,\n        lineHeight = 16.sp,\n        letterSpacing = 0.5.sp\n    )\n    */\n)"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/userpost/PollView.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.userpost\n\nimport androidx.annotation.FloatRange\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.key\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.drawBehind\nimport androidx.compose.ui.geometry.CornerRadius\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.tooling.preview.Devices\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport dev.serhiiyaremych.imla.data.Poll\nimport dev.serhiiyaremych.imla.data.PollPosition\nimport dev.serhiiyaremych.imla.ui.theme.ImlaTheme\nimport kotlin.math.roundToInt\n\n@Composable\nfun PollView(poll: Poll, compactMode: Boolean, modifier: Modifier = Modifier) {\n    val borderColor = MaterialTheme.colorScheme.primary\n    val buttonBorder = remember(borderColor) {\n        BorderStroke(1.dp, borderColor)\n    }\n    Column(\n        modifier = modifier,\n        verticalArrangement = Arrangement.spacedBy(4.dp)\n    ) {\n        for (pollPosition in poll.positions) {\n            key(pollPosition) {\n                if (poll.isCompleted) {\n                    val progress = pollPosition.voted / poll.totalVotes.toFloat()\n                    PollProgressLine(progress = progress, pollPosition.text, compactMode)\n                } else {\n                    PollButton(buttonBorder, pollPosition.text, compactMode)\n                }\n            }\n        }\n\n        Text(\n            text = \"${poll.totalVotes} votes\",\n            color = MaterialTheme.colorScheme.onSurface,\n            fontSize = 12.sp\n        )\n    }\n}\n\n@Composable\nprivate fun PollProgressLine(\n    @FloatRange(from = 0.0, to = 1.0) progress: Float,\n    label: String,\n    compactMode: Boolean\n) {\n    var pollProgress by rememberSaveable {\n        mutableFloatStateOf(0.0f)\n    }\n    val animatedProgress by animateFloatAsState(\n        targetValue = pollProgress,\n        animationSpec = tween(1500),\n        label = \"animatedProgress\"\n    )\n\n\n    Box(\n        modifier = Modifier\n            .fillMaxWidth()\n            .heightIn(min = ButtonDefaults.MinHeight + 4.dp)\n            .drawBehind {\n                val cornerSize = 6.dp.toPx()\n                val width = (size.width * animatedProgress).coerceAtLeast(cornerSize)\n                drawRoundRect(\n                    color = Color.LightGray,\n                    size = Size(\n                        width = width,\n                        height = size.height\n                    ),\n                    cornerRadius = CornerRadius(cornerSize, cornerSize)\n                )\n            },\n        contentAlignment = Alignment.CenterStart\n    ) {\n        Row(modifier = Modifier.fillMaxWidth()) {\n            val textSize = if (compactMode) 12.sp else 14.sp\n            val text = remember(progress.roundToInt()) {\n                \"${(progress * 100).roundToInt().coerceIn(0, 100)}%\"\n            }\n            Text(\n                modifier = Modifier\n                    .weight(1.0f)\n                    .padding(horizontal = 8.dp),\n                text = label,\n                color = MaterialTheme.colorScheme.onBackground,\n                fontSize = textSize\n            )\n            Text(\n                text = text,\n                color = MaterialTheme.colorScheme.onBackground,\n                fontSize = textSize\n            )\n        }\n    }\n\n    SideEffect {\n        pollProgress = progress\n    }\n}\n\n@Composable\nprivate fun PollButton(\n    buttonBorder: BorderStroke,\n    title: String,\n    compactMode: Boolean\n) {\n    OutlinedButton(\n        modifier = Modifier\n            .fillMaxWidth(),\n        shape = MaterialTheme.shapes.large,\n        border = buttonBorder,\n        colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),\n        onClick = { /*TODO*/ }\n    ) {\n        val textSize = if (compactMode) 12.sp else 14.sp\n        Text(\n            text = title,\n            color = MaterialTheme.colorScheme.primary,\n            fontSize = textSize\n        )\n    }\n}\n\n@Preview(name = \"Poll not completed\", device = Devices.PIXEL)\n@Composable\nprivate fun PollNotCompletedPreview() {\n    ImlaTheme {\n        val poll = Poll(\n            isCompleted = false,\n            totalVotes = 0,\n            positions = listOf(\n                PollPosition(\"Option 1\", 0),\n                PollPosition(\"Option 2\", 0),\n                PollPosition(\"Option 3\", 0),\n            )\n        )\n        PollView(modifier = Modifier.fillMaxWidth(), poll = poll, compactMode = false)\n    }\n}\n\n@Preview(name = \"Poll completed\", device = Devices.PIXEL)\n@Composable\nprivate fun PollCompletedPreview() {\n    ImlaTheme {\n        val poll = Poll(\n            isCompleted = true,\n            totalVotes = 100,\n            positions = listOf(\n                PollPosition(\"Option 0\", 12),\n                PollPosition(\"Option 1\", 18),\n                PollPosition(\"Option 2\", 60),\n                PollPosition(\"Option 3\\ndddd\\nddddf\", 20),\n            )\n        )\n        PollView(modifier = Modifier.fillMaxWidth(), poll = poll, compactMode = false)\n    }\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/userpost/PostImageView.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.userpost\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.horizontalScroll\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.key\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.graphics.Shape\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.layout.Layout\nimport androidx.compose.ui.layout.onPlaced\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalInspectionMode\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.dp\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport dev.serhiiyaremych.imla.ui.theme.ImlaTheme\n\nconst val LOW_IMAGE_QUALITY = 0.2f\nconst val HIGH_IMAGE_QUALITY = 0.5f\n\nprivate val imageCorners = RoundedCornerShape(4.dp)\nprivate val startCorners = RoundedCornerShape(topStart = 4.dp, bottomStart = 4.dp)\nprivate val endCorners = RoundedCornerShape(topEnd = 4.dp, bottomEnd = 4.dp)\nprivate val topStartCorner = RoundedCornerShape(topStart = 4.dp)\nprivate val topEndCorner = RoundedCornerShape(topEnd = 4.dp)\nprivate val bottomStartCorner = RoundedCornerShape(bottomStart = 4.dp)\nprivate val bottomEndCorner = RoundedCornerShape(bottomEnd = 4.dp)\n\n/*\nMono image layout:\n--------\n|      |\n|  A   |\n|      |\n--------\n*/\n@Composable\nfun MonoImage(\n    imageUrl: String,\n    imageHeight: Dp,\n    compactMode: Boolean,\n    onImageClick: (String) -> Unit\n) {\n    SinglePostImage(\n        modifier = Modifier\n            .height(imageHeight)\n            .fillMaxWidth()\n            .clickable { onImageClick(imageUrl) },\n        clipShape = imageCorners,\n        imgUrl = imageUrl,\n        compactMode = compactMode\n    )\n}\n\n/*\nDuo image layout:\n--------\n|   |   |\n| A | B |\n|   |   |\n--------\n*/\n@Composable\nfun DuoImage(\n    imgA: String,\n    imgB: String,\n    imageHeight: Dp,\n    compactMode: Boolean,\n    onImageClick: (String) -> Unit\n) {\n    Row(modifier = Modifier.fillMaxWidth()) {\n        // Image A\n        SinglePostImage(\n            modifier = Modifier\n                .height(imageHeight)\n                .weight(1.0f)\n                .clickable { onImageClick(imgA) },\n            clipShape = startCorners,\n            imgUrl = imgA,\n            compactMode = compactMode\n        )\n        Spacer(modifier = Modifier.width(4.dp))\n        // Image B\n        SinglePostImage(\n            modifier = Modifier\n                .height(imageHeight)\n                .weight(1.0f)\n                .clickable { onImageClick(imgB) },\n            clipShape = endCorners,\n            imgUrl = imgB,\n            compactMode = compactMode\n        )\n    }\n}\n\n/*\nTriple image layout:\n--------\n|   | B |\n| A |-- |\n|   | C |\n--------\n*/\n@Composable\nfun TripleImage(\n    imageHeight: Dp,\n    imgA: String,\n    imgB: String,\n    imgC: String,\n    compactMode: Boolean,\n    onImageClick: (String) -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .height(IntrinsicSize.Min)\n    ) {\n        // Image A\n        SinglePostImage(\n            modifier = Modifier\n                .height(imageHeight)\n                .weight(1.0f)\n                .clickable { onImageClick(imgA) },\n            clipShape = startCorners,\n            imgUrl = imgA,\n            compactMode = compactMode\n        )\n        Spacer(modifier = Modifier.width(4.dp))\n        Column(\n            modifier = Modifier\n                .fillMaxHeight()\n                .weight(1.0f)\n        ) {\n            var imgBCSize by remember { mutableStateOf(IntSize.Zero) }\n            // Image B\n            SinglePostImage(\n                modifier = Modifier\n                    .height((imageHeight / 2) - 2.dp)\n                    .fillMaxWidth()\n                    .onPlaced { imgBCSize = it.size }\n                    .clickable { onImageClick(imgB) },\n                clipShape = topEndCorner,\n                imgUrl = imgB,\n                compactMode = compactMode\n            )\n            Spacer(modifier = Modifier.height(4.dp))\n            // Image C\n            SinglePostImage(\n                modifier = Modifier\n                    .height((imageHeight / 2) - 2.dp)\n                    .fillMaxWidth()\n                    .clickable { onImageClick(imgC) },\n                clipShape = bottomEndCorner,\n                imgUrl = imgC,\n                compactMode = compactMode\n            )\n        }\n\n    }\n}\n\n/*\nQuad image layout:\n--------\n| A | B |\n| --|-- |\n| C | D |\n--------\n*/\n@Composable\nfun QuadImage(\n    imageHeight: Dp,\n    imgA: String,\n    imgB: String,\n    imgC: String,\n    imgD: String,\n    compactMode: Boolean,\n    onImageClick: (String) -> Unit\n) {\n    // static 2x2 grid\n    val cols = 2\n    Layout(\n        modifier = Modifier\n            .height(imageHeight)\n            .fillMaxWidth(),\n        content = {\n            SinglePostImage(\n                modifier = Modifier\n                    .padding(end = 2.dp, bottom = 2.dp)\n                    .clickable { onImageClick(imgA) },\n                clipShape = topStartCorner,\n                imgUrl = imgA,\n                compactMode = compactMode\n            )\n            SinglePostImage(\n                modifier = Modifier\n                    .padding(start = 2.dp, bottom = 2.dp)\n                    .clickable { onImageClick(imgB) },\n                clipShape = topEndCorner,\n                imgUrl = imgB,\n                compactMode = compactMode\n            )\n            SinglePostImage(\n                modifier = Modifier\n                    .padding(top = 2.dp, end = 2.dp)\n                    .clickable { onImageClick(imgC) },\n                clipShape = bottomStartCorner,\n                imgUrl = imgC,\n                compactMode = compactMode\n            )\n            SinglePostImage(\n                modifier = Modifier\n                    .padding(top = 2.dp, start = 2.dp)\n                    .clickable { onImageClick(imgD) },\n                clipShape = bottomEndCorner,\n                imgUrl = imgD,\n                compactMode = compactMode\n            )\n        }\n    ) { measurables, constraints ->\n        val rows = (measurables.size / cols) + measurables.size % cols\n        val childWidth = constraints.maxWidth / cols\n        val childHeight = constraints.maxHeight / rows\n\n        val children = measurables.map { child ->\n            child.measure(\n                constraints.copy(\n                    minWidth = childWidth,\n                    minHeight = childHeight,\n                    maxWidth = childWidth,\n                    maxHeight = childHeight\n                )\n            )\n        }\n\n        layout(constraints.maxWidth, constraints.maxHeight) {\n            children.forEachIndexed { idx, child ->\n                val row = idx / cols\n                val col = idx - (row * cols)\n\n                val offset = IntOffset(\n                    x = col * child.measuredWidth,\n                    y = row * child.measuredHeight\n                )\n                child.placeRelative(offset)\n            }\n        }\n    }\n}\n\n\n@Composable\nfun SinglePostImage(\n    modifier: Modifier,\n    clipShape: Shape,\n    imgUrl: String,\n    compactMode: Boolean\n) {\n    val imageLayoutSize = remember {\n        mutableStateOf(IntSize.Zero)\n    }\n    val context = LocalContext.current\n    val imageSize = imageLayoutSize.value\n    val request = remember(imageSize, imgUrl, context, compactMode) {\n        ImageRequest.Builder(context)\n            .data(imgUrl)\n            .setImageSize(\n                width = imageSize.width,\n                height = imageSize.height,\n                compactMode = compactMode\n            )\n            .crossfade(false)\n            .build()\n    }\n    AsyncImage(\n        modifier = modifier\n            .clip(clipShape)\n            .onPlaced { imageLayoutSize.value = it.size }\n            .let { if (LocalInspectionMode.current) it.background(Color.Magenta) else it },\n        model = request,\n        contentScale = ContentScale.Crop,\n        contentDescription = null\n    )\n}\n\n/*\nImage row layout:\n-------------------\n| A | B | C | ... |\n-------------------\n*/\n@Composable\nfun ImageRow(\n    imageHeight: Dp,\n    images: List<String>,\n    compactMode: Boolean,\n    onImageClick: (String) -> Unit\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .horizontalScroll(state = rememberScrollState())\n    ) {\n        images.forEachIndexed { idx, image ->\n            key(image) {\n                val clipper = when (idx) {\n                    0 -> startCorners\n                    images.lastIndex -> endCorners\n                    else -> RectangleShape\n                }\n                SinglePostImage(\n                    modifier = Modifier\n                        .size(imageHeight)\n                        .clickable { onImageClick(image) },\n                    clipShape = clipper,\n                    imgUrl = image,\n                    compactMode = compactMode\n                )\n                if (idx < images.lastIndex) {\n                    Spacer(modifier = Modifier.width(4.dp))\n                }\n            }\n        }\n    }\n}\n\n\nprivate fun ImageRequest.Builder.setImageSize(\n    width: Int,\n    height: Int,\n    compactMode: Boolean\n): ImageRequest.Builder {\n    val quality = if (compactMode) LOW_IMAGE_QUALITY else HIGH_IMAGE_QUALITY\n    return if (width > 0 && height > 0) size(\n        width = (width * quality).toInt(),\n        height = (height * quality).toInt()\n    ) else this\n}\n\n@Preview\n@Composable\nprivate fun MonoPreview() {\n    ImlaTheme {\n        MonoImage(imageUrl = \"\", imageHeight = 150.dp, compactMode = false) {}\n    }\n}\n\n@Preview\n@Composable\nprivate fun DuoPreview() {\n    ImlaTheme {\n        DuoImage(imgA = \"\", imgB = \"\", imageHeight = 150.dp, compactMode = false) {}\n    }\n}\n\n@Preview\n@Composable\nprivate fun TriplePreview() {\n    ImlaTheme {\n        TripleImage(imgA = \"\", imgB = \"\", imgC = \"\", imageHeight = 150.dp, compactMode = false) {}\n    }\n}\n\n\n\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/userpost/SimpleImageViewer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.userpost\n\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.foundation.clickable\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport coil.size.Size\n\n@Composable\nfun SimpleImageViewer(\n    modifier: Modifier,\n    imageUrl: String,\n    onDismiss: () -> Unit\n) {\n    BackHandler(true) {\n        onDismiss()\n    }\n    val context = LocalContext.current\n    val request = remember(imageUrl, context) {\n        ImageRequest.Builder(context)\n            .data(imageUrl)\n            .size(Size.ORIGINAL)\n            .crossfade(true)\n            .build()\n    }\n    AsyncImage(\n        modifier = modifier.clickable(onClick = onDismiss),\n        model = request,\n        contentDescription = null,\n        contentScale = ContentScale.Fit\n    )\n}"
  },
  {
    "path": "app/src/main/java/dev/serhiiyaremych/imla/ui/userpost/UserPostView.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui.userpost\n\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.Favorite\nimport androidx.compose.material.icons.outlined.Message\nimport androidx.compose.material.icons.outlined.Refresh\nimport androidx.compose.material.icons.outlined.Share\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.LocalContentColor\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.withStyle\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport dev.serhiiyaremych.imla.data.UserPost\nimport java.time.Instant\nimport java.util.concurrent.TimeUnit\n\nprivate const val SHOW_ICONS = true\n\nprivate val AvatarSizeLarge = 44.dp\nprivate val AvatarSizeSmall = 22.dp\n\n@Composable\nfun UserPostView(\n    post: UserPost,\n    modifier: Modifier = Modifier,\n    compactMode: Boolean = false,\n    monochromeBottomIcons: Boolean = false,\n    onImageClick: (imageUrl: String) -> Unit = {},\n    onItemClick: (id: String) -> Unit = {}\n) {\n    val contentPadding = if (compactMode) 8.dp else 16.dp\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .clickable(\n                interactionSource = remember { MutableInteractionSource() },\n                indication = null,\n                onClick = { onItemClick.invoke(post.id) }\n            )\n            .padding(contentPadding)\n    ) {\n        UserLine(post, compactMode)\n        Column(\n            modifier = Modifier\n                .padding(start = if (compactMode) 0.dp else contentPadding)\n                .padding(\n                    start = if (compactMode) 0.dp else AvatarSizeLarge,\n                    top = if (compactMode) AvatarSizeSmall else AvatarSizeLarge / 2\n                )\n                .fillMaxWidth()\n        ) {\n\n            Spacer(modifier = Modifier.height(8.dp))\n\n            val textSize = if (compactMode) 12.sp else 14.sp\n            Text(\n                text = post.text,\n                style = MaterialTheme.typography.bodyLarge.copy(fontSize = textSize)\n            )\n\n            if (post.images.isNotEmpty()) {\n                Spacer(modifier = Modifier.height(8.dp))\n                ImageList(\n                    images = post.images,\n                    compactMode = compactMode,\n                    onImageClick = onImageClick\n                )\n            }\n            if (post.reply != null) {\n                Spacer(modifier = Modifier.height(8.dp))\n                ReplyView(post.reply)\n            }\n\n            if (post.poll != null) {\n                Spacer(modifier = Modifier.height(8.dp))\n                PollView(poll = post.poll, compactMode = compactMode)\n            }\n\n            if (compactMode.not()) {\n                TweetFooter(post, monochromeBottomIcons)\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ReplyView(tweet: UserPost) {\n    UserPostView(\n        modifier = Modifier\n            .height(IntrinsicSize.Min)\n            .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))\n            .padding(2.dp),\n        post = tweet,\n        compactMode = true\n    )\n}\n\n@Composable\nprivate fun UserLine(tweet: UserPost, compactMode: Boolean) {\n    val nameColor = MaterialTheme.colorScheme.onBackground\n    val userString =\n        remember(tweet.userFullName, tweet.userNickname, tweet.createdAt, compactMode, nameColor) {\n            buildUserString(\n                fullName = tweet.userFullName,\n                nickName = tweet.userNickname,\n                createdAt = tweet.createdAt,\n                compactMode = compactMode,\n                nameColor = nameColor\n            )\n        }\n    Row {\n        Avatar(userAvatar = tweet.userAvatar, compactMode = compactMode)\n        Spacer(modifier = Modifier.width(if (compactMode) 8.dp else 16.dp))\n        Text(text = userString, style = MaterialTheme.typography.bodySmall)\n    }\n}\n\n@Composable\nprivate fun TweetFooter(tweet: UserPost, monochromeBottomIcons: Boolean) {\n    Spacer(modifier = Modifier.height(8.dp))\n    Row(\n        horizontalArrangement = Arrangement.SpaceAround,\n        modifier = Modifier.fillMaxWidth()\n    ) {\n        IconWithText(\n            icon = Icons.Outlined.Message,\n            text = \"${tweet.messages}\",\n            iconTint = if (monochromeBottomIcons) Color.LightGray else MaterialTheme.colorScheme.primary\n        )\n        IconWithText(\n            icon = Icons.Outlined.Refresh,\n            text = \"${tweet.replyCount}\",\n            iconTint = if (monochromeBottomIcons) Color.LightGray else MaterialTheme.colorScheme.secondary\n        )\n        IconWithText(\n            icon = Icons.Outlined.Favorite,\n            text = \"${tweet.likes}\",\n            iconTint = if (monochromeBottomIcons) Color.LightGray else MaterialTheme.colorScheme.tertiary\n        )\n        IconWithText(\n            icon = Icons.Outlined.Share,\n            text = \"\",\n            iconTint = if (monochromeBottomIcons) Color.LightGray else Color.LightGray\n        )\n    }\n}\n\nprivate fun buildUserString(\n    fullName: String,\n    nickName: String,\n    createdAt: Long,\n    compactMode: Boolean,\n    nameColor: Color\n) = buildAnnotatedString {\n    withStyle(\n        SpanStyle(\n            fontSize = 13.sp,\n            fontWeight = FontWeight.Black,\n            color = nameColor\n        )\n    ) {\n        append(fullName)\n    }\n    append(\" \")\n    withStyle(\n        SpanStyle(\n            fontSize = 12.sp,\n            fontWeight = FontWeight.Normal,\n            color = Color.DarkGray\n        )\n    ) {\n        append(nickName)\n        append(\" • \")\n        append(formattedTime(createdAt, compactMode))\n    }\n}\n\nprivate fun formattedTime(createdAt: Long, compactMode: Boolean): String {\n    val hours = if (compactMode) \"h\" else \" hours\"\n    val minutes = if (compactMode) \"min\" else \" minutes\"\n    val timeAgo = buildString {\n        val now = Instant.now()\n        val diff = now.minusMillis(createdAt).toEpochMilli()\n        val hrs = TimeUnit.MILLISECONDS.toHours(diff)\n        val minutesTime = if (hrs > 0) 0L else TimeUnit.MILLISECONDS.toMinutes(diff)\n        if (hrs > 0) {\n            when (hrs) {\n                1L -> if (compactMode) append(\"1h ago\") else append(\"an hour ago\")\n                else -> append(\"$hrs$hours ago\")\n            }\n            append(\" \")\n        }\n        if (minutesTime > 0) {\n            when (minutesTime) {\n                1L -> append(\"now\")\n                else -> append(\"$minutesTime$minutes ago\")\n            }\n        }\n\n        if (this.isBlank()) {\n            append(\"now\")\n        }\n    }\n    return timeAgo\n}\n\n@Composable\nfun Avatar(userAvatar: String, compactMode: Boolean) {\n    val imgLayoutSizeDp = if (compactMode) AvatarSizeSmall else AvatarSizeLarge\n    SinglePostImage(\n        modifier = Modifier.size(imgLayoutSizeDp),\n        clipShape = CircleShape,\n        imgUrl = userAvatar,\n        compactMode = compactMode\n    )\n}\n\n@Composable\nfun ImageList(\n    images: List<String>,\n    compactMode: Boolean,\n    imageRowHeight: Dp = if (compactMode) 125.dp else 150.dp,\n    onImageClick: (imageUrl: String) -> Unit\n) {\n    when (images.size) {\n        1 -> MonoImage(images[0], imageRowHeight, compactMode, onImageClick)\n        2 -> DuoImage(images[0], images[1], imageRowHeight, compactMode, onImageClick)\n        3 -> TripleImage(\n            imageHeight = imageRowHeight,\n            imgA = images[0],\n            imgB = images[1],\n            imgC = images[2],\n            compactMode = compactMode,\n            onImageClick = onImageClick\n        )\n\n        4 -> QuadImage(\n            imageHeight = imageRowHeight + 56.dp,\n            imgA = images[0],\n            imgB = images[1],\n            imgC = images[2],\n            imgD = images[3],\n            compactMode = compactMode,\n            onImageClick = onImageClick\n        )\n\n        else -> ImageRow(imageRowHeight, images, compactMode, onImageClick)\n    }\n\n}\n\n@Composable\nprivate fun IconWithText(icon: ImageVector, text: String, iconTint: Color) {\n\n    CompositionLocalProvider(\n        (LocalContentColor provides iconTint)\n    ) {\n        Row(verticalAlignment = Alignment.CenterVertically) {\n            if (SHOW_ICONS) {\n                Icon(\n                    imageVector = icon,\n                    contentDescription = null,\n                    modifier = Modifier.size(18.dp),\n                )\n            }\n            if (text.isNotEmpty()) {\n                Spacer(modifier = Modifier.width(6.dp))\n                Text(\n                    text = text,\n                    style = MaterialTheme.typography.labelMedium.copy(fontSize = 14.sp)\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<resources>\n    <string name=\"app_name\">Imla</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<resources>\n\n    <style name=\"Theme.Imla\" parent=\"android:Theme.Material.Light.NoActionBar\" />\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!--\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "app/src/test/java/dev/serhiiyaremych/imla/ExampleUnitTest.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "benchmark/.gitignore",
    "content": "/build"
  },
  {
    "path": "benchmark/build.gradle.kts",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\nplugins {\n    alias(libs.plugins.android.test)\n    alias(libs.plugins.jetbrains.kotlin.android)\n}\n\nandroid {\n    namespace = \"dev.serhiiyaremych.benchmark\"\n    compileSdk = 34\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_1_8\n        targetCompatibility = JavaVersion.VERSION_1_8\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n    defaultConfig {\n        minSdk = 26\n        targetSdk = 34\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        // This benchmark buildType is used for benchmarking, and should function like your\n        // release build (for example, with minification on). It\"s signed with a debug key\n        // for easy local/CI testing.\n        create(\"benchmark\") {\n            isDebuggable = true\n            signingConfig = getByName(\"debug\").signingConfig\n            matchingFallbacks += listOf(\"release\")\n        }\n    }\n\n    targetProjectPath = \":app\"\n    experimentalProperties[\"android.experimental.self-instrumenting\"] = true\n}\n\ndependencies {\n    implementation(libs.androidx.junit)\n    implementation(libs.androidx.espresso.core)\n    implementation(libs.androidx.uiautomator)\n    implementation(libs.androidx.benchmark.macro.junit4)\n}\n\nandroidComponents {\n    beforeVariants(selector().all()) {\n        it.enable = it.buildType == \"benchmark\"\n    }\n}"
  },
  {
    "path": "benchmark/src/main/AndroidManifest.xml",
    "content": "<!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<manifest />"
  },
  {
    "path": "benchmark/src/main/java/dev/serhiiyaremych/benchmark/ImlaBenchmark.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.benchmark\n\nimport androidx.benchmark.macro.CompilationMode\nimport androidx.benchmark.macro.ExperimentalMetricApi\nimport androidx.benchmark.macro.FrameTimingGfxInfoMetric\nimport androidx.benchmark.macro.FrameTimingMetric\nimport androidx.benchmark.macro.StartupMode\nimport androidx.benchmark.macro.TraceSectionMetric\nimport androidx.benchmark.macro.junit4.MacrobenchmarkRule\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.uiautomator.By\nimport androidx.test.uiautomator.UiDevice\nimport androidx.test.uiautomator.Until\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * This is an example startup benchmark.\n *\n * It navigates to the device's home screen, and launches the default activity.\n *\n * Before running this benchmark:\n * 1) switch your app's active build variant in the Studio (affects Studio runs only)\n * 2) add `<profileable android:shell=\"true\" />` to your app's manifest, within the `<application>` tag\n *\n * Run this benchmark from Studio to see startup measurements, and captured system traces\n * for investigating your app's performance.\n */\n@RunWith(AndroidJUnit4::class)\nclass ImlaBenchmark {\n    @get:Rule\n    val benchmarkRule = MacrobenchmarkRule()\n\n    @OptIn(ExperimentalMetricApi::class)\n    @Test\n    fun measureRendering() = benchmarkRule.measureRepeated(\n        packageName = \"dev.serhiiyaremych.imla\",\n        compilationMode = CompilationMode.Full(),\n        metrics = listOf(\n            FrameTimingMetric(),\n            FrameTimingGfxInfoMetric(),\n            metricSection(\"RenderingPipeline#applyAllEffects\", TraceSectionMetric.Mode.Average),\n            metricSection(\"RenderObject#onRender\", TraceSectionMetric.Mode.Average),\n            metricSection(\"copyExtTextureToFrameBuffer\", TraceSectionMetric.Mode.Average),\n            metricSection(\"fullSizeBuffer\", TraceSectionMetric.Mode.Average),\n        ),\n        iterations = 4,\n        startupMode = StartupMode.WARM\n    ) {\n        pressHome()\n        startActivityAndWait()\n\n        val uiDevice = this.device\n        val width = uiDevice.displayWidth\n        val height = uiDevice.displayHeight\n\n        repeat(3) {\n            uiDevice.awaitComposeIdle()\n            uiDevice.swipe(\n                width / 2,\n                (height * 0.75).toInt(),\n                width / 2,\n                (height * 0.25).toInt(),\n                8\n            )\n        }\n    }\n\n    @OptIn(ExperimentalMetricApi::class)\n    private fun metricSection(label: String, mode: TraceSectionMetric.Mode) = TraceSectionMetric(\n        sectionName = label,\n        mode = mode\n    )\n\n    private fun UiDevice.awaitComposeIdle(timeout: Long = 3000) {\n        wait(Until.findObject(By.desc(\"COMPOSE-IDLE\")), timeout)\n    }\n}"
  },
  {
    "path": "build.gradle.kts",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.jetbrains.kotlin.android) apply false\n    alias(libs.plugins.jetbrains.kotlin.serialization) apply false\n    alias(libs.plugins.android.library) apply false\n    alias(libs.plugins.compose.compiler) apply false\n    alias(libs.plugins.android.test) apply false\n}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"8.9.1\"\ncoilCompose = \"2.7.0\"\ncollection = \"1.5.0\"\ncomposeBomAlpha = \"2025.03.00\"\ndesugar_jdk_libs = \"2.1.5\"\ngraphicsCore = \"1.0.2\"\nhaze = \"1.5.2\"\nkotlin = \"2.1.20\"\ncoreKtx = \"1.15.0\"\njunit = \"4.13.2\"\njunitVersion = \"1.2.1\"\nespressoCore = \"3.6.1\"\nkotlinxCollectionsImmutable = \"0.3.8\"\nkotlinxSerializationJson = \"1.8.0\"\nlifecycleRuntimeKtx = \"2.8.7\"\nactivityCompose = \"1.10.1\"\n#composeBom = \"2024.04.01\"\nappcompat = \"1.7.0\"\nmaterial = \"1.12.0\"\nkotlinMath = \"1.6.0\"\nruntimeTracing = \"1.7.8\"\ntracingKtx = \"1.3.0-beta01\"\nuiautomator = \"2.3.0\"\nbenchmarkMacroJunit4 = \"1.3.3\"\n\n[libraries]\nandroidx-collection = { module = \"androidx.collection:collection\", version.ref = \"collection\" }\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nandroidx-foundation = { module = \"androidx.compose.foundation:foundation\" }\nandroidx-graphics-core = { module = \"androidx.graphics:graphics-core\", version.ref = \"graphicsCore\" }\nandroidx-material-icons-extended = { module = \"androidx.compose.material:material-icons-extended\" }\nandroidx-runtime-tracing = { module = \"androidx.compose.runtime:runtime-tracing\", version.ref = \"runtimeTracing\" }\nandroidx-tracing-ktx = { module = \"androidx.tracing:tracing-ktx\", version.ref = \"tracingKtx\" }\nandroidx-ui-util = { module = \"androidx.compose.ui:ui-util\" }\ncoil-compose = { module = \"io.coil-kt:coil-compose\", version.ref = \"coilCompose\" }\ncompose-bom = { module = \"androidx.compose:compose-bom-alpha\", version.ref = \"composeBomAlpha\" }\ndesugar_jdk_libs = { module = \"com.android.tools:desugar_jdk_libs\", version.ref = \"desugar_jdk_libs\" }\nhaze = { module = \"dev.chrisbanes.haze:haze\", version.ref = \"haze\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycleRuntimeKtx\" }\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activityCompose\" }\n#androidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\nandroidx-ui = { group = \"androidx.compose.ui\", name = \"ui\" }\nandroidx-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\" }\nandroidx-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\nandroidx-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\nandroidx-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\" }\nandroidx-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"appcompat\" }\nkotlinx-collections-immutable = { module = \"org.jetbrains.kotlinx:kotlinx-collections-immutable\", version.ref = \"kotlinxCollectionsImmutable\" }\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerializationJson\" }\nmaterial = { group = \"com.google.android.material\", name = \"material\", version.ref = \"material\" }\n#material3 = { module = \"androidx.compose.material3:material3\" }\nkotlin-math = { module = \"dev.romainguy:kotlin-math\", version.ref = \"kotlinMath\" }\nandroidx-uiautomator = { group = \"androidx.test.uiautomator\", name = \"uiautomator\", version.ref = \"uiautomator\" }\nandroidx-benchmark-macro-junit4 = { group = \"androidx.benchmark\", name = \"benchmark-macro-junit4\", version.ref = \"benchmarkMacroJunit4\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\njetbrains-kotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\njetbrains-kotlin-serialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\nandroid-library = { id = \"com.android.library\", version.ref = \"agp\" }\nandroid-test = { id = \"com.android.test\", version.ref = \"agp\" }\ncompose-compiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#\n# /*\n#  * Copyright 2025, Serhii Yaremych\n#  * SPDX-License-Identifier: MIT\n#  */\n#\n\n#Fri Sep 27 19:22:38 EEST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "#\n# Copyright 2024, Serhii Yaremych\n# SPDX-License-Identifier: MIT\n#\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2024, Serhii Yaremych\n# SPDX-License-Identifier: MIT\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "imla/.gitignore",
    "content": "/build"
  },
  {
    "path": "imla/build.gradle.kts",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\nplugins {\n    alias(libs.plugins.android.library)\n    alias(libs.plugins.jetbrains.kotlin.android)\n    alias(libs.plugins.compose.compiler)\n}\n\nandroid {\n    namespace = \"dev.serhiiyaremych.imla\"\n    compileSdk = 34\n\n    defaultConfig {\n        minSdk = 23\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n\n    buildTypes {\n        create(\"benchmark\") {\n            initWith(buildTypes.getByName(\"release\"))\n            matchingFallbacks += listOf(\"release\")\n        }\n\n        release {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n    kotlin {\n        explicitApi()\n    }\n    kotlinOptions {\n        freeCompilerArgs += \"-Xcontext-receivers\"\n        jvmTarget = \"17\"\n    }\n    buildFeatures {\n        compose = true\n        shaders = true\n\n        dataBinding = false\n        viewBinding = false\n        mlModelBinding = false\n        aidl = false\n        buildConfig = true\n    }\n    lint {\n        abortOnError = true\n        warningsAsErrors = true\n    }\n}\n\ndependencies {\n\n    api(platform(libs.compose.bom))\n    implementation(libs.androidx.ui.util)\n    implementation(libs.androidx.collection)\n    implementation(libs.kotlin.math)\n    implementation(libs.androidx.foundation)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.graphics.core)\n    implementation(libs.androidx.runtime.tracing)\n    implementation(libs.androidx.tracing.ktx)\n\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n}"
  },
  {
    "path": "imla/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "imla/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n-dontobfuscate"
  },
  {
    "path": "imla/src/androidTest/java/dev/serhiiyaremych/imla/ExampleInstrumentedTest.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"dev.serhiiyaremych.imla.test\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "imla/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2024, Serhii Yaremych\n  ~ SPDX-License-Identifier: MIT\n  -->\n\n<manifest>\n\n</manifest>"
  },
  {
    "path": "imla/src/main/assets/shader/blur_down.frag",
    "content": "#version 300 es\nprecision mediump float;\n\n#define GAMMA 2.2\n\nuniform sampler2D u_Texture;\nuniform vec2 u_Texel;\nuniform vec2 u_ContentOffset;\n\nin vec2 texCoord;\n\nout vec4 color;\n\nconst vec2 S = vec2(-1, 1);\nconst float WEIGHT_SUM_INV = 1.0 / 3.125;\nconst vec2 UV_MIN = vec2(0.0);\nconst vec2 UV_MAX = vec2(1.0);\n\n// Simple approximation\n// Convert sRGB color to linear space\nvec4 gammaDecode(vec4 rgba) {\n    vec3 rgb = rgba.rgb;\n    return vec4(rgb * rgb, rgba.a);\n}\n// Convert linear color to sRGB space\nvec4 gammaEncode(vec4 rgba) {\n    vec3 rgb = rgba.rgb;\n    return vec4(sqrt(rgb), rgba.a);\n}\n\nvec4 safeTexture(sampler2D tex, vec2 uv) {\n//    return texture(tex, clamp(uv, UV_MIN + u_ContentOffset, UV_MAX - u_ContentOffset));\n    return texture(tex, clamp(uv, UV_MIN, UV_MAX));\n}\n\n// Credits:\n// Jorge Jimenez,\n// NEXT GENERATION POST PROCESSING IN CALL OF DUTY: ADVANCED WARFARE, https://advances.realtimerendering.com/s2014/index.html\n// . . . . . . .\n// . A . B . C .\n// . . D . E . .\n// . F . G . H .\n// . . I . J . .\n// . K . L . M .\n// . . . . . . .\n// Temporally stable box filtering\nvoid main() {\n    vec2 uv = texCoord;\n\n    // center point\n    vec4 sum = gammaDecode(safeTexture(u_Texture, uv)) * 0.125; // G\n    // inner ring corners\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S)) * 0.5; // D\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yy)) * 0.5; // E\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yx)) * 0.5; // I\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.xx)) * 0.5; // J\n\n    // outer ring corners\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S * 2.0)) * 0.125; // A\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yy * 2.0)) * 0.125; // C\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yx * 2.0)) * 0.125; // M\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.xx * 2.0)) * 0.125; // K\n\n    // middle ring sides\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(S.x, 0.0) * 2.0)) * 0.125; // F\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(0.0, S.y) * 2.0)) * 0.125; // B\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(S.y, 0.0) * 2.0)) * 0.125; // H\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(0.0, S.x) * 2.0)) * 0.125; // L\n\n    sum *= WEIGHT_SUM_INV;\n    color = gammaEncode(sum);\n}\n"
  },
  {
    "path": "imla/src/main/assets/shader/blur_quad.frag",
    "content": "#version 300 es\nprecision mediump float;\n\n#define GAMMA 2.2\n\nstruct VertexOutput\n{\n    float texIndex;\n    float flipTexture;\n    float isExternalTexture;\n    float alpha;\n    float mask;\n};\n// horiz=(1.0, 0.0), vert=(0.0, 1.0)\nuniform vec2 u_BlurDirection;\nuniform vec2 u_TexelSize;\nuniform float u_BlurSigma;\nuniform vec4 u_BlurTint;\n\nuniform sampler2D u_Textures[8];\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin VertexOutput data;\n\nout vec4 color;\n\n\n//Classic gamma correction functions\nvec3 linear_from_srgb(vec3 srgb) {\n    return vec3(\n        srgb.r <= 0.04045 ? srgb.r / 12.92 : pow((srgb.r + 0.055) / 1.055, GAMMA),\n        srgb.g <= 0.04045 ? srgb.g / 12.92 : pow((srgb.g + 0.055) / 1.055, GAMMA),\n        srgb.b <= 0.04045 ? srgb.b / 12.92 : pow((srgb.b + 0.055) / 1.055, GAMMA)\n    );\n}\n\nvec3 srgb_from_linear(vec3 lin) {\n    return vec3(\n        lin.r <= 0.0031308 ? lin.r * 12.92 : 1.055 * pow(lin.r, 1.0 / GAMMA) - 0.055,\n        lin.g <= 0.0031308 ? lin.g * 12.92 : 1.055 * pow(lin.g, 1.0 / GAMMA) - 0.055,\n        lin.b <= 0.0031308 ? lin.b * 12.92 : 1.055 * pow(lin.b, 1.0 / GAMMA) - 0.055\n    );\n}\n\nfloat gaussianWeight(float x, float sigma) {\n    return exp(-0.5 * (x * x) / (sigma * sigma));\n}\n\nvoid main() {\n    bool flipTexture = int(data.flipTexture) > 0;\n    vec2 loc = mix(texCoord, vec2(texCoord.x, 1.0 - texCoord.y), data.flipTexture);\n    vec2 dir = u_BlurDirection / u_TexelSize;\n    vec4 acc = vec4(0.0);\n    float totalWeight = 0.0;\n    int support = int(u_BlurSigma) * 3;\n    float sigma = u_BlurSigma;\n    //    int step = 9; // creates an interesting effect of embossed glass\n    int step = 1;\n\n    for (int i = -support; i <= support; i += step) {\n        float fi = float(i);\n        float x = fi;\n        float weight = gaussianWeight(x, sigma);\n        vec4 texColor = texture(u_Textures[1], loc + x * dir); // todo: support batching\n        texColor.rgb = linear_from_srgb(texColor.rgb);\n        acc += texColor * weight;\n        totalWeight += weight;\n    }\n    acc.rgb = srgb_from_linear(acc.rgb * (1.0 / totalWeight));\n    acc.a *= (1.0 / totalWeight);\n    color = mix(acc, u_BlurTint, u_BlurTint.a * u_BlurTint.a);\n}"
  },
  {
    "path": "imla/src/main/assets/shader/blur_up.frag",
    "content": "#version 300 es\nprecision mediump float;\n\n#define GAMMA 2.2\n\nuniform sampler2D u_Texture;\nuniform vec2 u_Texel;\nuniform vec4 u_Tint;\n\nin vec2 texCoord;\n\nout vec4 color;\n\nconst vec2 S = vec2(-1, 1);\nconst float WEIGHT_SUM_INV = 1.0 / 16.0;\nconst vec2 UV_MIN = vec2(0.0);\nconst vec2 UV_MAX = vec2(1.0);\n\n// Simple approximation\n// Convert sRGB color to linear space\nvec4 gammaDecode(vec4 rgba) {\n    vec3 rgb = rgba.rgb;\n    return vec4(rgb * rgb, rgba.a);\n}\n// Convert linear color to sRGB space\nvec4 gammaEncode(vec4 rgba) {\n    vec3 rgb = rgba.rgb;\n    return vec4(sqrt(rgb), rgba.a);\n}\n\nvec4 safeTexture(sampler2D tex, vec2 uv) {\n    return texture(tex, clamp(uv, UV_MIN, UV_MAX));\n}\n\n// Credits:\n// Jorge Jimenez,\n// NEXT GENERATION POST PROCESSING IN CALL OF DUTY: ADVANCED WARFARE, https://advances.realtimerendering.com/s2014/index.html\n// 9-tap bilinear upsampler (tent filter)\n//      [ 1  2  1 ]\n// 1/16 [ 2  4  2 ]\n//      [ 1  2  1 ]\nvoid main() {\n    vec2 uv = texCoord;\n    // center point\n    vec4 sum = gammaDecode(safeTexture(u_Texture, uv)) * 4.0;\n\n    // corners\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S)); // tl\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yy)); // tr\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.yx)); // br\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * S.xx)); // bl\n\n    // sides\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(S.x, 0.0))) * 2.0; // ml\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(0.0, S.y))) * 2.0; // mt\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(S.y, 0.0))) * 2.0; // mr\n    sum += gammaDecode(safeTexture(u_Texture, uv + u_Texel * vec2(0.0, S.x))) * 2.0; // mb\n\n    sum *= WEIGHT_SUM_INV;\n    color = gammaEncode(sum);\n    color = mix(color, u_Tint, u_Tint.a * u_Tint.a);\n}"
  },
  {
    "path": "imla/src/main/assets/shader/default_quad.frag",
    "content": "#version 300 es\n#extension GL_OES_standard_derivatives: enable\nprecision mediump float;\n\nstruct VertexOutput\n{\n    float texIndex;\n    float flipTexture;\n    float isExternalTexture;\n    float alpha;\n    float mask;\n};\n\nuniform sampler2D u_Textures[8]; // todo: pre-process source before compilation to set HW value\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin VertexOutput data;\n\nout vec4 color;\n\nvoid main()\n{\n    vec4 baseColor = vec4(1.);\n    vec2 texCoord = mix(texCoord, vec2(texCoord.x, 1.0 - texCoord.y), data.flipTexture);\n\n    switch (int(data.texIndex)) {\n        case 0:\n            baseColor = texture(u_Textures[0], texCoord);break;\n        case 1:\n            baseColor = texture(u_Textures[1], texCoord);break;\n        case 2:\n            baseColor = texture(u_Textures[2], texCoord);break;\n        case 3:\n            baseColor = texture(u_Textures[3], texCoord);break;\n        case 4:\n            baseColor = texture(u_Textures[4], texCoord);break;\n        case 5:\n            baseColor = texture(u_Textures[5], texCoord);break;\n        case 6:\n            baseColor = texture(u_Textures[6], texCoord);break;\n        case 7:\n            baseColor = texture(u_Textures[7], texCoord);break;\n    }\n\n    baseColor.a = data.alpha;\n    color = baseColor;\n}\n"
  },
  {
    "path": "imla/src/main/assets/shader/default_quad.vert",
    "content": "#version 300 es\nprecision mediump float;\n\nuniform mat4 u_ViewProjection;\nlayout (location = 0) in vec2 a_TexCoord;\nlayout (location = 1) in vec4 a_Position;\nlayout (location = 2) in float a_TexIndex;\nlayout (location = 3) in float a_FlipTexture;\nlayout (location = 4) in float a_IsExternalTexture;\nlayout (location = 5) in float a_Alpha;\nlayout (location = 6) in float a_Mask;\nlayout (location = 7) in vec2 a_MaskCoord;\n\nstruct VertexOutput\n{\n    float texIndex;\n    float flipTexture;\n    float isExternalTexture;\n    float alpha;\n    float mask;\n};\n\nout vec2 maskCoord;\nout vec2 texCoord;\nout VertexOutput data;\n\nvoid main() {\n    maskCoord = a_MaskCoord;\n    texCoord = a_TexCoord;\n    data.texIndex = a_TexIndex;\n    data.flipTexture = a_FlipTexture;\n    data.isExternalTexture = a_IsExternalTexture;\n    data.alpha = a_Alpha;\n    data.mask = a_Mask;\n    gl_Position = u_ViewProjection * a_Position;\n}"
  },
  {
    "path": "imla/src/main/assets/shader/external_quad.frag",
    "content": "#version 300 es\n#extension GL_OES_EGL_image_external_essl3: enable\n#extension GL_OES_EGL_image_external: require\nprecision mediump float;\n\nstruct VertexOutput\n{\n    float texIndex;\n    float flipTexture;\n    float isExternalTexture;\n    float alpha;\n    float mask;\n};\n\nuniform samplerExternalOES u_Textures[8];\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin VertexOutput data;\n\nout vec4 color;\n\nvoid main()\n{\n    vec4 baseColor = vec4(1.);\n    vec2 texCoord = mix(texCoord, vec2(texCoord.x, 1.0 - texCoord.y), data.flipTexture);\n\n    switch (int(data.texIndex)) {\n        case 0:\n            baseColor = texture(u_Textures[0], texCoord);break;\n        case 1:\n            baseColor = texture(u_Textures[1], texCoord);break;\n        case 2:\n            baseColor = texture(u_Textures[2], texCoord);break;\n        case 3:\n            baseColor = texture(u_Textures[3], texCoord);break;\n        case 4:\n            baseColor = texture(u_Textures[4], texCoord);break;\n        case 5:\n            baseColor = texture(u_Textures[5], texCoord);break;\n        case 6:\n            baseColor = texture(u_Textures[6], texCoord);break;\n        case 7:\n            baseColor = texture(u_Textures[7], texCoord);break;\n    }\n\n    baseColor.a = data.alpha;\n    color = baseColor;\n}\n"
  },
  {
    "path": "imla/src/main/assets/shader/mask.frag",
    "content": "#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_Background;\nuniform sampler2D u_Mask;\nuniform sampler2D u_Textures[8];\n\nstruct VertexOutput\n{\n    float texIndex;\n    float flipTexture;\n    float isExternalTexture;\n    float alpha;\n    float mask;\n};\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin VertexOutput data;\n\nout vec4 color;\n\nvoid main() {\n    vec4 maskColor = texture(u_Mask, maskCoord);\n\n    vec2 texCoord = mix(texCoord, vec2(texCoord.x, 1.0 - texCoord.y), data.flipTexture);\n\n    vec4 backgroundColor = texture(u_Background, vec2(maskCoord.x, 1. - maskCoord.y));\n\n    vec4 contentColor = vec4(1.0, 1.0, 1.0, 1.0);\n\n    switch (int(data.texIndex)) {\n        case 0:\n            contentColor = texture(u_Textures[0], texCoord);break;\n        case 1:\n            contentColor = texture(u_Textures[1], texCoord);break;\n        case 2:\n            contentColor = texture(u_Textures[2], texCoord);break;\n        case 3:\n            contentColor = texture(u_Textures[3], texCoord);break;\n        case 4:\n            contentColor = texture(u_Textures[4], texCoord);break;\n        case 5:\n            contentColor = texture(u_Textures[5], texCoord);break;\n        case 6:\n            contentColor = texture(u_Textures[6], texCoord);break;\n        case 7:\n            contentColor = texture(u_Textures[7], texCoord);break;\n    }\n\n    vec4 finalColor = contentColor;\n\n    finalColor.rgb = mix(backgroundColor.rgb, contentColor.rgb, maskColor.r);\n    color = finalColor;\n}"
  },
  {
    "path": "imla/src/main/assets/shader/noise.frag",
    "content": "#version 300 es\n#extension GL_OES_standard_derivatives: enable\nprecision mediump float;\n\n#define GAMMA 2.2\n\nin vec2 texCoord;\nout vec4 color;\n\nhighp float rand(vec2 co)\n{\n    highp float a = 12.9898;\n    highp float b = 78.233;\n    highp float c = 43758.5453;\n    highp float dt = dot(co.xy, vec2(a, b));\n    highp float sn = mod(dt, 3.14);\n    return fract(sin(sn) * c);\n}\n\nvoid main()\n{\n    // color = vec4(1.0);\n    float val = pow(clamp(rand(texCoord), 0.5, 0.8), 1.0 / GAMMA);\n    color = vec4(vec3(val, val, val), 1.0);\n}"
  },
  {
    "path": "imla/src/main/assets/shader/simple_blur.frag",
    "content": "#version 300 es\nprecision mediump float;\n\n#define GAMMA 2.2\n\n// horiz=(1.0, 0.0), vert=(0.0, 1.0)\nuniform vec2 u_BlurDirection;\nuniform float u_BlurSigma;\nuniform vec4 u_BlurTint;\nuniform vec2 u_TexelSize;\n\nuniform sampler2D u_Texture;\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin float alpha;\n\nout vec4 color;\n\n\n//Classic gamma correction functions\nvec3 linear_from_srgb(vec3 srgb) {\n    return srgb * srgb;\n}\n\nvec3 srgb_from_linear(vec3 lin) {\n    return sqrt(lin);\n}\n\nfloat gaussianWeight(float x, float sigma) {\n    return exp(-0.5 * (x * x) / (sigma * sigma));\n}\n\nvoid main() {\n    vec2 loc = texCoord;\n    vec2 dir = u_BlurDirection / u_TexelSize;\n    vec4 acc = vec4(0.0);\n    float totalWeight = 0.0;\n    int support = int(u_BlurSigma) * 3;\n    float sigma = u_BlurSigma;\n    //    int step = 9; // creates an interesting effect of embossed glass\n    int step = 1;\n\n    for (int i = -support; i <= support; i += step) {\n        float fi = float(i);\n        float x = fi;\n        float weight = gaussianWeight(x, sigma);\n        vec4 texColor = texture(u_Texture, loc + x * dir);\n        texColor.rgb = linear_from_srgb(texColor.rgb);\n        acc += texColor * weight;\n        totalWeight += weight;\n    }\n    acc.rgb = srgb_from_linear(acc.rgb * (1.0 / totalWeight));\n    acc.a *= (1.0 / totalWeight);\n    color = mix(acc, u_BlurTint, u_BlurTint.a * u_BlurTint.a);\n}"
  },
  {
    "path": "imla/src/main/assets/shader/simple_ext_quad.frag",
    "content": "#version 300 es\n#extension GL_OES_EGL_image_external_essl3: enable\n#extension GL_OES_EGL_image_external: require\nprecision mediump float;\n\nuniform samplerExternalOES u_Texture;\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin vec2 texSize;\nin float alpha;\nin float flip;\n\nout vec4 color;\n\nvoid main()\n{\n    vec4 baseColor = texture(u_Texture, texCoord);\n    baseColor.a = alpha;\n    color = baseColor;\n}\n"
  },
  {
    "path": "imla/src/main/assets/shader/simple_mask.frag",
    "content": "#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_Background;\nuniform sampler2D u_Mask;\nuniform sampler2D u_Texture;\n\nin vec2 maskCoord;\nin vec2 texCoord;\n\nout vec4 color;\n\nvoid main() {\n    vec4 maskColor = texture(u_Mask, maskCoord);\n    vec4 backgroundColor = texture(u_Background, maskCoord);\n    vec4 contentColor = texture(u_Texture, vec2(texCoord.x, texCoord.y));\n    vec4 finalColor = contentColor;\n    finalColor.rgb = mix(backgroundColor.rgb, contentColor.rgb, maskColor.r);\n    color = finalColor;\n}"
  },
  {
    "path": "imla/src/main/assets/shader/simple_quad.frag",
    "content": "#version 300 es\n#extension GL_OES_standard_derivatives: enable\nprecision mediump float;\n\nuniform sampler2D u_Texture;\n\nin vec2 maskCoord;\nin vec2 texCoord;\nin float alpha;\n\nout vec4 color;\n\nvoid main()\n{\n    vec4 baseColor = texture(u_Texture, texCoord);\n    baseColor.a *= alpha;\n    color = baseColor;\n}\n"
  },
  {
    "path": "imla/src/main/assets/shader/simple_quad.vert",
    "content": "#version 300 es\nprecision mediump float;\n\nlayout (std140) uniform TextureDataUBO {\n    vec2 uv[4];         // x, y,\n    //    vec2 size;          // width, height\n    float flipTexture;  // flip Y texture coordinate\n    float alpha;        // alpha blending of the texture\n} textureData;\n\nlayout (location = 0) in vec2 aPosition;\n\nout vec2 maskCoord;\nout vec2 texCoord;\n//out vec2 texSize;\nout float alpha;\n\nvoid main() {\n    // Set the final position of the vertex in clip space\n    gl_Position = vec4(aPosition, 0.0, 1.0);\n\n    alpha = textureData.alpha;\n\n    maskCoord = aPosition * 0.5 + 0.5;\n    maskCoord.y = abs(textureData.flipTexture - maskCoord.y);\n    texCoord = textureData.uv[gl_VertexID % 4];\n    texCoord.y = abs(textureData.flipTexture - texCoord.y);\n    //    texSize = textureData.size;\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/ext/util.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ext\n\nimport android.opengl.GLES30\nimport android.util.Log\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.BuildConfig\nimport javax.microedition.khronos.egl.EGL10\nimport javax.microedition.khronos.egl.EGLContext\n\ninternal fun logw(tag: String, message: String) {\n    if (BuildConfig.DEBUG) Log.w(tag, message)\n}\n\ninternal fun logd(tag: String, message: String) {\n    if (BuildConfig.DEBUG) Log.d(tag, message)\n}\n\ninternal fun isGLThread(): Boolean {\n    val egl = EGLContext.getEGL() as? EGL10\n    return egl != null && egl.eglGetCurrentContext() != EGL10.EGL_NO_CONTEXT\n}\n\n@Suppress(\"UNUSED_PARAMETER\", \"NOTHING_TO_INLINE\")\ninternal fun checkGlError(action: Unit = Unit) {\n    if (BuildConfig.DEBUG) {\n        trace(\"checkGlError\") {\n            val error = GLES30.glGetError()\n            if (error != GLES30.GL_NO_ERROR) {\n                Log.e(\n                    \"checkGlError\",\n                    \"failed with error $error on thread ${Thread.currentThread().name}\"\n                )\n                throwError(error)\n            }\n        }\n    }\n}\n\ninternal fun throwError(errorCode: Int) {\n    error(errorCode)\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/modifier/ImlaBlurSourceModifier.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.modifier\n\nimport android.view.View\nimport android.view.ViewTreeObserver\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.drawscope.ContentDrawScope\nimport androidx.compose.ui.graphics.layer.drawLayer\nimport androidx.compose.ui.graphics.nativeCanvas\nimport androidx.compose.ui.node.CompositionLocalConsumerModifierNode\nimport androidx.compose.ui.node.DrawModifierNode\nimport androidx.compose.ui.node.ModifierNodeElement\nimport androidx.compose.ui.node.ObserverModifierNode\nimport androidx.compose.ui.node.currentValueOf\nimport androidx.compose.ui.node.observeReads\nimport androidx.compose.ui.platform.LocalView\nimport dev.serhiiyaremych.imla.ext.logd\nimport dev.serhiiyaremych.imla.uirenderer.UiLayerRenderer\n\npublic fun Modifier.blurSource(uiLayerRenderer: UiLayerRenderer): Modifier {\n    return this then ImlaSourceElement(uiLayerRenderer)\n}\n\ninternal class ImlaSourceNode(\n    internal var uiLayerRenderer: UiLayerRenderer\n) : Modifier.Node(), DrawModifierNode, ObserverModifierNode, CompositionLocalConsumerModifierNode {\n\n    private var currentView: View? by mutableStateOf(null)\n\n    @Suppress(\"ObjectLiteralToLambda\")\n    private val onDrawListener = object : ViewTreeObserver.OnPreDrawListener {\n        override fun onPreDraw(): Boolean {\n            uiLayerRenderer.onUiLayerUpdated()\n            return true\n        }\n    }\n\n    override fun onAttach() {\n        super.onAttach()\n        logd(TAG, \"onAttach\")\n        onObservedReadsChanged()\n    }\n\n    private fun subscribeOnDrawListener() {\n        currentView?.viewTreeObserver?.addOnPreDrawListener(onDrawListener)\n    }\n\n    private fun removeOnDrawListener() {\n        currentView?.viewTreeObserver?.removeOnPreDrawListener(onDrawListener)\n    }\n\n    override fun onObservedReadsChanged() {\n        observeReads {\n            currentView = currentValueOf(LocalView)\n            subscribeOnDrawListener()\n        }\n    }\n\n    override fun onDetach() {\n        super.onDetach()\n        logd(TAG, \"onDetach\")\n        removeOnDrawListener()\n    }\n\n    override fun ContentDrawScope.draw() {\n        if (drawContext.canvas.nativeCanvas.isHardwareAccelerated) {\n            uiLayerRenderer.recordCanvas { this@draw.drawContent() }\n        }\n        drawContent()\n    }\n\n    companion object {\n        private const val TAG = \"ImlaNode\"\n    }\n}\n\ninternal class ImlaSourceElement(\n    private val uiLayerRenderer: UiLayerRenderer\n) : ModifierNodeElement<ImlaSourceNode>() {\n    override fun create(): ImlaSourceNode {\n        return ImlaSourceNode(uiLayerRenderer)\n    }\n\n    override fun update(node: ImlaSourceNode) {\n        node.uiLayerRenderer = uiLayerRenderer\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as ImlaSourceElement\n\n        return uiLayerRenderer == other.uiLayerRenderer\n    }\n\n    override fun hashCode(): Int {\n        return uiLayerRenderer.hashCode()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/GfxBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport dev.serhiiyaremych.imla.renderer.opengl.OpenGLUniformBuffer\nimport dev.serhiiyaremych.imla.renderer.opengl.buffer.OpenGLIndexBuffer\nimport dev.serhiiyaremych.imla.renderer.opengl.buffer.OpenGLVertexBuffer\nimport java.nio.Buffer\nimport java.nio.ByteBuffer\nimport java.nio.ByteOrder\nimport java.nio.FloatBuffer\nimport java.nio.IntBuffer\n\ninternal interface GfxBuffer {\n    val elements: Int\n    val sizeBytes: Int\n    fun bind()\n    fun unbind()\n    fun destroy()\n}\n\ninternal enum class ShaderDataType(val components: kotlin.Int) {\n    None(0),\n    Float(1),\n    Float2(2),\n    Float3(3),\n    Float4(4),\n    Mat3(3 * 3),\n    Mat4(4 * 4),\n    Int(1),\n    Int2(2),\n    Int3(3),\n    Int4(4),\n    Bool(1);\n}\n\ninternal data class BufferElement(\n    val name: String,\n    val type: ShaderDataType,\n    val sizeBytes: Int,\n    var offset: Int = 0,\n    val normalized: Boolean = false\n)\n\ninternal class BufferLayout(\n    val elements: List<BufferElement>\n) : Iterable<BufferElement> by elements {\n    var stride: Int = 0\n        private set\n\n    constructor(action: MutableList<BufferElement>.() -> Unit) : this(buildList(action))\n\n    init {\n        calculateOffsetAndStride()\n    }\n\n    private fun calculateOffsetAndStride() {\n        var offset = 0\n        elements.forEach { element ->\n            element.offset = offset\n            offset += element.sizeBytes\n            stride += element.sizeBytes\n        }\n    }\n\n}\n\ninternal interface VertexBuffer : GfxBuffer {\n    var layout: BufferLayout?\n    fun setData(data: FloatArray)\n\n    companion object {\n        fun create(count: Int): VertexBuffer {\n            return OpenGLVertexBuffer(count)\n        }\n\n        fun create(vertices: FloatArray): VertexBuffer {\n            return OpenGLVertexBuffer(vertices)\n        }\n    }\n}\n\ninternal interface IndexBuffer : GfxBuffer {\n    companion object {\n        fun create(indices: IntArray): IndexBuffer {\n            return OpenGLIndexBuffer(indices)\n        }\n    }\n}\n\ninternal interface UniformBuffer : GfxBuffer {\n    fun setData(data: FloatArray)\n    fun setData(data: Buffer)\n\n    companion object {\n        fun create(count: Int, bindingPoint: Int): UniformBuffer {\n            return OpenGLUniformBuffer(count, bindingPoint)\n        }\n    }\n}\ninternal fun FloatArray.toFloatBuffer(): FloatBuffer = ByteBuffer\n    .allocateDirect(size * Float.SIZE_BYTES)\n    .order(ByteOrder.nativeOrder())\n    .asFloatBuffer()\n    .put(this)\n    .position(0) as FloatBuffer\n\ninternal fun IntArray.toIntBuffer(): IntBuffer = ByteBuffer\n    .allocateDirect(size * Int.SIZE_BYTES)\n    .order(ByteOrder.nativeOrder())\n    .asIntBuffer()\n    .put(this)\n    .position(0) as IntBuffer\n\ninternal fun MutableList<BufferElement>.addElement(\n    name: String,\n    type: ShaderDataType,\n    normalized: Boolean = false\n) {\n    val element = BufferElement(\n        name = name,\n        type = type,\n        sizeBytes = type.sizeBytes,\n        normalized = normalized\n    )\n    add(element)\n}\n\n// @formatter:off\ninternal val ShaderDataType.sizeBytes: Int\n    get() = when (this) {\n        ShaderDataType.Float  -> Float.SIZE_BYTES * 1\n        ShaderDataType.Float2 -> Float.SIZE_BYTES * 2\n        ShaderDataType.Float3 -> Float.SIZE_BYTES * 3\n        ShaderDataType.Float4 -> Float.SIZE_BYTES * 4\n        ShaderDataType.Mat3   -> Float.SIZE_BYTES * 3 * 3\n        ShaderDataType.Mat4   -> Float.SIZE_BYTES * 4 * 4\n        ShaderDataType.Int    -> Int.SIZE_BYTES   * 1\n        ShaderDataType.Int2   -> Int.SIZE_BYTES   * 2\n        ShaderDataType.Int3   -> Int.SIZE_BYTES   * 3\n        ShaderDataType.Int4   -> Int.SIZE_BYTES   * 4\n        ShaderDataType.Bool   -> 1\n        ShaderDataType.None   -> 0\n    }\n// @formatter:on"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/RenderCommand.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport androidx.compose.ui.graphics.Color\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.opengl.OpenGLRendererAPI\n\ninternal object RenderCommand {\n    private val rendererAPI: RendererApi = OpenGLRendererAPI()\n\n    val colorBufferBit: Int = rendererAPI.colorBufferBit\n    val linearTextureFilter: Int = rendererAPI.linearTextureFilter\n\n    fun init() {\n        rendererAPI.init()\n    }\n\n    fun setClearColor(color: Color) {\n        rendererAPI.setClearColor(color)\n    }\n\n    fun colorMask(red: Boolean, green: Boolean, blue: Boolean, alpha: Boolean) {\n        rendererAPI.colorMask(red, green, blue, alpha)\n    }\n\n    fun clear() {\n        rendererAPI.clear()\n    }\n\n    fun clear(color: Color) {\n        setClearColor(color)\n        rendererAPI.clear()\n    }\n\n    fun drawIndexed(vertexArray: VertexArray, indexCount: Int = 0) {\n        rendererAPI.drawIndexed(vertexArray, indexCount)\n    }\n\n    fun setViewPort(x: Int = 0, y: Int = 0, width: Int, height: Int) {\n        rendererAPI.setViewPort(x, y, width, height)\n    }\n\n    fun disableDepthTest() {\n        rendererAPI.disableDepthTest()\n    }\n\n    fun enableBlending() {\n        rendererAPI.enableBlending()\n    }\n\n    fun disableBlending() {\n        rendererAPI.disableBlending()\n    }\n\n    fun bindDefaultFramebuffer(bind: Bind = Bind.BOTH) {\n        rendererAPI.bindDefaultFramebuffer(bind)\n    }\n\n    fun useDefaultProgram() = rendererAPI.bindDefaultProgram()\n\n    fun blitFramebuffer(\n        srcX0: Int,\n        srcY0: Int,\n        srcX1: Int,\n        srcY1: Int,\n        dstX0: Int,\n        dstY0: Int,\n        dstX1: Int,\n        dstY1: Int,\n        mask: Int = colorBufferBit,\n        filter: Int = linearTextureFilter,\n    ) {\n        rendererAPI.blitFramebuffer(\n            srcX0 = srcX0,\n            srcY0 = srcY0,\n            srcX1 = srcX1,\n            srcY1 = srcY1,\n            dstX0 = dstX0,\n            dstY0 = dstY0,\n            dstX1 = dstX1,\n            dstY1 = dstY1,\n            mask = mask,\n            filter = filter\n        )\n    }\n\n    inline fun withBlendingModeEnabled(block: () -> Unit) {\n        enableBlending()\n        block()\n        disableBlending()\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/Renderer2D.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport android.content.res.AssetManager\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.tracing.trace\nimport dev.romainguy.kotlin.math.Float2\nimport dev.romainguy.kotlin.math.Float3\nimport dev.romainguy.kotlin.math.Float4\nimport dev.romainguy.kotlin.math.Mat4\nimport dev.romainguy.kotlin.math.rotation\nimport dev.romainguy.kotlin.math.scale\nimport dev.romainguy.kotlin.math.translation\nimport dev.romainguy.kotlin.math.transpose\nimport dev.serhiiyaremych.imla.renderer.camera.OrthographicCamera\nimport dev.serhiiyaremych.imla.renderer.objects.QuadShaderProgram\nimport dev.serhiiyaremych.imla.renderer.primitive.QuadVertex\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderProgram\n\ninternal const val MAX_QUADS = 50\ninternal const val MAX_VERTICES = MAX_QUADS * 4\ninternal const val MAX_INDICES = MAX_QUADS * 6\ninternal const val MAX_TEXTURE_SLOTS = 8 // query from actual HW\n\n\ninternal class Renderer2D {\n    private var _data: Renderer2DData? = null\n    internal val data: Renderer2DData get() = _data ?: error(\"Renderer2D not initialized!\")\n\n    private var isDrawingScene: Boolean = false\n\n    fun init(shaderLibrary: ShaderLibrary, shaderBinder: ShaderBinder) {\n        val quadIndices = IntArray(MAX_INDICES)\n        var offset = 0\n        for (i in quadIndices.indices step 6) {\n            quadIndices[i + 0] = offset + 0\n            quadIndices[i + 1] = offset + 1\n            quadIndices[i + 2] = offset + 2\n\n            quadIndices[i + 3] = offset + 2\n            quadIndices[i + 4] = offset + 3\n            quadIndices[i + 5] = offset + 0\n\n            offset += 4\n        }\n        val quadVertexArray: VertexArray = VertexArray.create()\n        val defaultQuadShaderProgram = QuadShaderProgram(\n            shaderBinder,\n            shader = shaderLibrary.loadShaderFromFile(\n                vertFileName = \"default_quad\",\n                fragFileName = \"default_quad\"\n            )\n        )\n        val quadVertexBuffer: VertexBuffer =\n            VertexBuffer.create(MAX_VERTICES * defaultQuadShaderProgram.componentsCount).apply {\n                layout = defaultQuadShaderProgram.vertexBufferLayout\n            }\n        quadVertexArray.addVertexBuffer(quadVertexBuffer)\n        quadVertexArray.indexBuffer = IndexBuffer.create(quadIndices)\n        val externalQuadShaderProgram = QuadShaderProgram(\n            shaderBinder,\n            shader = shaderLibrary.loadShaderFromFile(\n                vertFileName = \"default_quad\",\n                fragFileName = \"external_quad\"\n            )\n        )\n        val quadIndexCount = 0\n        val quadVertexBufferBase: MutableList<QuadVertex> = ArrayList(MAX_VERTICES)\n\n        val defaultQuadVertexPositions: Array<Float4> = Array(4) {\n            when (it) {\n                0 -> Float4(-0.5f, -0.5f, 0.0f, 1.0f) // BL\n                1 -> Float4(0.5f, -0.5f, 0.0f, 1.0f)  // BR\n                2 -> Float4(0.5f, 0.5f, 0.0f, 1.0f)   // TR\n                3 -> Float4(-0.5f, 0.5f, 0.0f, 1.0f)  // TL\n                else -> error(\"Wrong QuadVertex index: $it, max 3\")\n            }\n        }\n\n        val stats = RenderStatistics()\n\n        val whiteTexture: Texture2D =\n            Texture2D.create(Texture.Target.TEXTURE_2D, Texture.Specification())\n        val textureSlots: Array<Texture2D?> = Array(MAX_TEXTURE_SLOTS) { null }\n        val textureSlotIndex = 1 // 0 = white texture slot\n\n        _data = Renderer2DData(\n            cameraData = CameraData(Mat4.identity()),\n            whiteTexture = whiteTexture,\n            setStaticQuadData = false,\n            quadVertexArray = quadVertexArray,\n            quadVertexBuffer = quadVertexBuffer,\n            defaultQuadShaderProgram = defaultQuadShaderProgram,\n            externalQuadShaderProgram = externalQuadShaderProgram,\n            quadShaderProgram = externalQuadShaderProgram,\n            quadIndexCount = quadIndexCount,\n            quadVertexBufferBase = quadVertexBufferBase,\n            textureSlots = textureSlots,\n            quadVertexBufferStatic = null,\n            textureSlotIndex = textureSlotIndex,\n            defaultQuadVertexPositions = defaultQuadVertexPositions,\n            stats = stats,\n            shaderBinder = shaderBinder\n        )\n\n        whiteTexture.setData(intArrayOf(Color.White.toArgb()).toIntBuffer())\n        externalQuadShaderProgram.shader.bind(shaderBinder)\n\n        textureSlots.fill(null)\n        textureSlots[WHITE_TEXTURE_SLOT_INDEX] = whiteTexture\n\n    }\n\n    fun shutdown() {\n        data.defaultQuadShaderProgram.destroy()\n        data.externalQuadShaderProgram.destroy()\n        data.quadShaderProgram.destroy()\n        data.quadVertexArray.destroy()\n        data.textureSlots.fill(null)\n        _data = null\n    }\n\n    fun beginScene(camera: OrthographicCamera) {\n        beginScene(camera, data.defaultQuadShaderProgram)\n    }\n\n    fun beginScene(camera: OrthographicCamera, shaderProgram: ShaderProgram) =\n        trace(\"Renderer2D#beginScene\") {\n            require(isDrawingScene.not()) { \"Please complete the current scene before starting a new one.\" }\n            isDrawingScene = true\n            data.cameraData.viewProjection = camera.viewProjectionMatrix\n\n            if (shaderProgram != data.quadShaderProgram) {\n                data.quadShaderProgram = shaderProgram\n            }\n            val mat4 = data.cameraData.viewProjection\n\n            trace(\"viewProjection\") {\n                data.defaultQuadShaderProgram.shader.bind(data.shaderBinder)\n                data.defaultQuadShaderProgram.shader.setMat4(\"u_ViewProjection\", mat4)\n\n                data.externalQuadShaderProgram.shader.bind(data.shaderBinder)\n                data.externalQuadShaderProgram.shader.setMat4(\"u_ViewProjection\", mat4)\n\n                data.quadShaderProgram.shader.bind(data.shaderBinder)\n                data.quadShaderProgram.shader.setMat4(\"u_ViewProjection\", mat4)\n\n            }\n\n            data.quadIndexCount = 0\n            data.quadVertexBufferBase.clear()\n\n            data.textureSlotIndex = 1\n            data.textureSlots.fill(null, 1)\n        }\n\n    fun endScene() = trace(\"Renderer2D#endScene\") {\n        data.quadVertexBuffer.setData(\n            data.quadShaderProgram.mapVertexData(data.quadVertexBufferBase)\n        )\n        flush()\n        isDrawingScene = false\n    }\n\n    private fun flush() = trace(\"flush\") {\n        if (data.quadIndexCount > 0) {\n            // Bind textures\n            for (i in 0 until data.textureSlotIndex) {\n                data.textureSlots[i]?.bind(slot = i)\n            }\n            val isCustomShader = data.quadShaderProgram != data.defaultQuadShaderProgram &&\n                    data.quadShaderProgram != data.externalQuadShaderProgram\n            when {\n                isCustomShader -> {\n                    data.quadShaderProgram.shader.bind(data.shaderBinder)\n                }\n\n                else -> {\n                    if (data.quadVertexBufferBase.any { it.isExternalTexture > 0f }) {\n                        data.externalQuadShaderProgram.shader.bind(data.shaderBinder)\n                    } else {\n                        data.defaultQuadShaderProgram.shader.bind(data.shaderBinder)\n                    }\n                }\n            }\n            RenderCommand.drawIndexed(data.quadVertexArray, data.quadIndexCount)\n            data.stats.drawCalls++\n        }\n    }\n\n    fun drawFullQuadStatic(texture: Texture, alpha: Float) = trace(\"drawFullQuadStatic\") {\n        if (data.setStaticQuadData.not()) {\n            submitQuad(\n                transform = matId,\n                textureCoords = if (texture is SubTexture2D) texture.texCoords else defaultTextureCoords,\n                alpha = alpha,\n                mask = 0f\n            )\n            data.quadVertexBufferStatic = VertexBuffer.create(\n                vertices = data.quadShaderProgram.mapVertexData(data.quadVertexBufferBase)\n            )\n            data.quadVertexArray.addVertexBuffer(data.quadVertexBufferStatic!!)\n            data.setStaticQuadData = true\n            data.quadShaderProgram.shader.bind(data.shaderBinder)\n            data.quadShaderProgram.shader.setFloat(\"staticRenderer\", 1.0f)\n        }\n        flush()\n        isDrawingScene = false\n    }\n\n    fun drawQuad(\n        position: Float3,\n        size: Float2,\n        rotated: Float3 = zero3,\n        cameraDistance: Float = 3f,\n        texture2D: Texture2D? = null,\n        alpha: Float = 1.0f,\n        withMask: Boolean = false\n    ) = trace(\"drawQuad\") {\n        if (data.quadIndexCount >= MAX_INDICES) {\n            flushAndReset()\n        }\n        val transform: Mat4\n        if (rotated != zero3) {\n            val depth = cameraDistance * 72f\n            var cameraDepth = matId\n            if (rotated.x != 0f || rotated.y != 0f) { // faking perspective mapping\n                cameraDepth = Mat4.identity()\n                cameraDepth.set(row = 2, column = 3, v = -1f / depth)\n                cameraDepth.set(row = 2, column = 2, v = 0f)\n                cameraDepth = transpose(cameraDepth)\n            }\n\n            val rotX = if (rotated.x != 0.0f) rotation(X_AXIS, rotated.x) else matId\n            val rotY = if (rotated.y != 0.0f) rotation(Y_AXIS, rotated.y) else matId\n            val rotZ = if (rotated.z != 0.0f) rotation(Z_AXIS, rotated.z) else matId\n\n            transform = translation(position) *\n                    cameraDepth *\n                    rotX * rotY * rotZ *\n                    scale(Float3(size, 1.0f))\n        } else {\n            transform = translation(position) * scale(Float3(size, 1.0f))\n        }\n\n        var texIndex = 0.0f\n        var flipTexture = false\n        var isExternalTexture = false\n        if (texture2D != null) {\n            flipTexture = texture2D.flipTexture\n            isExternalTexture = texture2D.target == Texture.Target.TEXTURE_EXTERNAL_OES\n            var textureIndex = findTextureSlotIndexFor(texture2D)\n            if (textureIndex == -1) {\n                textureIndex = data.textureSlotIndex++\n            } else {\n                data.textureSlotIndex = textureIndex + 1\n            }\n            data.textureSlots[textureIndex] = texture2D\n            texIndex = textureIndex.toFloat()\n        }\n\n        submitQuad(\n            transform = transform,\n            texIndex = texIndex,\n            flipTexture = if (flipTexture) 1.0f else 0.0f,\n            isExternalTexture = if (isExternalTexture) 1.0f else 0.0f,\n            alpha = alpha,\n            mask = if (withMask) 1.0f else 0.0f\n        )\n    }\n\n    fun drawQuad(\n        position: Float3,\n        size: Float2,\n        texture: Texture,\n        alpha: Float = 1.0f,\n        withMask: Boolean = false\n    ) = trace(\"drawQuad[${size.x.toInt()} x ${size.y.toInt()}]\") {\n        when (texture) {\n            is Texture2D -> drawQuad(\n                position = position,\n                size = size,\n                texture2D = texture,\n                alpha = alpha,\n                withMask = withMask\n            )\n\n            is SubTexture2D -> drawQuad(\n                position = position,\n                size = size,\n                subTexture = texture,\n                alpha = alpha,\n                withMask = withMask\n            )\n        }\n        Unit\n    }\n\n    fun drawQuad(\n        position: Float3,\n        size: Float2,\n        subTexture: SubTexture2D,\n        alpha: Float = 1.0f,\n        withMask: Boolean = false\n    ) {\n        if (data.quadIndexCount >= MAX_INDICES) {\n            flushAndReset()\n        }\n\n        var textureIndex = findTextureSlotIndexFor(subTexture.texture)\n        if (textureIndex == -1) {\n            textureIndex = data.textureSlotIndex\n            data.textureSlots[textureIndex] = subTexture.texture\n            data.textureSlotIndex++\n        } else {\n            data.textureSlotIndex = textureIndex + 1\n        }\n        val isExternalTexture = subTexture.texture.target == Texture.Target.TEXTURE_EXTERNAL_OES\n        submitQuad(\n            transform = translation(position) * scale(Float3(size, 1.0f)),\n            texIndex = textureIndex.toFloat(),\n            textureCoords = subTexture.texCoords,\n            flipTexture = if (subTexture.flipTexture) 1.0f else 0.0f,\n            isExternalTexture = if (isExternalTexture) 1.0f else 0.0f,\n            alpha = alpha,\n            mask = if (withMask) 1.0f else 0.0f\n        )\n    }\n\n    fun resetRenderStats() {\n        data.stats.reset()\n    }\n\n    fun renderStats(): RenderStatistics {\n        return if (_data != null) data.stats else RenderStatistics()\n    }\n\n    private fun flushAndReset() {\n        endScene()\n\n        data.quadIndexCount = 0\n        data.quadVertexBufferBase.clear()\n        data.textureSlotIndex = 1\n        data.textureSlots.fill(null, 1)\n        data.textureSlots[WHITE_TEXTURE_SLOT_INDEX] = data.whiteTexture\n\n    }\n\n    private fun findTextureSlotIndexFor(texture: Texture2D): Int {\n        var textureIndex = -1\n        for (i in 1..data.textureSlotIndex) {\n            if (data.textureSlots[i] == texture) {\n                textureIndex = i\n            }\n        }\n        return textureIndex\n    }\n\n\n    private fun submitQuad(\n        transform: Mat4,\n        textureCoords: Array<Offset> = defaultTextureCoords,\n        texIndex: Float = 0.0f, // 0 = white texture\n        flipTexture: Float = 1.0f,\n        isExternalTexture: Float = 0.0f,\n        alpha: Float = 1.0f,\n        mask: Float\n    ) = trace(\"submitQuad\") {\n\n        for (i in 0 until 4) {\n            data.quadVertexBufferBase += QuadVertex(\n                position = (transform * data.defaultQuadVertexPositions[i]),\n                texCoord = textureCoords[i],\n                texIndex = texIndex,\n                flipTexture = flipTexture,\n                isExternalTexture = isExternalTexture,\n                alpha = alpha,\n                mask = mask,\n                maskCoord = defaultTextureCoords[i]\n            )\n        }\n\n        data.quadIndexCount += 6\n        data.stats.quadCount++\n    }\n\n}\n\n\ninternal data class CameraData(var viewProjection: Mat4)\n\n@Suppress(\"ArrayInDataClass\")\ninternal data class Renderer2DData(\n    val cameraData: CameraData,\n    var whiteTexture: Texture2D,\n    var setStaticQuadData: Boolean,\n\n    val defaultQuadShaderProgram: ShaderProgram,\n    val externalQuadShaderProgram: ShaderProgram,\n\n    var quadVertexArray: VertexArray,\n    var quadVertexBuffer: VertexBuffer,\n    var quadShaderProgram: ShaderProgram,\n    var quadIndexCount: Int = 0,\n    val quadVertexBufferBase: MutableList<QuadVertex> = ArrayList(MAX_VERTICES),\n    val textureSlots: Array<Texture2D?> = Array(MAX_TEXTURE_SLOTS) { null },\n\n    var quadVertexBufferStatic: VertexBuffer?,\n\n    var textureSlotIndex: Int = 1, // 0 = white texture slot\n    val defaultQuadVertexPositions: Array<Float4> = Array(4) { Float4(0.0f) },\n    val stats: RenderStatistics = RenderStatistics(),\n    val shaderBinder: ShaderBinder,\n) {\n}\n\npublic data class RenderStatistics(\n    var drawCalls: Int = 0,\n    var quadCount: Int = 0,\n    var lineCount: Int = 0,\n    var frameTime: Float = 0f,\n) {\n    val vertexCount: Int get() = (quadCount * 4) + (lineCount * 4)\n    val indexCount: Int get() = quadCount * 6 + (lineCount * 6)\n\n    public fun reset() {\n        drawCalls = 0\n        quadCount = 0\n        lineCount = 0\n        frameTime = 0f\n    }\n}\n\n// Texture coordinates\nprivate val bottomLeft = Offset(0.0f, 0.0f)\nprivate val bottomRight = Offset(1.0f, 0.0f)\nprivate val topRight = Offset(1.0f, 1.0f)\nprivate val topLeft = Offset(0.0f, 1.0f)\nprivate val defaultTextureCoords = arrayOf(bottomLeft, bottomRight, topRight, topLeft) // CCW\n\n//private val defaultTextureCoords = arrayOf(\n//    Offset(0.0f, 0.0f), // Bottom Left\n//    Offset(1.0f, 0.0f), // Bottom Right\n//    Offset(0.0f, 1.0f), // Top Left\n//    Offset(1.0f, 1.0f)  // Top Right\n//)\n\nprivate val whiteColor = Float4(1.0f)\nprivate val zero3 = Float3(0.0f)\nprivate val zero4 = Float4(0.0f)\nprivate val X_AXIS = Float3(1.0f, 0.0f, 0.0f)\nprivate val Y_AXIS = Float3(0.0f, 1.0f, 0.0f)\nprivate val Z_AXIS = Float3(0.0f, 0.0f, 1.0f)\nprivate val matId = Mat4.identity()\n\nprivate const val WHITE_TEXTURE_SLOT_INDEX = 0\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/RendererApi.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport androidx.compose.ui.graphics.Color\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\n\ninternal interface RendererApi {\n    val colorBufferBit: Int\n    val linearTextureFilter: Int\n\n    enum class Api { None, OpenGL }\n\n    fun init()\n    fun setClearColor(color: Color)\n    fun clear()\n    fun drawIndexed(vertexArray: VertexArray, indexCount: Int = 0)\n    fun setViewPort(x: Int, y: Int, width: Int, height: Int)\n    fun disableDepthTest()\n    fun colorMask(red: Boolean, green: Boolean, blue: Boolean, alpha: Boolean)\n    fun enableBlending()\n    fun disableBlending()\n    fun bindDefaultFramebuffer(bind: Bind)\n    fun bindDefaultProgram()\n    fun blitFramebuffer(\n        srcX0: Int,\n        srcY0: Int,\n        srcX1: Int,\n        srcY1: Int,\n        dstX0: Int,\n        dstY0: Int,\n        dstX1: Int,\n        dstY1: Int,\n        mask: Int,\n        filter: Int,\n    )\n\n    companion object {\n        internal val api: Api = Api.OpenGL\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/SimpleRenderer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport androidx.tracing.trace\n\n\ninternal class SimpleRenderer {\n    internal val data: StaticRendererData\n        get() = _data ?: error(\"StaticRenderer not initialised!\")\n    private var _data: StaticRendererData? = null\n\n    fun init() {\n        // vec2 uv[4];         // x, y\n        // vec2 size;          // width, height\n        // float flipTexture;  //\n        // float alpha;        //\n        val texDataUboElements = 20\n        val textureDataUBO = UniformBuffer.create(\n            count = texDataUboElements,\n            bindingPoint = TEXTURE_DATA_UBO_BINDING_POINT\n        )\n        val rendererData = StaticRendererData(\n            textureDataUBO = textureDataUBO,\n            vao = VertexArray.create(),\n        )\n        rendererData.vao.bind()\n        rendererData.vao.indexBuffer = allocateIndexBuffer(indices = 6)\n        rendererData.vao.addVertexBuffer(allocateVertexBuffer())\n        _data = rendererData\n    }\n\n    private fun allocateVertexBuffer(): VertexBuffer {\n\n\n        return VertexBuffer.create(\n            // @formatter:off\n            floatArrayOf(\n                -1.0f, -1.0f,  // bottom left\n                 1.0f, -1.0f,  // bottom right\n                 1.0f,  1.0f,  // top right\n                -1.0f,  1.0f,  // top left\n            )\n            // @formatter:on\n        ).apply {\n            layout = BufferLayout {\n                addElement(\"aPosition\", ShaderDataType.Float2)\n            }\n        }\n    }\n\n    private fun allocateIndexBuffer(indices: Int = MAX_INDICES): IndexBuffer {\n        val quadIndices = IntArray(indices)\n        var offset = 0\n        // simple quad indices\n        for (i in quadIndices.indices step 6) {\n            quadIndices[i + 0] = offset + 0\n            quadIndices[i + 1] = offset + 1\n            quadIndices[i + 2] = offset + 2\n\n            quadIndices[i + 3] = offset + 2\n            quadIndices[i + 4] = offset + 3\n            quadIndices[i + 5] = offset + 0\n\n            offset += 4\n        }\n        return IndexBuffer.create(quadIndices)\n    }\n\n    fun flush() = trace(\"SimpleRenderer#flush\") {\n        RenderCommand.drawIndexed(data.vao, 6)\n    }\n\n    companion object {\n        const val TEXTURE_DATA_UBO_BLOCK = \"TextureDataUBO\"\n        const val TEXTURE_DATA_UBO_BINDING_POINT = 0\n    }\n}\n\ninternal class StaticRendererData(\n    val textureDataUBO: UniformBuffer,\n    val vao: VertexArray,\n)"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/SubTexture2D.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.toIntSize\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\n\ninternal class SubTexture2D(\n    val texture: Texture2D\n) : Texture by texture {\n    val texCoords: Array<Offset> = Array(4) { index ->\n        SimpleQuadRenderer.defaultTextureCoords[index]\n    }\n\n    var subTextureSize: IntSize = IntSize.Zero\n        private set\n\n    constructor(texture: Texture2D, min: Offset, max: Offset) : this(texture) {\n        texCoords[0] = min\n        texCoords[1] = Offset(max.x, min.y)\n        texCoords[2] = Offset(max.x, max.y)\n        texCoords[3] = Offset(min.x, max.y)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as SubTexture2D\n\n        if (texture != other.texture) return false\n        if (!texCoords.contentEquals(other.texCoords)) return false\n        if (subTextureSize != other.subTextureSize) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = texture.hashCode()\n        result = 31 * result + texCoords.contentHashCode()\n        result = 31 * result + subTextureSize.hashCode()\n        return result\n    }\n\n    companion object {\n        fun createFromCoords(\n            texture: Texture2D,\n            rect: Rect\n        ): SubTexture2D {\n            val texLeft: Float = rect.left / texture.width\n            val texRight: Float = (rect.right) / texture.width\n            val texTop: Float = 1.0f - (rect.top / texture.height)\n            val texBottom: Float = 1.0f - ((rect.bottom) / texture.height)\n\n            val min = Offset(x = texLeft, y = texTop)\n            val max = Offset(x = texRight, y = texBottom)\n\n            return SubTexture2D(texture = texture, min = min, max = max).apply {\n                subTextureSize = rect.size.toIntSize()\n            }\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/Texture.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport androidx.compose.ui.unit.IntSize\nimport dev.serhiiyaremych.imla.renderer.opengl.OpenGLTexture2D\nimport java.nio.Buffer\n\ninternal interface Texture {\n    enum class Target {\n        TEXTURE_2D,\n        TEXTURE_EXTERNAL_OES,\n        //TEXTURE_2D_ARRAY // do I need it?\n    }\n\n    enum class ImageFormat {\n        None,\n        A8,\n        R8,\n        R16F,\n        RGB8,\n        RGBA8,\n        RGB10_A2,\n        DEPTH24STENCIL8\n    }\n\n    data class Specification(\n        val size: IntSize = IntSize(1, 1),\n        val format: ImageFormat = ImageFormat.RGBA8,\n        val generateMips: Boolean = false,\n        var flipTexture: Boolean = false,\n        val mipmapFiltering: Boolean = false\n    )\n\n\n    val id: Int\n    val target: Target\n    val width: Int\n    val height: Int\n    val flipTexture: Boolean\n    val specification: Specification\n\n    fun bind(slot: Int = 0)\n    fun setData(data: Buffer)\n    fun isLoaded(): Boolean\n    fun destroy()\n}\n\ninternal abstract class Texture2D : Texture {\n\n    companion object {\n        fun create(target: Texture.Target, specification: Texture.Specification): Texture2D {\n            return OpenGLTexture2D(target, specification)\n        }\n\n        fun create(\n            target: Texture.Target,\n            textureId: Int,\n            specification: Texture.Specification\n        ): Texture2D {\n            return OpenGLTexture2D(textureId, target, specification)\n        }\n    }\n\n    abstract fun generateMipMaps()\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n        other as Texture2D\n        return id == other.id\n    }\n\n    override fun hashCode(): Int {\n        return id.hashCode()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/VertexArray.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer\n\nimport dev.serhiiyaremych.imla.renderer.opengl.OpenGLVertexArray\n\n\ninternal interface VertexArray {\n\n    fun bind()\n    fun unbind()\n    fun destroy()\n\n    fun addVertexBuffer(vertexBuffer: VertexBuffer)\n    var indexBuffer: IndexBuffer?\n\n    companion object {\n        fun create(): VertexArray {\n            return OpenGLVertexArray()\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/camera/OrthographicCamera.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.camera\n\nimport dev.romainguy.kotlin.math.Float3\nimport dev.romainguy.kotlin.math.Mat4\nimport dev.romainguy.kotlin.math.inverse\nimport dev.romainguy.kotlin.math.ortho\nimport dev.romainguy.kotlin.math.rotation\nimport dev.romainguy.kotlin.math.translation\n\ninternal class OrthographicCamera(\n    left: Float, right: Float, bottom: Float, top: Float\n) {\n    var position: Float3 = Float3(0.0f)\n        set(value) {\n            field = value\n            recalculateViewMatrix()\n        }\n\n    var rotation: Float = 0.0f\n        set(value) {\n            field = value\n            recalculateViewMatrix()\n        }\n\n    var projectionMatrix: Mat4 = Mat4.identity()\n        private set\n    var viewMatrix: Mat4 = Mat4.identity()\n        private set\n    var viewProjectionMatrix: Mat4 = Mat4.identity()\n        private set\n\n    init {\n        projectionMatrix = ortho(left, right, bottom, top, -1.0f, 1.0f)\n        viewProjectionMatrix = projectionMatrix * viewMatrix\n    }\n\n    fun setProjection(left: Float, right: Float, bottom: Float, top: Float) {\n        projectionMatrix = ortho(left, right, bottom, top, -1.0f, 1.0f)\n        viewProjectionMatrix = projectionMatrix * viewMatrix\n    }\n\n    private fun recalculateViewMatrix() {\n        val transform = translation(position) * rotation(Float3(0.0f, 0.0f, 1.0f), rotation)\n        viewMatrix = inverse(transform)\n        viewProjectionMatrix = projectionMatrix * viewMatrix\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/camera/OrthographicCameraController.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer.camera\n\ninternal class OrthographicCameraController {\n    var camera: OrthographicCamera\n        private set\n    var aspectRatio: Float = 1.0f\n        private set\n\n    var zoomLevel: Float = 1.0f\n\n    private var viewPortWidth: Int = 0\n    private var viewPortHeight: Int = 0\n    private var pixelCoordinates: Boolean = false\n    val orthographicSize: Float\n        get() = viewPortHeight / 2f / zoomLevel\n\n    private constructor(aspectRatio: Float, height: Int) {\n        this.aspectRatio = aspectRatio\n        this.viewPortHeight = height\n\n        camera = OrthographicCamera(\n            left = -aspectRatio * orthographicSize,\n            right = aspectRatio * orthographicSize,\n            bottom = -orthographicSize,\n            top = orthographicSize\n        )\n        onVisibleBoundsResize(width = 0, height = height)\n    }\n\n    private constructor(width: Int, height: Int) {\n        pixelCoordinates = true\n        camera = OrthographicCamera(\n            left = 0f,\n            right = width.toFloat(),\n            bottom = 0f,\n            top = height.toFloat()\n        )\n        onVisibleBoundsResize(width, height)\n    }\n\n    fun onVisibleBoundsResize(width: Int, height: Int) {\n        if (width != viewPortWidth || height != viewPortHeight) {\n            viewPortWidth = width\n            viewPortHeight = height\n            if (width != 0 && height != 0) {\n                aspectRatio = width / height.toFloat()\n            }\n            updateCameraProjection()\n        }\n    }\n\n    fun updateCameraProjection() {\n        if (pixelCoordinates) {\n            camera.setProjection(\n                left = 0f,\n                right = viewPortWidth.toFloat(),\n                bottom = 0f,\n                top = viewPortHeight.toFloat()\n            )\n        } else {\n            camera.setProjection(\n                left = -aspectRatio * orthographicSize,\n                right = aspectRatio * orthographicSize,\n                bottom = -orthographicSize,\n                top = orthographicSize\n            )\n        }\n    }\n\n    companion object {\n        fun createPixelUnitsController(\n            viewportWidth: Int,\n            viewportHeight: Int\n        ) = OrthographicCameraController(viewportWidth, viewportHeight)\n\n        fun createWorldUnitsController(\n            viewportWidth: Int,\n            viewportHeight: Int\n        ) = OrthographicCameraController(viewportWidth / viewportHeight.toFloat(), viewportHeight)\n    }\n\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/framebuffer/BumpAllocatorPool.kt",
    "content": "package dev.serhiiyaremych.imla.renderer.framebuffer\n\ninternal class BumpAllocatorPool<T> {\n    val items = ArrayList<T>()\n    var length: Int = 0\n\n    inline fun acquire(factory: () -> T): T {\n        if (length >= items.size) {\n            items.add(factory())\n        }\n        return items[length++]\n    }\n\n    fun resetPool() {\n        length = 0\n    }\n\n    fun onEach(onEach: (T) -> Unit) {\n        items.forEach(onEach)\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/framebuffer/FrameBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer.framebuffer\n\nimport androidx.compose.ui.unit.IntSize\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.opengl.buffer.OpenGLFramebuffer\n\ninternal enum class Bind {\n    READ, DRAW, BOTH\n}\n\ninternal enum class FramebufferTextureFormat {\n    // bw masks, noise\n    R8,\n\n    // Color\n    RGBA8,\n    RGB10_A2,\n\n    // Depth/stencil\n    DEPTH24STENCIL8,\n}\n\ninternal data class FramebufferTextureSpecification(\n    val format: FramebufferTextureFormat = FramebufferTextureFormat.RGBA8,\n    val flip: Boolean = false,\n    val mipmapFiltering: Boolean = false\n)\n\ninternal data class FramebufferAttachmentSpecification(\n    val attachments: List<FramebufferTextureSpecification> = listOf(\n        FramebufferTextureSpecification(format = FramebufferTextureFormat.RGBA8),\n//        FramebufferTextureSpecification(format = FramebufferTextureFormat.RGB10_A2),\n    )\n) {\n    companion object {\n        fun singleColor(\n            format: FramebufferTextureFormat = FramebufferTextureFormat.RGBA8,\n            mipmapFiltering: Boolean = false,\n            flip: Boolean = false\n        ): FramebufferAttachmentSpecification {\n            return FramebufferAttachmentSpecification(\n                attachments = listOf(\n                    FramebufferTextureSpecification(\n                        format = format,\n                        mipmapFiltering = mipmapFiltering,\n                        flip = flip\n                    )\n                )\n            )\n        }\n    }\n}\n\ninternal data class FramebufferSpecification(\n    val size: IntSize,\n    val attachmentsSpec: FramebufferAttachmentSpecification,\n    val downSampleFactor: Int = 1,\n)\n\ninternal interface Framebuffer {\n    val rendererId: Int\n    val specification: FramebufferSpecification\n    val colorAttachmentTexture: Texture2D\n\n    fun invalidate()\n    fun bind(bind: Bind = Bind.BOTH, updateViewport: Boolean = true)\n    fun unbind()\n    fun resize(width: Int, height: Int)\n\n    fun invalidateAttachments()\n    fun clearAttachment(attachmentIndex: Int, value: Int)\n    fun getColorAttachmentRendererID(index: Int = 0): Int\n\n    fun destroy()\n    fun setColorAttachmentAt(attachmentIndex: Int)\n\n    companion object {\n        fun create(spec: FramebufferSpecification): Framebuffer {\n            return OpenGLFramebuffer(spec)\n        }\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/framebuffer/FramebufferPool.kt",
    "content": "package dev.serhiiyaremych.imla.renderer.framebuffer\n\nimport androidx.compose.ui.graphics.Color\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\n\ninternal class FramebufferPool {\n    private val frameBuffers = mutableMapOf<FramebufferSpecification, BumpAllocatorPool<Framebuffer>>()\n\n    fun acquire(spec: FramebufferSpecification): Framebuffer {\n        val existing = frameBuffers.getOrPut(spec) { BumpAllocatorPool() }\n        return existing.acquire { Framebuffer.create(spec) }\n    }\n\n    fun resetPool() {\n        frameBuffers.values.forEach { it.resetPool() }\n    }\n\n    fun eraseAll() {\n        frameBuffers.values.forEach {\n            it.onEach {\n                it.bind(updateViewport = false)\n                RenderCommand.clear(Color.Transparent)\n                it.unbind()\n            }\n        }\n    }\n\n    fun clear() {\n        frameBuffers.values.forEach { it.onEach { it.destroy() } }\n        frameBuffers.clear()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/objects/QuadShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.objects\n\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.BufferLayout\nimport dev.serhiiyaremych.imla.renderer.MAX_TEXTURE_SLOTS\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.ShaderDataType\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderProgram\nimport dev.serhiiyaremych.imla.renderer.addElement\nimport dev.serhiiyaremych.imla.renderer.primitive.QuadVertex\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\n\ninternal val defaultQuadBufferLayout = BufferLayout {\n    addElement(\"a_TexCoord\", ShaderDataType.Float2)\n    addElement(\"a_Position\", ShaderDataType.Float4)\n    addElement(\"a_TexIndex\", ShaderDataType.Float)\n    addElement(\"a_FlipTexture\", ShaderDataType.Float)\n    addElement(\"a_IsExternalTexture\", ShaderDataType.Float)\n    addElement(\"a_Alpha\", ShaderDataType.Float)\n    addElement(\"a_Mask\", ShaderDataType.Float)\n    addElement(\"a_MaskCoord\", ShaderDataType.Float2)\n}\n\ninternal fun defaultQuadVertexMapper(\n    quadVertexBufferBase: List<QuadVertex>\n): FloatArray = trace(\"defaultQuadVertexMapper\") {\n    // TODO: Use Bytebuffer to ensure vertex data is aligned\n    val verticesData =\n        FloatArray(quadVertexBufferBase.count() * QuadVertex.NUMBER_OF_COMPONENTS)\n\n    var lastVertexIndex = 0\n    for (quad in quadVertexBufferBase) {\n        // a_TexCoord\n        verticesData[lastVertexIndex + 0] = quad.texCoord.x\n        verticesData[lastVertexIndex + 1] = quad.texCoord.y\n        // a_Position\n        verticesData[lastVertexIndex + 2] = quad.position.x\n        verticesData[lastVertexIndex + 3] = quad.position.y\n        verticesData[lastVertexIndex + 4] = quad.position.z\n        verticesData[lastVertexIndex + 5] = quad.position.w\n        // a_TexIndex\n        verticesData[lastVertexIndex + 6] = quad.texIndex\n        // a_FlipTexture\n        verticesData[lastVertexIndex + 7] = quad.flipTexture\n        // a_IsExternalTexture\n        verticesData[lastVertexIndex + 8] = quad.isExternalTexture\n        // a_Alpha\n        verticesData[lastVertexIndex + 9] = quad.alpha\n        // a_Mask\n        verticesData[lastVertexIndex + 10] = quad.mask\n        // a_MaskCoord\n        verticesData[lastVertexIndex + 11] = quad.maskCoord.x\n        verticesData[lastVertexIndex + 12] = quad.maskCoord.y\n\n        lastVertexIndex += QuadVertex.NUMBER_OF_COMPONENTS\n    }\n    return verticesData\n}\n\n\ninternal class QuadShaderProgram(\n    shaderBinder: ShaderBinder,\n    override val shader: Shader,\n    textureSlots: Int = MAX_TEXTURE_SLOTS\n) : ShaderProgram {\n    override val vertexBufferLayout: BufferLayout = defaultQuadBufferLayout\n    override val componentsCount: Int = vertexBufferLayout.elements.sumOf { it.type.components }\n\n    init {\n        shader.bind(shaderBinder)\n        val samplers = IntArray(textureSlots) { index -> index }\n        shader.setIntArray(\"u_Textures\", samplers)\n    }\n\n    override fun mapVertexData(quadVertexBufferBase: List<QuadVertex>): FloatArray {\n        return defaultQuadVertexMapper(quadVertexBufferBase)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as QuadShaderProgram\n\n        return shader == other.shader\n    }\n\n    override fun hashCode(): Int {\n        return shader.hashCode()\n    }\n\n    override fun destroy() {\n        shader.destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/OpenGLRendererApi.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl\n\nimport android.opengl.GLES30\nimport android.util.Log\nimport androidx.compose.ui.graphics.Color\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.RendererApi\nimport dev.serhiiyaremych.imla.renderer.VertexArray\nimport dev.serhiiyaremych.imla.renderer.opengl.buffer.toGlTarget\n\ninternal class OpenGLRendererAPI : RendererApi {\n\n    override val colorBufferBit: Int = GLES30.GL_COLOR_BUFFER_BIT\n    override val linearTextureFilter: Int = GLES30.GL_LINEAR\n\n    override fun init() {\n        // LOG\n        Log.d(TAG, \"vendor: \" + GLES30.glGetString(GLES30.GL_VENDOR))\n        Log.d(TAG, \"renderer: \" + GLES30.glGetString(GLES30.GL_RENDERER))\n        Log.d(TAG, \"version: \" + GLES30.glGetString(GLES30.GL_VERSION))\n        checkGlError(GLES30.glBlendFunc(GLES30.GL_SRC_ALPHA, GLES30.GL_ONE_MINUS_SRC_ALPHA))\n        checkGlError(GLES30.glDisable(GLES30.GL_DEPTH_TEST))\n        checkGlError(GLES30.glDisable(GLES30.GL_STENCIL_TEST))\n        checkGlError(GLES30.glDisable(GLES30.GL_SCISSOR_TEST))\n        checkGlError(GLES30.glDisable(GLES30.GL_CULL_FACE))\n        setClearColor(Color.Transparent)\n    }\n\n    override fun setClearColor(color: Color) = trace(\"setClearColor\") {\n        checkGlError(GLES30.glClearColor(color.red, color.green, color.blue, color.alpha))\n    }\n\n    override fun clear() = trace(\"glClear\") {\n        checkGlError(GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT or GLES30.GL_STENCIL_BUFFER_BIT))\n    }\n\n    // @formatter:off\n    override fun drawIndexed(vertexArray: VertexArray, indexCount: Int) = trace(\"drawIndexed\") {\n        vertexArray.bind()\n        GLES30.glDrawElements(\n            /* mode = */ GLES30.GL_TRIANGLES,\n            /* count = */ if (indexCount == 0) requireNotNull(vertexArray.indexBuffer).elements else indexCount,\n            /* type = */ GLES30.GL_UNSIGNED_INT,\n            /* offset = */ 0\n        )\n    }\n    // @formatter:on\n\n    override fun setViewPort(x: Int, y: Int, width: Int, height: Int) = trace(\"glViewport\") {\n        checkGlError(GLES30.glViewport(x, y, width, height))\n    }\n\n    override fun disableDepthTest() {\n        GLES30.glDisable(GLES30.GL_DEPTH_TEST)\n    }\n\n    override fun colorMask(red: Boolean, green: Boolean, blue: Boolean, alpha: Boolean) {\n        // @formatter:off\n        GLES30.glColorMask(\n            /* red = */ red,\n            /* green = */ green,\n            /* blue = */ blue,\n            /* alpha = */ alpha\n        )\n        // @formatter:on\n    }\n\n    override fun enableBlending() = trace(\"enableBlending\") {\n        GLES30.glEnable(GLES30.GL_BLEND)\n    }\n\n    override fun disableBlending() = trace(\"disableBlending\") {\n        GLES30.glDisable(GLES30.GL_BLEND)\n    }\n\n    override fun bindDefaultFramebuffer(bind: Bind) = trace(\"bindDefaultFBO\") {\n        checkGlError(GLES30.glBindFramebuffer(bind.toGlTarget(), 0))\n    }\n\n    override fun bindDefaultProgram() = trace(\"useDefaultProgram\") {\n        checkGlError(GLES30.glUseProgram(0))\n    }\n\n    override fun blitFramebuffer(\n        srcX0: Int,\n        srcY0: Int,\n        srcX1: Int,\n        srcY1: Int,\n        dstX0: Int,\n        dstY0: Int,\n        dstX1: Int,\n        dstY1: Int,\n        mask: Int,\n        filter: Int\n    ) = trace(\"glBlitFramebuffer\") {\n        // @formatter:off\n        checkGlError(\n            GLES30.glBlitFramebuffer(\n                /* srcX0 = */  srcX0,\n                /* srcY0 = */  srcY0,\n                /* srcX1 = */  srcX1,\n                /* srcY1 = */  srcY1,\n                /* dstX0 = */  dstX0,\n                /* dstY0 = */  dstY0,\n                /* dstX1 = */  dstX1,\n                /* dstY1 = */  dstY1,\n                /* mask = */   mask,\n                /* filter = */ filter\n            )\n        )\n        // @formatter:on\n    }\n\n    companion object {\n        private const val TAG = \"OpenGLRendererAPI\"\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/OpenGLShader.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl\n\nimport android.opengl.GLES30\nimport android.util.Log\nimport androidx.collection.MutableObjectFloatMap\nimport androidx.collection.MutableObjectIntMap\nimport androidx.compose.ui.util.trace\nimport dev.romainguy.kotlin.math.Float2\nimport dev.romainguy.kotlin.math.Float3\nimport dev.romainguy.kotlin.math.Float4\nimport dev.romainguy.kotlin.math.Mat3\nimport dev.romainguy.kotlin.math.Mat4\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.stats.ShaderStats\nimport org.intellij.lang.annotations.Language\n\ninternal class OpenGLShader(\n    name: String,\n    @Language(\"GLSL\")\n    vertexSrc: String,\n    @Language(\"GLSL\")\n    fragmentSrc: String\n) : Shader {\n    private var _name: String = name\n    override val name: String get() = _name\n    private var rendererId: Int = 0\n\n    private val locationMap: MutableObjectIntMap<String> = MutableObjectIntMap()\n    private val intValueCache: MutableObjectIntMap<String> = MutableObjectIntMap()\n    private val intArrayValueCache: MutableMap<String, IntArray> = mutableMapOf()\n    private val floatValueCache: MutableObjectFloatMap<String> = MutableObjectFloatMap()\n    private val floatArrayValueCache: MutableMap<String, FloatArray> = mutableMapOf()\n    private var traceName = \"shaderBind\"\n\n    init {\n        compile(vertexSrc, fragmentSrc)\n        ShaderStats.shaderInstances++\n    }\n\n    @Deprecated(\"\")\n    override fun bind() {\n        trace(traceName) {\n            checkGlError(GLES30.glUseProgram(rendererId))\n        }\n        ShaderStats.shaderBinds++\n    }\n\n    override fun bind(shaderBinder: ShaderBinder) {\n        shaderBinder.bind(this)\n    }\n\n    override fun unbind() = trace(traceName) {\n        GLES30.glUseProgram(0)\n        intValueCache.clear()\n        intArrayValueCache.clear()\n        floatValueCache.clear()\n        floatArrayValueCache.clear()\n    }\n\n    override fun bindUniformBlock(blockName: String, bindingPoint: Int) {\n        trace(\"bindUniformBlock\") {\n            val blockIndex =\n                locationMap.getOrPut(blockName) {\n                    GLES30.glGetUniformBlockIndex(\n                        rendererId,\n                        blockName\n                    ).also { checkGlError() }\n                }\n            checkGlError(GLES30.glUniformBlockBinding(rendererId, blockIndex, bindingPoint))\n        }\n        ShaderStats.shaderBindUniformBlock++\n    }\n\n    override fun setInt(name: String, value: Int) = trace(\"setInt\") {\n        uploadUniformInt(name, value)\n    }\n\n    override fun setIntArray(name: String, values: IntArray) = trace(\"setIntArray\") {\n        uploadUniformIntArray(name, values)\n    }\n\n    override fun setFloatArray(name: String, values: FloatArray) = trace(\"setFloatArray\") {\n        uploadFloatArray(name, values)\n    }\n\n    override fun setFloat(name: String, value: Float) = trace(\"setFloat\") {\n        uploadUniformFloat(name, value)\n    }\n\n    override fun setFloat2(name: String, value: Float2) = trace(\"setFloat2\") {\n        uploadUniformFloat2(name, value)\n    }\n\n    override fun setFloat3(name: String, value: Float3) = trace(\"setFloat3\") {\n        uploadUniformFloat3(name, value)\n    }\n\n    override fun setFloat4(name: String, value: Float4) = trace(\"setFloat4\") {\n        uploadUniformFloat4(name, value)\n    }\n\n    override fun setMat3(name: String, value: Mat3) = trace(\"setMat3\") {\n        uploadUniformMat3(name, value)\n    }\n\n    override fun setMat4(name: String, value: Mat4) = trace(\"setMat4\") {\n        uploadUniformMat4(name, value)\n    }\n\n    private fun uploadUniformInt(name: String, value: Int) {\n        if (intValueCache.getOrDefault(name, Int.MIN_VALUE) != value) {\n            val location = uniformLocation(rendererId, name)\n            checkGlError(GLES30.glUniform1i(location, value))\n            ShaderStats.shaderUploads++\n            intValueCache[name] = value\n        }\n    }\n\n    private fun uploadUniformIntArray(name: String, values: IntArray) {\n        if (!values.contentEquals(intArrayValueCache[name])) {\n            val location = uniformLocation(rendererId, name)\n            checkGlError(GLES30.glUniform1iv(location, values.size, values, 0))\n            ShaderStats.shaderUploads++\n            intArrayValueCache[name] = values\n        }\n    }\n\n    private fun uploadFloatArray(name: String, values: FloatArray) {\n        if (!values.contentEquals(floatArrayValueCache[name])) {\n            val location = uniformLocation(rendererId, name)\n            checkGlError(GLES30.glUniform1fv(location, values.size, values, 0))\n            ShaderStats.shaderUploads++\n            floatArrayValueCache[name] = values\n        }\n    }\n\n    private fun uploadUniformFloat(name: String, value: Float) {\n        if (floatValueCache.getOrDefault(name, Float.MIN_VALUE) != value) {\n            val location = uniformLocation(rendererId, name)\n            checkGlError(GLES30.glUniform1f(location, value))\n            ShaderStats.shaderUploads++\n            floatValueCache[name] = value\n        }\n    }\n\n    private fun uploadUniformFloat2(name: String, value: Float2) {\n        val location = uniformLocation(rendererId, name)\n        checkGlError(GLES30.glUniform2f(location, value.x, value.y))\n        ShaderStats.shaderUploads++\n    }\n\n    private fun uploadUniformFloat3(name: String, value: Float3) {\n        val location = uniformLocation(rendererId, name)\n        checkGlError(GLES30.glUniform3f(location, value.x, value.y, value.z))\n        ShaderStats.shaderUploads++\n    }\n\n    private fun uploadUniformFloat4(name: String, value: Float4) {\n        val location = uniformLocation(rendererId, name)\n        checkGlError(GLES30.glUniform4f(location, value.x, value.y, value.z, value.w))\n        ShaderStats.shaderUploads++\n    }\n\n    private fun uploadUniformMat3(name: String, value: Mat3) {\n        val location = uniformLocation(rendererId, name)\n        checkGlError(GLES30.glUniformMatrix3fv(location, 1, true, value.toFloatArray(), 0))\n        ShaderStats.shaderUploads++\n    }\n\n    private fun uploadUniformMat4(name: String, value: Mat4) {\n        val location = uniformLocation(rendererId, name)\n        checkGlError(GLES30.glUniformMatrix4fv(location, 1, true, value.toFloatArray(), 0))\n        ShaderStats.shaderUploads++\n    }\n\n    override fun destroy() {\n        unbind()\n        GLES30.glDetachShader(rendererId, GLES30.GL_VERTEX_SHADER)\n        GLES30.glDetachShader(rendererId, GLES30.GL_FRAGMENT_SHADER)\n        GLES30.glDeleteProgram(rendererId)\n    }\n\n    private fun compile(vertexSrc: String, fragmentSrc: String) = trace(\"shaderCompile\") {\n        val vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER)\n        checkGlError(GLES30.glShaderSource(vertexShader, vertexSrc))\n        checkGlError(GLES30.glCompileShader(vertexShader))\n\n        val status = IntArray(1)\n        GLES30.glGetShaderiv(vertexShader, GLES30.GL_COMPILE_STATUS, status, 0)\n        if (status[0] == GLES30.GL_FALSE) {\n            val errorMessage = GLES30.glGetShaderInfoLog(vertexShader)\n            GLES30.glDeleteShader(vertexShader)\n            error(errorMessage)\n        }\n\n        val fragmentShader = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER)\n        checkGlError(GLES30.glShaderSource(fragmentShader, fragmentSrc))\n        checkGlError(GLES30.glCompileShader(fragmentShader))\n\n        GLES30.glGetShaderiv(fragmentShader, GLES30.GL_COMPILE_STATUS, status, 0)\n        if (status[0] == GLES30.GL_FALSE) {\n            val errorMessage = GLES30.glGetShaderInfoLog(fragmentShader)\n            GLES30.glDeleteShader(fragmentShader)\n            Log.e(TAG, errorMessage)\n            error(errorMessage)\n        }\n\n        rendererId = GLES30.glCreateProgram()\n        traceName = \"shaderBind[$rendererId]\"\n        val program = rendererId\n        checkGlError(GLES30.glAttachShader(program, vertexShader))\n        checkGlError(GLES30.glAttachShader(program, fragmentShader))\n\n        GLES30.glLinkProgram(program)\n\n        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, status, 0)\n        if (status[0] == GLES30.GL_FALSE) {\n            val errorMessage = GLES30.glGetProgramInfoLog(program)\n            GLES30.glDeleteProgram(program)\n            Log.e(TAG, errorMessage)\n            error(errorMessage)\n        }\n\n        checkGlError(GLES30.glDetachShader(program, vertexShader))\n        checkGlError(GLES30.glDetachShader(program, fragmentShader))\n    }\n\n    private fun uniformLocation(rendererId: Int, uniformName: String): Int {\n        return locationMap.getOrPut(uniformName) {\n            GLES30.glGetUniformLocation(rendererId, uniformName).also { checkGlError() }\n        }\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as OpenGLShader\n\n        return rendererId == other.rendererId\n    }\n\n    override fun hashCode(): Int {\n        return rendererId.hashCode()\n    }\n\n    override fun toString(): String {\n        return \"OpenGLShader('$_name:$rendererId')\"\n    }\n\n\n    private companion object {\n        private const val TAG = \"OpenGLShader\"\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/OpenGLTexture2D.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer.opengl\n\nimport android.opengl.GLES11Ext\nimport android.opengl.GLES30\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.ext.logd\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport java.nio.Buffer\nimport kotlin.math.ln\n\ninternal class OpenGLTexture2D : Texture2D {\n    override val target: Texture.Target\n    override val specification: Texture.Specification\n\n    override val width: Int get() = specification.size.width\n\n    override val height: Int get() = specification.size.height\n    override var id: Int = 0\n        private set\n    override val flipTexture: Boolean get() = specification.flipTexture\n    private var _isDataLoaded: Boolean = false\n\n    constructor(target: Texture.Target, specification: Texture.Specification) : super() {\n        this.target = target\n        this.specification = specification\n        val glTarget = target.toGlTextureTarget()\n        createGLTexture(glTarget)\n\n        if (target != Texture.Target.TEXTURE_EXTERNAL_OES) {\n            val maxSize: Int = width.coerceAtLeast(height)\n            val maxLevels =\n                if (specification.generateMips) (1 + (ln(maxSize.toDouble()) / ln(2.0)).toInt()).coerceAtMost(\n                    6\n                ) else 1\n            logd(\"OpenGLTexture2D\", \"create texture: $target, $specification, mipmaps $maxLevels\")\n            checkGlError(\n                GLES30.glTexStorage2D(\n                    /* target = */ GLES30.GL_TEXTURE_2D,\n                    /* levels = */ maxLevels,\n                    /* internalformat = */ specification.format.toGlInternalFormat(),\n                    /* width = */ width,\n                    /* height = */ height\n                )\n            )\n        }\n\n        checkGlError(\n            GLES30.glTexParameteri(\n                glTarget,\n                GLES30.GL_TEXTURE_WRAP_S,\n                GLES30.GL_CLAMP_TO_EDGE\n            )\n        )\n        checkGlError(\n            GLES30.glTexParameteri(\n                glTarget,\n                GLES30.GL_TEXTURE_WRAP_T,\n                GLES30.GL_CLAMP_TO_EDGE\n            )\n        )\n        if (target != Texture.Target.TEXTURE_EXTERNAL_OES && specification.mipmapFiltering) {\n            checkGlError(\n                GLES30.glTexParameteri(\n                    glTarget,\n                    GLES30.GL_TEXTURE_MIN_FILTER,\n                    GLES30.GL_LINEAR_MIPMAP_LINEAR\n                )\n            )\n        } else {\n            checkGlError(\n                GLES30.glTexParameteri(\n                    glTarget,\n                    GLES30.GL_TEXTURE_MIN_FILTER,\n                    GLES30.GL_LINEAR\n                )\n            )\n        }\n        checkGlError(\n            GLES30.glTexParameteri(\n                glTarget,\n                GLES30.GL_TEXTURE_MAG_FILTER,\n                GLES30.GL_LINEAR\n            )\n        )\n    }\n\n    constructor(\n        textureId: Int,\n        target: Texture.Target,\n        specification: Texture.Specification\n    ) : this(target, specification) {\n        this.id = textureId\n        _isDataLoaded = true\n    }\n\n    override fun generateMipMaps() = trace(\"glGenerateMipmap\") {\n        if (specification.generateMips && (specification.size.width > 1 || specification.size.height > 1)) {\n            checkGlError(GLES30.glGenerateMipmap(/* target = */ target.toGlTextureTarget()))\n        }\n    }\n\n    private fun createGLTexture(glTarget: Int) {\n        val ids = IntArray(1)\n        checkGlError(GLES30.glGenTextures(1, ids, 0))\n        id = ids[0]\n        checkGlError(GLES30.glBindTexture(glTarget, id))\n    }\n\n    override fun setData(data: Buffer) {\n        val glTextureTarget = target.toGlTextureTarget()\n        checkGlError(\n            GLES30.glTexSubImage2D(\n                /* target = */ glTextureTarget,\n                /* level = */ 0,\n                /* xoffset = */ 0,\n                /* yoffset = */ 0,\n                /* width = */ width,\n                /* height = */ height,\n                /* format = */ specification.format.toGlImageFormat(),\n                /* type = */ specification.format.getDataType(),\n                /* pixels = */ data\n            )\n        )\n        generateMipMaps()\n        _isDataLoaded = true\n    }\n\n    override fun bind(slot: Int) = trace(\"textureBind\") {\n        checkGlError(GLES30.glActiveTexture(GLES30.GL_TEXTURE0 + slot))\n        checkGlError(GLES30.glBindTexture(target.toGlTextureTarget(), id))\n    }\n\n    override fun destroy() {\n        GLES30.glDeleteTextures(1, intArrayOf(id), 0)\n    }\n\n    override fun isLoaded(): Boolean {\n        return _isDataLoaded\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n        if (!super.equals(other)) return false\n\n        other as OpenGLTexture2D\n\n        if (target != other.target) return false\n        if (specification != other.specification) return false\n        if (id != other.id) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = super.hashCode()\n        result = 31 * result + target.hashCode()\n        result = 31 * result + specification.hashCode()\n        result = 31 * result + id\n        return result\n    }\n\n    override fun toString(): String {\n        return \"OpenGLTexture2D(target=$target, specification=$specification, width=$width, height=$height, id=$id)\"\n    }\n\n    companion object {\n        private const val TAG = \"OpenGLTexture2D\"\n    }\n\n}\n\ninternal fun Texture.Target.toGlTextureTarget(): Int {\n    return when (this) {\n        Texture.Target.TEXTURE_2D -> GLES30.GL_TEXTURE_2D\n        Texture.Target.TEXTURE_EXTERNAL_OES -> GLES11Ext.GL_TEXTURE_EXTERNAL_OES\n    }\n}\n\ninternal fun Texture.ImageFormat.toGlInternalFormat(): Int {\n    return when (this) {\n        Texture.ImageFormat.None -> 0\n        Texture.ImageFormat.A8,\n        Texture.ImageFormat.R8 -> GLES30.GL_R8\n\n        Texture.ImageFormat.R16F -> GLES30.GL_R16F\n        Texture.ImageFormat.RGB8 -> GLES30.GL_RGB8\n        Texture.ImageFormat.RGBA8 -> GLES30.GL_SRGB8_ALPHA8\n        Texture.ImageFormat.RGB10_A2 -> GLES30.GL_RGB10_A2\n        Texture.ImageFormat.DEPTH24STENCIL8 -> GLES30.GL_DEPTH24_STENCIL8\n    }\n}\n\ninternal fun Texture.ImageFormat.getDataType(): Int {\n    // Use a when expression to return the corresponding OpenGL type constant\n    return when (this) {\n        Texture.ImageFormat.None -> 0 // No type\n        Texture.ImageFormat.A8 -> GLES30.GL_UNSIGNED_BYTE\n        Texture.ImageFormat.R8 -> GLES30.GL_UNSIGNED_BYTE\n        Texture.ImageFormat.R16F -> GLES30.GL_FLOAT\n        Texture.ImageFormat.RGB8 -> GLES30.GL_UNSIGNED_BYTE\n        Texture.ImageFormat.RGBA8 -> GLES30.GL_UNSIGNED_BYTE\n        Texture.ImageFormat.RGB10_A2 -> GLES30.GL_UNSIGNED_INT_2_10_10_10_REV\n        Texture.ImageFormat.DEPTH24STENCIL8 -> GLES30.GL_UNSIGNED_INT_24_8\n    }\n}\n\ninternal fun Texture.ImageFormat.toGlImageFormat(): Int {\n    return when (this) {\n        Texture.ImageFormat.None -> 0\n        Texture.ImageFormat.A8 -> GLES30.GL_ALPHA\n        Texture.ImageFormat.R8, Texture.ImageFormat.R16F -> GLES30.GL_RED\n        Texture.ImageFormat.RGB8 -> GLES30.GL_RGB\n        Texture.ImageFormat.RGBA8, Texture.ImageFormat.RGB10_A2 -> GLES30.GL_RGBA\n        Texture.ImageFormat.DEPTH24STENCIL8 -> GLES30.GL_DEPTH_STENCIL\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/OpenGLUniformBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl\n\nimport android.opengl.GLES30\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.UniformBuffer\nimport dev.serhiiyaremych.imla.renderer.toFloatBuffer\nimport java.nio.Buffer\n\ninternal class OpenGLUniformBuffer(\n    count: Int,\n    binding: Int\n) : UniformBuffer {\n\n    override val elements: Int = count\n\n    override val sizeBytes: Int\n        get() = elements * Float.SIZE_BYTES\n\n    private var rendererId: Int = 0\n    private var isBound: Boolean = false\n\n    init {\n        val ids = IntArray(1)\n        checkGlError(GLES30.glGenBuffers(1, ids, 0))\n        rendererId = ids[0]\n        bind()\n        checkGlError(\n            GLES30.glBufferData(\n                /* target = */ GLES30.GL_UNIFORM_BUFFER,\n                /* size = */ sizeBytes,\n                /* data = */ null,\n                /* usage = */ GLES30.GL_DYNAMIC_DRAW\n            )\n        )\n        checkGlError(\n            GLES30.glBindBufferBase(\n                /* target = */ GLES30.GL_UNIFORM_BUFFER,\n                /* index = */ binding,\n                /* buffer = */ rendererId\n            )\n        )\n    }\n\n    override fun setData(data: FloatArray) {\n        bind()\n        trace(\"uboSetData\") {\n            checkGlError(\n                GLES30.glBufferSubData(\n                    /* target = */ GLES30.GL_UNIFORM_BUFFER,\n                    /* offset = */ 0,\n                    /* size = */ data.size * Float.SIZE_BYTES,\n                    /* data = */ data.toFloatBuffer()\n                )\n            )\n        }\n    }\n\n    override fun setData(data: Buffer) {\n        bind()\n        trace(\"uboSetData\") {\n            checkGlError(\n                GLES30.glBufferSubData(\n                    /* target = */ GLES30.GL_UNIFORM_BUFFER,\n                    /* offset = */ 0,\n                    /* size = */ data.capacity() * Float.SIZE_BYTES,\n                    /* data = */ data\n                )\n            )\n        }\n    }\n\n    override fun bind() {\n        if (isBound.not()) {\n            trace(\"uboBind\") {\n                checkGlError(GLES30.glBindBuffer(GLES30.GL_UNIFORM_BUFFER, rendererId))\n            }\n            isBound = true\n        }\n    }\n\n    override fun unbind() {\n        GLES30.glBindBuffer(GLES30.GL_UNIFORM_BUFFER, 0)\n        isBound = false\n    }\n\n    override fun destroy() {\n        GLES30.glDeleteBuffers(1, intArrayOf(rendererId), 0)\n        isBound = false\n    }\n\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/OpenGLVertexArray.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl\n\nimport android.opengl.GLES30\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.IndexBuffer\nimport dev.serhiiyaremych.imla.renderer.ShaderDataType\nimport dev.serhiiyaremych.imla.renderer.VertexArray\nimport dev.serhiiyaremych.imla.renderer.VertexBuffer\n\ninternal class OpenGLVertexArray : VertexArray {\n    private var rendererId: Int = 0\n\n    private val _vertexBuffers: MutableList<VertexBuffer> = ArrayList()\n    val vertexBuffers: List<VertexBuffer> get() = _vertexBuffers\n\n    override var indexBuffer: IndexBuffer? = null\n        set(value) {\n            field = value\n            checkGlError(GLES30.glBindVertexArray(rendererId))\n            value?.bind()\n        }\n\n    init {\n        val ids = IntArray(1)\n        checkGlError(GLES30.glGenVertexArrays(1, ids, 0))\n        rendererId = ids[0]\n    }\n\n    override fun bind() = trace(\"vaoBind\") {\n        checkGlError(GLES30.glBindVertexArray(rendererId))\n    }\n\n    override fun unbind() {\n        GLES30.glBindVertexArray(0)\n    }\n\n    override fun destroy() {\n        GLES30.glDeleteVertexArrays(1, intArrayOf(rendererId), 0)\n    }\n\n    override fun addVertexBuffer(vertexBuffer: VertexBuffer) {\n        checkGlError(GLES30.glBindVertexArray(rendererId))\n        vertexBuffer.bind()\n        vertexBuffer.layout?.let { bufferLayout ->\n            bufferLayout.forEachIndexed { index, element ->\n                when (element.type) {\n                    ShaderDataType.None -> { /* no-op */\n                    }\n\n                    ShaderDataType.Float,\n                    ShaderDataType.Float2,\n                    ShaderDataType.Float3,\n                    ShaderDataType.Float4 -> {\n                        checkGlError(GLES30.glEnableVertexAttribArray(index))\n                        checkGlError(\n                            GLES30.glVertexAttribPointer(\n                                /* indx = */ index,\n                                /* size = */ element.type.components,\n                                /* type = */ element.type.openGLBaseType,\n                                /* normalized = */ element.normalized,\n                                /* stride = */ bufferLayout.stride,\n                                /* offset = */ element.offset\n                            )\n                        )\n                    }\n\n                    ShaderDataType.Int,\n                    ShaderDataType.Int2,\n                    ShaderDataType.Int3,\n                    ShaderDataType.Int4,\n                    ShaderDataType.Bool -> {\n                        checkGlError(GLES30.glEnableVertexAttribArray(index))\n                        checkGlError(\n                            GLES30.glVertexAttribIPointer(\n                                /* index = */ index,\n                                /* size = */ element.type.components,\n                                /* type = */ element.type.openGLBaseType,\n                                /* stride = */ bufferLayout.stride,\n                                /* offset = */ element.offset\n                            )\n                        )\n                    }\n\n                    ShaderDataType.Mat3,\n                    ShaderDataType.Mat4 -> {\n                        val count = element.type.components\n                        for (i in 0 until count) {\n                            checkGlError(GLES30.glEnableVertexAttribArray(index))\n                            checkGlError(\n                                GLES30.glVertexAttribPointer(\n                                    /* indx = */ index,\n                                    /* size = */ count,\n                                    /* type = */ element.type.openGLBaseType,\n                                    /* normalized = */ element.normalized,\n                                    /* stride = */ bufferLayout.stride,\n                                    /* offset = */ (element.offset + Float.SIZE_BYTES * count * i)\n                                )\n                            )\n                            checkGlError(GLES30.glVertexAttribDivisor(index, 1))\n                        }\n                    }\n                }\n\n            }\n        }\n\n        _vertexBuffers.add(vertexBuffer)\n    }\n\n    private val ShaderDataType.openGLBaseType: Int\n        get() = when (this) {\n            ShaderDataType.None -> error(\"Unconvertable shader data type: ${this.name}\")\n            ShaderDataType.Float -> GLES30.GL_FLOAT\n            ShaderDataType.Float2 -> GLES30.GL_FLOAT\n            ShaderDataType.Float3 -> GLES30.GL_FLOAT\n            ShaderDataType.Float4 -> GLES30.GL_FLOAT\n            ShaderDataType.Mat3 -> GLES30.GL_FLOAT\n            ShaderDataType.Mat4 -> GLES30.GL_FLOAT\n            ShaderDataType.Int -> GLES30.GL_INT\n            ShaderDataType.Int2 -> GLES30.GL_INT\n            ShaderDataType.Int3 -> GLES30.GL_INT\n            ShaderDataType.Int4 -> GLES30.GL_INT\n            ShaderDataType.Bool -> GLES30.GL_BOOL\n        }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/buffer/OpenGLFrameBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl.buffer\n\nimport android.opengl.GLES30\nimport android.util.Log\nimport androidx.compose.ui.unit.IntSize\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind.BOTH\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind.DRAW\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind.READ\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureSpecification\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.opengl.toGlTextureTarget\nimport dev.serhiiyaremych.imla.renderer.stats.ShaderStats\nimport dev.serhiiyaremych.imla.renderer.toIntBuffer\n\ninternal class OpenGLFramebuffer(\n    spec: FramebufferSpecification\n) : Framebuffer {\n    override var specification: FramebufferSpecification = spec\n        private set\n    override val colorAttachmentTexture: Texture2D\n        get() = _colorAttachmentTexture!!\n\n    override var rendererId: Int = 0\n        private set\n    private var depthAttachment: Int = 0\n\n    private val colorAttachmentSpecifications: MutableList<FramebufferTextureSpecification> =\n        mutableListOf()\n\n    private var _colorAttachmentTexture: Texture2D? = null\n\n    private var drawAttachments: IntArray = IntArray(0)\n    private val colorAttachments: MutableList<Texture2D> = mutableListOf()\n    private var depthAttachmentSpecification: FramebufferTextureSpecification? = null\n\n    private val sampledWidth get() = specification.size.width / specification.downSampleFactor\n    private val sampledHeight get() = specification.size.height / specification.downSampleFactor\n\n    init {\n        ShaderStats.fboInstances++\n        invalidate()\n    }\n\n    override fun invalidate() {\n        if (rendererId != 0) {\n            destroy()\n        }\n\n        val id = IntArray(1)\n        GLES30.glGenFramebuffers(1, id, 0)\n        rendererId = id[0]\n\n        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, rendererId)\n        val attachments = specification.attachmentsSpec.attachments\n\n        val colorAttachmentSpecs =\n            attachments.filter { it.format != FramebufferTextureFormat.DEPTH24STENCIL8 }\n        depthAttachmentSpecification =\n            attachments.find { it.format == FramebufferTextureFormat.DEPTH24STENCIL8 }\n\n        colorAttachmentSpecs.forEachIndexed { index, attachment ->\n            createAttachment(\n                width = sampledWidth,\n                height = sampledHeight,\n                format = attachment.format,\n                flip = attachment.flip,\n                mipmapFiltering = attachment.mipmapFiltering\n            ).apply {\n                colorAttachments.add(this)\n                GLES30.glFramebufferTexture2D(\n                    /* target = */ GLES30.GL_FRAMEBUFFER,\n                    /* attachment = */ GLES30.GL_COLOR_ATTACHMENT0 + index,\n                    /* textarget = */ this.target.toGlTextureTarget(),\n                    /* texture = */ this.id,\n                    /* level = */ 0\n                )\n            }\n        }\n\n        if (_colorAttachmentTexture?.id != colorAttachments.first().id) {\n            _colorAttachmentTexture?.destroy()\n            _colorAttachmentTexture = colorAttachments.first()\n        }\n\n        depthAttachmentSpecification?.let {\n            createAttachment(\n                width = sampledWidth,\n                height = sampledHeight,\n                format = FramebufferTextureFormat.DEPTH24STENCIL8,\n                flip = it.flip,\n                mipmapFiltering = false\n            ).apply {\n                depthAttachment = this.id\n                GLES30.glFramebufferTexture2D(\n                    /* target = */ GLES30.GL_FRAMEBUFFER,\n                    /* attachment = */ GLES30.GL_DEPTH_ATTACHMENT,\n                    /* textarget = */ GLES30.GL_TEXTURE_2D,\n                    /* texture = */ depthAttachment,\n                    /* level = */ 0\n                )\n            }\n        }\n\n        if (colorAttachmentSpecs.isNotEmpty()) {\n            val buffers: IntArray = IntArray(colorAttachmentSpecs.size) {\n                GLES30.GL_COLOR_ATTACHMENT0 + it\n            }\n            GLES30.glDrawBuffers(colorAttachmentSpecs.size, buffers, 0)\n            drawAttachments = buffers\n        } else {\n            // Only depth-pass\n            GLES30.glDrawBuffers(0, intArrayOf(), 0)\n        }\n\n        require(GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) == GLES30.GL_FRAMEBUFFER_COMPLETE) {\n            \"OpenGL20Framebuffer is incomplete!\"\n        }\n\n        // switchback to default framebuffer\n        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)\n    }\n\n    private fun readBuffer() {\n        GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0)\n    }\n\n    override fun bind(bind: Bind, updateViewport: Boolean) = trace(\"glBindFramebuffer[$bind]\") {\n        GLES30.glBindFramebuffer(bind.toGlTarget(), rendererId)\n        if (updateViewport) {\n            trace(\"glViewport\") {\n                GLES30.glViewport(0, 0, sampledWidth, sampledHeight)\n            }\n        }\n\n        if (bind == READ) {\n            readBuffer()\n        }\n    }\n\n    override fun unbind() = trace(\"glUnBindFramebuffer\") {\n        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)\n    }\n\n    override fun resize(width: Int, height: Int) {\n        if (width == 0 || height == 0 || width > MAX_FRAMEBUFFER_SIZE || height > MAX_FRAMEBUFFER_SIZE) {\n            Log.w(TAG, \"Attempt to resize framebuffer to $width, $height failed\")\n            return\n        }\n        if (specification.size.width != width || specification.size.height != height) {\n            specification = specification.copy(\n                size = IntSize(width, height)\n            )\n            invalidate()\n        }\n    }\n\n    override fun invalidateAttachments() = trace(\"invalidateAttachments\") {\n        checkGlError(\n            GLES30.glInvalidateFramebuffer(\n                GLES30.GL_FRAMEBUFFER,\n                drawAttachments.size,\n                drawAttachments,\n                0\n            )\n        )\n    }\n\n    override fun getColorAttachmentRendererID(index: Int): Int {\n        require(index <= colorAttachments.lastIndex)\n        return colorAttachments[index].id\n    }\n\n    override fun clearAttachment(attachmentIndex: Int, value: Int) {\n        val attachmentHandle = getColorAttachmentRendererID(attachmentIndex)\n        val spec = colorAttachmentSpecifications[attachmentIndex]\n        val textureFormat = spec.format\n        val type = when (textureFormat) {\n            FramebufferTextureFormat.RGBA8, FramebufferTextureFormat.R8 -> GLES30.GL_UNSIGNED_BYTE\n            FramebufferTextureFormat.RGB10_A2 -> GLES30.GL_UNSIGNED_INT_2_10_10_10_REV\n            FramebufferTextureFormat.DEPTH24STENCIL8 -> GLES30.GL_UNSIGNED_INT_24_8\n        }\n        val components = when (textureFormat) {\n            FramebufferTextureFormat.R8 -> 1\n            FramebufferTextureFormat.RGBA8 -> 4\n            FramebufferTextureFormat.RGB10_A2 -> 4\n            FramebufferTextureFormat.DEPTH24STENCIL8 -> 1\n        }\n        val emptyPixels = IntArray(\n            size = sampledWidth * sampledHeight * components,\n            init = { value }\n        ).toIntBuffer()\n\n        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, attachmentHandle)\n\n        GLES30.glTexSubImage2D(\n            /* target = */ GLES30.GL_TEXTURE_2D,\n            /* level = */ 0,\n            /* xoffset = */ 0,\n            /* yoffset = */ 0,\n            /* width = */ sampledWidth,\n            /* height = */ sampledHeight,\n            /* format = */ fbTextureFormatToGL(textureFormat),\n            /* type = */ type,\n            /* pixels = */ emptyPixels\n        )\n    }\n\n    override fun setColorAttachmentAt(attachmentIndex: Int) {\n        require(attachmentIndex <= colorAttachments.lastIndex)\n        _colorAttachmentTexture = colorAttachments[attachmentIndex]\n    }\n\n    override fun destroy() {\n        unbind()\n        GLES30.glDeleteFramebuffers(1, intArrayOf(rendererId), 0)\n        GLES30.glDeleteTextures(1, intArrayOf(depthAttachment), 0)\n        GLES30.glDeleteTextures(\n            colorAttachments.size,\n            colorAttachments.map { it.id }.toIntArray(),\n            0\n        )\n    }\n\n    override fun toString(): String {\n        return \"OpenGLFramebuffer(rendererId=$rendererId, specification=$specification, drawAttachments=${drawAttachments.contentToString()}, sampledWidth=$sampledWidth, sampledHeight=$sampledHeight)\"\n    }\n\n\n    private companion object {\n        private const val TAG = \"OpenGLFramebuffer\"\n        const val MAX_FRAMEBUFFER_SIZE = 8192\n\n        fun fbTextureFormatToGL(format: FramebufferTextureFormat): Int {\n            return when (format) {\n                FramebufferTextureFormat.R8 -> GLES30.GL_R8\n                FramebufferTextureFormat.RGBA8 -> GLES30.GL_RGBA8\n                FramebufferTextureFormat.RGB10_A2 -> GLES30.GL_RGB10_A2\n                FramebufferTextureFormat.DEPTH24STENCIL8 -> GLES30.GL_DEPTH24_STENCIL8\n            }\n        }\n\n        fun createAttachment(\n            width: Int,\n            height: Int,\n            format: FramebufferTextureFormat,\n            flip: Boolean,\n            mipmapFiltering: Boolean\n        ): Texture2D = Texture2D.create(\n            target = Texture.Target.TEXTURE_2D,\n            specification = Texture.Specification(\n                size = IntSize(width = width, height = height),\n                format = format.toTextureFormat(),\n                flipTexture = flip,\n                generateMips = mipmapFiltering,\n                mipmapFiltering = mipmapFiltering\n            )\n        )\n    }\n}\n\ninternal fun Bind.toGlTarget(): Int {\n    return when (this) {\n        READ -> GLES30.GL_READ_FRAMEBUFFER\n        DRAW -> GLES30.GL_DRAW_FRAMEBUFFER\n        BOTH -> GLES30.GL_FRAMEBUFFER\n    }\n}\n\nprivate fun FramebufferTextureFormat.toTextureFormat(): Texture.ImageFormat {\n    return when (this) {\n        FramebufferTextureFormat.R8 -> Texture.ImageFormat.R8\n        FramebufferTextureFormat.RGBA8 -> Texture.ImageFormat.RGBA8\n        FramebufferTextureFormat.RGB10_A2 -> Texture.ImageFormat.RGB10_A2\n        FramebufferTextureFormat.DEPTH24STENCIL8 -> Texture.ImageFormat.DEPTH24STENCIL8\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/buffer/OpenGLIndexBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl.buffer\n\nimport android.opengl.GLES30\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.IndexBuffer\nimport dev.serhiiyaremych.imla.renderer.toIntBuffer\n\ninternal class OpenGLIndexBuffer(indices: IntArray) : IndexBuffer {\n\n    override val elements: Int = indices.size\n    override val sizeBytes: Int = elements * Int.SIZE_BYTES\n    private var rendererId: Int = 0\n    private var isDestroyed: Boolean = false\n\n    init {\n        val ids = IntArray(1)\n        checkGlError(GLES30.glGenBuffers(1, ids, 0))\n        rendererId = ids[0]\n        checkGlError(GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, rendererId))\n        checkGlError(\n            GLES30.glBufferData(\n                /* target = */ GLES30.GL_ELEMENT_ARRAY_BUFFER,\n                /* size = */ sizeBytes,\n                /* data = */ indices.toIntBuffer(),\n                /* usage = */ GLES30.GL_STATIC_DRAW\n            )\n        )\n    }\n\n    override fun bind() {\n        if (isDestroyed) {\n            error(\"Can't bind destroyed index buffer.\")\n        }\n        checkGlError(GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, rendererId))\n    }\n\n    override fun unbind() {\n        if (!isDestroyed) {\n            GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)\n        }\n    }\n\n    override fun destroy() {\n        unbind()\n        GLES30.glDeleteBuffers(1, intArrayOf(rendererId), 0)\n        isDestroyed = true\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/opengl/buffer/OpenGLVertexBuffer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.opengl.buffer\n\nimport android.opengl.GLES30\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.ext.checkGlError\nimport dev.serhiiyaremych.imla.renderer.BufferLayout\nimport dev.serhiiyaremych.imla.renderer.VertexBuffer\nimport dev.serhiiyaremych.imla.renderer.toFloatBuffer\n\ninternal class OpenGLVertexBuffer : VertexBuffer {\n\n    override var elements: Int\n    override var sizeBytes: Int\n    override var layout: BufferLayout? = null\n    private var bufferId: Int = 0\n    private var isDestroyed: Boolean = false\n\n    constructor(count: Int) {\n        this.elements = count\n        this.sizeBytes = this.elements * Float.SIZE_BYTES\n        trace(\"vboCreateDynamic\") {\n            createVertexBuffer(null, GLES30.GL_DYNAMIC_DRAW)\n        }\n    }\n\n    constructor(vertices: FloatArray) {\n        this.elements = vertices.size\n        this.sizeBytes = elements * Float.SIZE_BYTES\n        trace(\"vboCreateStatic\") {\n            createVertexBuffer(vertices, GLES30.GL_STATIC_DRAW)\n        }\n    }\n\n    private fun createVertexBuffer(vertices: FloatArray?, usage: Int) {\n        val ids = IntArray(1)\n        checkGlError(GLES30.glGenBuffers(1, ids, 0))\n        bufferId = ids[0]\n        checkGlError(GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, bufferId))\n        checkGlError(\n            GLES30.glBufferData(\n                /* target = */ GLES30.GL_ARRAY_BUFFER,\n                /* size = */ sizeBytes,\n                /* data = */ vertices?.toFloatBuffer(),\n                /* usage = */ usage\n            )\n        )\n    }\n\n    override fun bind() = trace(\"vboBind\") {\n        if (isDestroyed) {\n            error(\"Can't bind destroyed buffer.\")\n        }\n        checkGlError(GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, bufferId))\n    }\n\n    override fun unbind() {\n        if (!isDestroyed) {\n            GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)\n        }\n    }\n\n    override fun setData(data: FloatArray) = trace(\"vboSetData\") {\n        bind()\n        this.sizeBytes = data.size * Float.SIZE_BYTES\n        this.elements = data.size\n\n        trace(\"glBufferSubData[${elements}, ${sizeBytes}bytes]\") {\n            checkGlError(\n                GLES30.glBufferSubData(GLES30.GL_ARRAY_BUFFER, 0, sizeBytes, data.toFloatBuffer())\n            )\n        }\n    }\n\n    override fun destroy() {\n        unbind()\n        GLES30.glDeleteBuffers(1, intArrayOf(bufferId), 0)\n        isDestroyed = true\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/primitive/QuadVertex.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.primitive\n\nimport androidx.compose.ui.geometry.Offset\nimport dev.romainguy.kotlin.math.Float4\n\ninternal data class QuadVertex(\n    val position: Float4,\n    val texCoord: Offset,\n    val texIndex: Float,\n    val flipTexture: Float,\n    val isExternalTexture: Float,\n    val alpha: Float,\n    val mask: Float,\n    val maskCoord: Offset\n) {\n    companion object {\n        // @formatter:off\n        const val NUMBER_OF_COMPONENTS =\n                       /*position*/ 4 +\n                       /*texCoord*/ 2 +\n                     /* texIndex */ 1 +\n                  /* flipTexture */ 1 +\n            /* isExternalTexture */ 1 +\n                        /* alpha */ 1 +\n                         /* mask */ 1 +\n                    /* maskCoord */ 2\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/shader/Shader.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.renderer.shader\n\nimport android.content.res.AssetManager\nimport dev.romainguy.kotlin.math.Float2\nimport dev.romainguy.kotlin.math.Float3\nimport dev.romainguy.kotlin.math.Float4\nimport dev.romainguy.kotlin.math.Mat3\nimport dev.romainguy.kotlin.math.Mat4\nimport dev.serhiiyaremych.imla.renderer.opengl.OpenGLShader\nimport org.intellij.lang.annotations.Language\nimport java.io.InputStream\n\ninternal interface Shader {\n    val name: String\n    @Deprecated(\"\")\n    fun bind()\n    fun bind(shaderBinder: ShaderBinder)\n    fun unbind()\n\n    fun bindUniformBlock(blockName: String, bindingPoint: Int)\n\n    fun setInt(name: String, value: Int)\n    fun setIntArray(name: String, values: IntArray)\n    fun setFloatArray(name: String, values: FloatArray)\n    fun setFloat(name: String, value: Float)\n    fun setFloat2(name: String, value: Float2)\n    fun setFloat3(name: String, value: Float3)\n    fun setFloat4(name: String, value: Float4)\n    fun setMat3(name: String, value: Mat3)\n    fun setMat4(name: String, value: Mat4)\n\n    fun destroy()\n\n    companion object {\n        private const val TAG = \"Shader\"\n\n        private fun dropExtension(fileName: String): String {\n            val lastIndexOfDot = fileName.lastIndexOf(\".\")\n            return if (lastIndexOfDot != -1) fileName.substring(0, lastIndexOfDot) else fileName\n        }\n\n        private fun readWithCloseStream(inputStream: InputStream): String {\n            return inputStream.bufferedReader().readText().also { inputStream.close() }\n        }\n\n        fun create(assetManager: AssetManager, vertexAsset: String, fragmentAsset: String): Shader {\n            return OpenGLShader(\n                name = dropExtension(vertexAsset),\n                vertexSrc = readWithCloseStream(assetManager.open(vertexAsset)),\n                fragmentSrc = readWithCloseStream(assetManager.open(fragmentAsset))\n            )\n        }\n\n        fun create(\n            name: String,\n            @Language(\"GLSL\") vertexSrc: String,\n            @Language(\"GLSL\") fragmentSrc: String\n        ): Shader {\n            return OpenGLShader(name, vertexSrc, fragmentSrc)\n        }\n\n        fun create(\n            assetManager: AssetManager,\n            @Language(\"GLSL\") fragmentSrc: String\n        ): Shader {\n            return OpenGLShader(\n                name = \"simple_quad\",\n                vertexSrc = readWithCloseStream(assetManager.open(\"shader/simple_quad.vert\")),\n                fragmentSrc = fragmentSrc\n            )\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/shader/ShaderBinder.kt",
    "content": "package dev.serhiiyaremych.imla.renderer.shader\n\ninternal class ShaderBinder {\n    private var currentShader: Shader? = null\n\n    internal fun bind(shader: Shader) {\n        if (shader != currentShader) {\n            currentShader?.unbind()\n            shader.bind()\n            currentShader = shader\n        }\n    }\n\n    internal fun destroyCurrent() {\n        currentShader?.destroy()\n        currentShader = null\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/shader/ShaderLibrary.kt",
    "content": "package dev.serhiiyaremych.imla.renderer.shader\n\nimport android.content.res.AssetManager\nimport androidx.tracing.trace\nimport org.intellij.lang.annotations.Language\n\ninternal class ShaderLibrary(private val assetManager: AssetManager) {\n    private val shaders: MutableMap<String, Shader> = mutableMapOf()\n\n    fun loadShaderFromFile(vertFileName: String, fragFileName: String): Shader =\n        trace(\"ShaderLibrary#loadShader\") {\n            return shaders.getOrPut(\"${vertFileName}_$fragFileName\") {\n                Shader.create(\n                    assetManager,\n                    \"shader/$vertFileName.vert\",\n                    \"shader/$fragFileName.frag\"\n                )\n            }\n        }\n\n    fun loadShader(\n        name: String,\n        @Language(\"GLSL\") vertexSrc: String,\n        @Language(\"GLSL\") fragmentSrc: String\n    ): Shader {\n        return shaders.getOrPut(name) {\n            Shader.create(name, vertexSrc, fragmentSrc)\n        }\n    }\n\n    fun loadShader(\n        name: String,\n        @Language(\"GLSL\") fragmentSrc: String\n    ): Shader {\n        return shaders.getOrPut(name) {\n            Shader.create(assetManager, fragmentSrc)\n        }\n    }\n\n\n    fun destroy(shader: Shader) {\n        shader.destroy()\n        shaders.remove(shader.name)\n    }\n\n    fun destroyAll() {\n        shaders.forEach { (_, shader) -> shader.destroy() }\n        shaders.clear()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/shader/ShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.shader\n\nimport dev.serhiiyaremych.imla.renderer.BufferLayout\nimport dev.serhiiyaremych.imla.renderer.primitive.QuadVertex\n\ninternal interface ShaderProgram {\n    val shader: Shader\n    val vertexBufferLayout: BufferLayout\n    val componentsCount: Int\n    fun mapVertexData(quadVertexBufferBase: List<QuadVertex>): FloatArray\n\n    fun destroy()\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/stats/ShaderStats.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.stats\n\nimport android.util.Log\n\ninternal object ShaderStats {\n    private const val TAG = \"ShaderStats\"\n\n    var fboInstances: Int = 0\n    var shaderInstances: Int = 0\n    var shaderBinds: Int = 0\n    var shaderBindUniformBlock: Int = 0\n    var shaderUploads: Int = 0\n\n    fun printStats() {\n        Log.d(TAG, \"--------ShaderStats--------\")\n        Log.d(\n            TAG, \"\"\"\n            fboInstances           = $fboInstances\n            shaderInstances        = $shaderInstances\n            shaderBinds            = $shaderBinds\n            shaderUploads          = $shaderUploads\n            shaderBindUniformBlock = $shaderBindUniformBlock\n        \"\"\".trimIndent()\n        )\n        Log.d(TAG, \"---------------------------\")\n    }\n\n    fun reset() {\n        shaderBinds = 0\n        shaderUploads = 0\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/renderer/util/SizeUtil.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.renderer.util\n\nimport androidx.compose.ui.unit.IntSize\n\ninternal object SizeUtil {\n    private val powersOfTwo = intArrayOf(\n        2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096\n    )\n\n    fun closestPOTDown(arbitrarySize: Int): Int {\n        if (arbitrarySize <= 2) return 2\n        return powersOfTwo.last { it <= arbitrarySize }\n    }\n\n    fun closestPOTUp(arbitrarySize: Int): Int {\n        if (arbitrarySize <= 2) return 2\n        return powersOfTwo.first { it >= arbitrarySize }\n    }\n\n    fun closestPOTDown(arbitrarySize: IntSize): IntSize {\n        return IntSize(\n            width = closestPOTDown(arbitrarySize.width),\n            height = closestPOTDown(arbitrarySize.height)\n        )\n    }\n\n    fun closestPOTUp(arbitrarySize: IntSize): IntSize {\n        return IntSize(\n            width = closestPOTUp(arbitrarySize.width),\n            height = closestPOTUp(arbitrarySize.height)\n        )\n    }\n\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/ui/BackdropBlur.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.ui\n\nimport android.view.Surface\nimport androidx.compose.foundation.AndroidExternalSurface\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.snapshotFlow\nimport androidx.compose.runtime.snapshots.Snapshot\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.drawWithCache\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Path\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.graphics.Shape\nimport androidx.compose.ui.graphics.addOutline\nimport androidx.compose.ui.graphics.drawscope.clipPath\nimport androidx.compose.ui.layout.boundsInRoot\nimport androidx.compose.ui.layout.onGloballyPositioned\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.toIntSize\nimport androidx.compose.ui.util.trace\nimport dev.serhiiyaremych.imla.uirenderer.Style\nimport dev.serhiiyaremych.imla.uirenderer.UiLayerRenderer\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.filter\nimport java.util.UUID\n\n@Composable\npublic fun BackdropBlur(\n    modifier: Modifier,\n    rendererState: UiLayerRenderer,\n    style: Style = Style.default,\n    blurMask: Brush? = null,\n    clipShape: Shape = RectangleShape,\n    content: @Composable (onOffsetChanged: (Offset) -> Unit) -> Unit = {}\n): Unit = Box {\n    val contentBoundingBoxState = remember { mutableStateOf(Rect.Zero) }\n    val id = remember { trace(\"BlurBehindView#id\") { UUID.randomUUID().toString() } }\n\n    val drawingSurfaceState = remember { mutableStateOf<Surface?>(null) }\n    val drawingSurfaceSizeState = remember { mutableStateOf(IntSize.Zero) }\n    val contentOffset = remember { mutableStateOf(Offset.Zero) }\n\n    val contentBoundingBox = contentBoundingBoxState.value\n    val clipPath = remember { Path() }\n    // Render the external surface\n    AndroidExternalSurface(\n        modifier = modifier\n            .onGloballyPositioned { layoutCoordinates ->\n                contentBoundingBoxState.value = layoutCoordinates.boundsInRoot()\n            }\n            .drawWithCache {\n                val outline = clipShape.createOutline(size, layoutDirection, this)\n                clipPath.rewind()\n                clipPath.addOutline(outline)\n                onDrawWithContent {\n                    clipPath(path = clipPath) {\n                        this@onDrawWithContent.drawContent()\n                    }\n                }\n            },\n        surfaceSize = contentBoundingBox.size.toIntSize(),\n    ) {\n        onSurface { surface, w, h ->\n            Snapshot.withMutableSnapshot {\n                drawingSurfaceState.value = surface\n                drawingSurfaceSizeState.value = IntSize(w, h)\n            }\n            surface.onChanged { _, _ ->\n                // todo\n            }\n            surface.onDestroyed {\n                rendererState.detachRenderObject(id)\n                drawingSurfaceState.value = null\n            }\n        }\n    }\n\n    val isRendererInitialized by rendererState.isInitialized\n\n    val topOffset = Offset(\n        x = contentBoundingBox.left,\n        y = contentBoundingBox.top\n    )\n    LaunchedEffect(id, drawingSurfaceState.value, rendererState, contentBoundingBox) {\n        val rendererFlow = snapshotFlow { isRendererInitialized }\n        val surfaceFlow = snapshotFlow { drawingSurfaceState.value }\n        combine(rendererFlow, surfaceFlow) { a, b -> a to b }\n            .filter { it.first && it.second != null }\n            .distinctUntilChanged()\n            .collect {\n                rendererState.attachRendererSurface(\n                    surface = it.second,\n                    id = id,\n                    size = drawingSurfaceSizeState.value,\n                )\n                rendererState.updateOffset(id, topOffset + contentOffset.value)\n                rendererState.updateStyle(id, style)\n                rendererState.updateMask(id, blurMask)\n            }\n    }\n\n\n    rendererState.updateMask(id, blurMask)\n    rendererState.updateOffset(id, topOffset + contentOffset.value)\n    trace(\"BackdropBlurView#renderObject.style\") {\n        rendererState.updateStyle(id, style)\n    }\n\n    // Render the content and handle offset changes\n    content { offset ->\n        contentOffset.value = offset\n    }\n\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/MaskTextureRenderer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport android.graphics.PorterDuff\nimport android.graphics.SurfaceTexture\nimport android.view.Surface\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.Canvas\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.drawscope.CanvasDrawScope\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.LayoutDirection\nimport androidx.compose.ui.unit.toSize\nimport androidx.compose.ui.util.trace\nimport androidx.graphics.opengl.GLRenderer\nimport dev.serhiiyaremych.imla.ext.isGLThread\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport java.util.concurrent.atomic.AtomicBoolean\n\n// TODO: Refactor it to custom shader\ninternal class MaskTextureRenderer(\n    density: Density,\n    private val shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder,\n    private val simpleQuadRenderer: SimpleQuadRenderer,\n    private val onRenderComplete: (Texture2D) -> Unit\n) : Density by density {\n    private val drawScope = CanvasDrawScope()\n\n    private lateinit var frameBuffer: Framebuffer\n    private lateinit var maskExternalTexture: Texture2D\n    private lateinit var surfaceTexture: SurfaceTexture\n    private lateinit var extOesShaderProgram: Shader\n\n    private lateinit var surface: Surface\n\n    private var isInitialized: AtomicBoolean = AtomicBoolean(false)\n\n    private var lastRenderedBrush: Brush? = null\n\n    private fun initialize(size: IntSize) {\n        require(isGLThread()) { \"Initialization failed: An active GL context is required in the current thread.\" }\n\n        if (isInitialized.get()) {\n            destroy()\n        }\n\n        extOesShaderProgram = shaderLibrary\n            .loadShaderFromFile(vertFileName = \"simple_quad\", fragFileName = \"simple_ext_quad\")\n            .apply {\n                bind(shaderBinder)\n                bindUniformBlock(\n                    SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n                    SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n                )\n            }\n\n        frameBuffer = Framebuffer.create(\n            FramebufferSpecification(\n                size = size,\n                attachmentsSpec = FramebufferAttachmentSpecification(\n                    attachments = listOf(FramebufferTextureSpecification(format = FramebufferTextureFormat.R8))\n                )\n            )\n        )\n        val texSpec = Texture.Specification(\n            size = size,\n            format = Texture.ImageFormat.RGBA8,\n            flipTexture = true\n        )\n        maskExternalTexture =\n            Texture2D.create(target = Texture.Target.TEXTURE_EXTERNAL_OES, specification = texSpec)\n        maskExternalTexture.bind()\n        surfaceTexture = SurfaceTexture(maskExternalTexture.id)\n        surfaceTexture.setDefaultBufferSize(size.width, size.height)\n        surfaceTexture.setOnFrameAvailableListener {\n            it.updateTexImage()\n            copyTextureToFrameBuffer()\n            onRenderComplete(frameBuffer.colorAttachmentTexture)\n        }\n        surface = Surface(surfaceTexture)\n        isInitialized.set(true)\n    }\n\n    private fun copyTextureToFrameBuffer() = trace(\n        sectionName = \"copyExtTextureToFrameBuffer\"\n    ) {\n        frameBuffer.bind(Bind.DRAW)\n        RenderCommand.clear(Color.Transparent)\n        simpleQuadRenderer.draw(shader = extOesShaderProgram, texture = maskExternalTexture)\n    }\n\n\n    private fun invalidateBySize(newSize: IntSize): Boolean {\n        return !isInitialized.get() ||\n                (maskExternalTexture.width != newSize.width || maskExternalTexture.height != newSize.height)\n    }\n\n    private fun shouldRedraw(brush: Brush): Boolean {\n        return lastRenderedBrush != brush\n    }\n\n    fun renderMask(glRenderer: GLRenderer, brush: Brush, size: IntSize) =\n        trace(\"MaskTextureRenderer#renderMask\") {\n            if (invalidateBySize(size)) glRenderer.execute { this.initialize(size) }\n\n            if (shouldRedraw(brush)) {\n                glRenderer.execute {\n                    trace(\"MaskTextureRenderer#shouldRedraw\") {\n                        val hwCanvas = surface.lockHardwareCanvas()\n                        hwCanvas.drawColor(Color.Transparent.toArgb(), PorterDuff.Mode.CLEAR)\n                        drawScope.draw(\n                            density = this,\n                            layoutDirection = LayoutDirection.Ltr,\n                            canvas = Canvas(hwCanvas),\n                            size = size.toSize()\n                        ) {\n                            drawRect(brush)\n                        }\n                        surface.unlockCanvasAndPost(hwCanvas)\n                    }\n                }\n            } else {\n                this.onRenderComplete(maskExternalTexture)\n            }\n        }\n\n    fun destroy() {\n        if (isInitialized.get()) {\n            surfaceTexture.release()\n            surface.release()\n            maskExternalTexture.destroy()\n            isInitialized.set(false)\n            frameBuffer.destroy()\n        }\n    }\n\n    fun releaseCurrentMask() {\n        destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/RenderObject.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\", \"CanBeParameter\")\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport android.graphics.PixelFormat\nimport android.hardware.HardwareBuffer\nimport android.media.ImageReader\nimport android.os.Build\nimport android.view.Surface\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.unit.toIntSize\nimport androidx.compose.ui.util.trace\nimport androidx.graphics.opengl.GLRenderer\nimport androidx.graphics.opengl.egl.EGLManager\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport kotlin.properties.Delegates\n\ninternal class RenderObject internal constructor(\n    internal val id: String,\n    internal var area: Rect,\n) {\n    private var renderCallback: ((RenderObject) -> Unit)? = null\n\n    private val openGLCallback = object : GLRenderer.RenderCallback {\n        override fun onDrawFrame(eglManager: EGLManager) {\n            trace(\"RenderObject#onRender\") {\n                renderCallback?.invoke(this@RenderObject)\n            }\n        }\n    }\n\n    internal var style: Style by Delegates.observable(Style.default) { _, old, new ->\n        if (old != new) {\n            invalidate()\n        }\n    }\n\n    internal var mask: Texture2D? = null\n\n    var renderTarget: GLRenderer.RenderTarget? = null\n\n    fun invalidate(onRenderComplete: ((GLRenderer.RenderTarget) -> Unit)? = null) {\n        renderTarget?.requestRender(onRenderComplete)\n    }\n\n    fun setRenderCallback(onRender: ((RenderObject) -> Unit)?) {\n        this.renderCallback = onRender\n    }\n\n    fun updateOffset(offset: Offset) = trace(\"RenderObject#updateOffset\") {\n        area = area.translate(translateX = offset.x, translateY = offset.y)\n        invalidate()\n    }\n\n    override fun toString(): String {\n        return \"RenderObject(id='$id', rect='$area'')\"\n    }\n\n\n    fun detachFromRenderer() {\n        renderTarget?.detach(true)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as RenderObject\n\n        if (id != other.id) return false\n        if (area != other.area) return false\n        if (style != other.style) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = id.hashCode()\n        result = 31 * result + area.hashCode()\n        result = 31 * result + style.hashCode()\n        return result\n    }\n\n\n    companion object {\n\n        private fun createImageReader(width: Int, height: Int): ImageReader {\n            val maxImages = 2\n            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                ImageReader.newInstance(\n                    /* width = */ width,\n                    /* height = */ height,\n                    /* format = */ PixelFormat.RGBA_8888,\n                    /* maxImages = */ maxImages,\n                    /* usage = */\n                    HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT\n                )\n            } else {\n                ImageReader.newInstance(\n                    /* width = */ width,\n                    /* height = */ height,\n                    /* format = */ PixelFormat.RGBA_8888,\n                    /* maxImages = */ maxImages\n                )\n            }\n        }\n\n        fun createFromSurface(\n            id: String,\n            renderableLayer: RenderableRootLayer,\n            glRenderer: GLRenderer,\n            surface: Surface,\n            rect: Rect,\n        ): RenderObject {\n            val renderObject = RenderObject(\n                id = id,\n                area = rect,\n            ).apply {\n                renderTarget = glRenderer.attach(\n                    surface = surface,\n                    width = rect.width.toInt(),\n                    height = rect.height.toInt(),\n                    renderer = openGLCallback\n                )\n            }\n            return renderObject\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/RenderableRootLayer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport android.graphics.Color\nimport android.graphics.PorterDuff\nimport android.graphics.SurfaceTexture\nimport android.view.Surface\nimport androidx.annotation.MainThread\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Canvas\nimport androidx.compose.ui.graphics.drawscope.CanvasDrawScope\nimport androidx.compose.ui.graphics.drawscope.scale\nimport androidx.compose.ui.graphics.layer.GraphicsLayer\nimport androidx.compose.ui.graphics.layer.drawLayer\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.LayoutDirection\nimport androidx.compose.ui.unit.toSize\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.Renderer2D\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\n\ninternal class RenderableRootLayer(\n    private val shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder,\n    private val layerDownsampleFactor: Int,\n    private val density: Density,\n    internal val graphicsLayer: GraphicsLayer,\n    internal val renderer2D: Renderer2D,\n    private val simpleQuadRenderer: SimpleQuadRenderer,\n    private val onLayerTextureUpdated: () -> Unit\n) {\n    val sizeInt: IntSize get() = graphicsLayer.size\n    val sizeDec: Size get() = sizeInt.toSize()\n    val scale: Float\n        get() = 1.0f / layerDownsampleFactor\n\n    val isReady: Boolean\n        get() = sizeInt == IntSize.Zero\n\n    private val drawingScope: CanvasDrawScope = CanvasDrawScope()\n    private lateinit var layerExternalTexture: SurfaceTexture\n    private lateinit var layerSurface: Surface\n    private lateinit var extOesLayerTexture: Texture2D\n\n    lateinit var highResFBO: Framebuffer\n        private set\n\n    private var isInitialized: Boolean = false\n    private var isDestroyed: Boolean = false\n\n    private lateinit var extOesShaderProgram: Shader\n\n    fun initialize() {\n        require(!isDestroyed) { \"Can't re-init destroyed layer\" }\n        if (!isReady) {\n            trace(\"RenderableRootLayer#initialize\") {\n                val specification = FramebufferSpecification(\n                    size = sizeInt,\n                    attachmentsSpec = FramebufferAttachmentSpecification(\n                        listOf(\n                            FramebufferTextureSpecification(\n                                format = FramebufferTextureFormat.RGBA8,\n                                flip = true\n                            )\n                        )\n                    ),\n                )\n\n                highResFBO = Framebuffer.create(specification)\n\n                extOesLayerTexture = Texture2D.create(\n                    target = Texture.Target.TEXTURE_EXTERNAL_OES,\n                    specification = Texture.Specification(size = sizeInt, flipTexture = false)\n                )\n                extOesLayerTexture.bind()\n                layerExternalTexture = SurfaceTexture(extOesLayerTexture.id)\n                layerExternalTexture.setDefaultBufferSize(sizeInt.width, sizeInt.height)\n                layerSurface = Surface(layerExternalTexture)\n\n                layerExternalTexture.setOnFrameAvailableListener {\n                    trace(\"surfaceTexture#updateTexImage\") { it.updateTexImage() }\n                    copyTextureToFrameBuffer()\n                    onLayerTextureUpdated()\n                }\n\n                extOesShaderProgram = shaderLibrary.loadShaderFromFile(\n                    vertFileName = \"simple_quad\",\n                    fragFileName = \"simple_ext_quad\"\n                ).apply {\n                    bind(shaderBinder)\n                    bindUniformBlock(\n                        SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n                        SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n                    )\n                }\n                isInitialized = true\n            }\n        }\n    }\n\n    fun resize() {\n        TODO(\"Implement runtime layer resizing\")\n    }\n\n    private fun copyTextureToFrameBuffer() = trace(\n        \"copyExtTextureToFrameBuffer\"\n    ) {\n        trace(\"fullSizeBuffer\") {\n            highResFBO.bind(Bind.DRAW)\n            RenderCommand.clear()\n            simpleQuadRenderer.draw(extOesShaderProgram, extOesLayerTexture)\n        }\n    }\n\n    @MainThread\n    fun updateTex() = trace(\"RenderableRootLayer#updateTex\") {\n        require(!isDestroyed) { \"Can't update destroyed layer\" }\n        require(!graphicsLayer.isReleased) { \"GraphicsLayer has been released!\" }\n        require(isInitialized) { \"RenderableRootLayer not initialized!\" }\n\n        trace(\"drawLayerToExtTexture[$sizeInt]\") {\n\n            layerSurface.lockHardwareCanvas()?.let { canvas ->\n                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)\n                drawingScope.draw(density, LayoutDirection.Ltr, Canvas(canvas), sizeDec) {\n                    trace(\"drawGraphicsLayer\") {\n                        scale(scaleX = 1.0f, scaleY = -1f) {\n                            drawLayer(graphicsLayer)\n                        }\n                    }\n                }\n                layerSurface.unlockCanvasAndPost(canvas)\n            }\n        }\n    }\n\n    fun destroy() {\n        layerExternalTexture.release()\n        layerSurface.release()\n        extOesLayerTexture.destroy()\n        isDestroyed = true\n    }\n}\n\ninternal operator fun IntSize.compareTo(other: IntSize): Int {\n    return (width.toLong() * height).compareTo((other.width.toLong() * other.height))\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/RenderingPipeline.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport android.view.View\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.util.trace\nimport androidx.graphics.opengl.GLRenderer\nimport androidx.tracing.Trace\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.Renderer2D\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.renderer.stats.ShaderStats\nimport dev.serhiiyaremych.imla.uirenderer.processing.EffectCoordinator\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport java.util.concurrent.ConcurrentHashMap\n\ninternal class RenderingPipeline(\n    rootLayer: RenderableRootLayer,\n    private val simpleRenderer: SimpleQuadRenderer,\n    private val shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder,\n    private val renderer2D: Renderer2D,\n    private val density: Density\n) {\n    private val framebufferPool: FramebufferPool = FramebufferPool()\n    private val masks: MutableMap<String, MaskTextureRenderer> = ConcurrentHashMap()\n    private val renderObjects: MutableMap<String, RenderObject> = ConcurrentHashMap()\n    private val effectCoordinator = EffectCoordinator(\n        density = density,\n        framebufferPool = framebufferPool,\n        rootLayer = rootLayer,\n        simpleQuadRenderer = simpleRenderer,\n        shaderLibrary = shaderLibrary,\n        shaderBinder = shaderBinder\n    )\n\n    fun getRenderObject(id: String?): RenderObject? {\n        return id?.let { renderObjects[it] }\n    }\n\n    fun addRenderObject(renderObject: RenderObject) {\n        renderObjects[renderObject.id] = renderObject.apply { setRenderCallback(renderCallback) }\n    }\n\n    fun updateMask(glRenderer: GLRenderer, renderObjectId: String?, mask: Brush?) {\n        val renderObject = renderObjectId?.let { renderObjects[it] }\n        if (renderObject != null) {\n            val maskRenderer = masks.getOrPut(renderObject.id) {\n                MaskTextureRenderer(\n                    density = density,\n                    shaderLibrary = shaderLibrary,\n                    shaderBinder = shaderBinder,\n                    simpleQuadRenderer = simpleRenderer,\n                    onRenderComplete = { tex ->\n                        renderObject.mask = tex\n                        renderObject.invalidate()\n                    }\n                )\n            }\n            if (mask != null) {\n                maskRenderer.renderMask(\n                    glRenderer = glRenderer,\n                    brush = mask,\n                    size = IntSize(\n                        width = renderObject.area.size.width.toInt(),\n                        height = renderObject.area.size.height.toInt()\n                    )\n                )\n            } else {\n                maskRenderer.releaseCurrentMask()\n                renderObject.mask = null\n                renderObject.invalidate()\n            }\n        }\n    }\n\n    private val renderCallback = fun(renderObject: RenderObject) {\n        trace(\"RenderingPipeline#applyAllEffects\") {\n            effectCoordinator.applyEffects(renderObject)\n            framebufferPool.resetPool()\n            framebufferPool.eraseAll()\n        }\n    }\n\n    fun requestRender(onRenderComplete: () -> Unit) {\n        val renderObjectsCount = renderObjects.size\n        if (renderObjectsCount == 0) {\n            onRenderComplete()\n            return\n        }\n\n        var remainingRenders = renderObjectsCount\n        val id = View.generateViewId()\n        Trace.beginAsyncSection(\"requestRender\", id)\n        renderObjects.forEach { (_, renderObject) ->\n            renderObject.invalidate {\n                if (--remainingRenders == 0) {\n                    onRenderComplete()\n\n                    RenderCommand.bindDefaultFramebuffer()\n                    RenderCommand.useDefaultProgram()\n                    RenderCommand.clear()\n                    Trace.endAsyncSection(\"requestRender\", id)\n                    ShaderStats.printStats()\n                    ShaderStats.reset()\n                }\n            }\n        }\n    }\n\n    fun removeRenderObject(renderObjectId: String?) {\n        val renderObject = renderObjects.remove(renderObjectId)\n        renderObject?.setRenderCallback(null)\n        effectCoordinator.removeEffectsOf(renderObjectId)\n    }\n\n    fun destroy() {\n        renderObjects.forEach { (_, ro) ->\n            ro.detachFromRenderer()\n            ro.setRenderCallback(null)\n            effectCoordinator.removeEffectsOf(ro.id)\n        }\n        masks.forEach { (_, mask) ->\n            mask.destroy()\n        }\n        renderObjects.clear()\n        masks.clear()\n        effectCoordinator.destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/Style.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport androidx.annotation.FloatRange\nimport androidx.annotation.IntRange\nimport androidx.compose.runtime.Immutable\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport dev.serhiiyaremych.imla.uirenderer.processing.blur.BlurContext\n\n@Immutable\npublic data class Style(\n    @FloatRange(from = 0.1, to = 2.0)\n    val offset: Float,\n    @IntRange(from = 0, to = BlurContext.MAX_PASSES.toLong())\n    val passes: Int,\n    val tint: Color = Color.Cyan.copy(alpha = 0.3f),\n    @FloatRange(from = 0.0, to = 1.0) val noiseAlpha: Float = 0.07f,\n    @FloatRange(from = 0.0, to = 1.0) val blurOpacity: Float = 1.0f,\n) {\n\n    public companion object {\n        public val default: Style = Style(\n            offset = 1.5f,\n            passes = 4,\n            tint = Color.Transparent,\n            noiseAlpha = 0.2f\n        )\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/UiLayerRenderer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\n\npackage dev.serhiiyaremych.imla.uirenderer\n\nimport android.content.res.AssetManager\nimport android.os.Handler\nimport android.os.Looper\nimport android.util.Log\nimport android.view.Surface\nimport androidx.annotation.MainThread\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.RememberObserver\nimport androidx.compose.runtime.Stable\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.snapshots.Snapshot\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.graphics.Brush\nimport androidx.compose.ui.graphics.drawscope.DrawScope\nimport androidx.compose.ui.graphics.layer.GraphicsLayer\nimport androidx.compose.ui.graphics.rememberGraphicsLayer\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.toSize\nimport androidx.compose.ui.util.trace\nimport androidx.graphics.opengl.GLRenderer\nimport androidx.graphics.opengl.egl.EGLManager\nimport dev.serhiiyaremych.imla.BuildConfig\nimport dev.serhiiyaremych.imla.ext.logw\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.Renderer2D\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport java.util.concurrent.atomic.AtomicBoolean\n\ninternal class UiRendererObserver(\n    val uiLayerRenderer: UiLayerRenderer\n) : RememberObserver {\n    override fun onAbandoned() {\n        uiLayerRenderer.destroy()\n    }\n\n    override fun onForgotten() {\n        uiLayerRenderer.destroy()\n    }\n\n    override fun onRemembered() {\n        // no-op\n        logw(\"UiLayerRenderer\", \"UiLayerRenderer $uiLayerRenderer has been instantiated\")\n    }\n}\n\n@Composable\npublic fun rememberUiLayerRenderer(downSampleFactor: Int = 2): UiLayerRenderer {\n    val density = LocalDensity.current\n    val graphicsLayer = rememberGraphicsLayer()\n    val assetManager = LocalContext.current.assets\n    return remember(density, graphicsLayer, assetManager, downSampleFactor) {\n        UiRendererObserver(\n            UiLayerRenderer(\n                density,\n                graphicsLayer,\n                downSampleFactor,\n                assetManager\n            )\n        ).uiLayerRenderer\n    }\n}\n\n@Stable\npublic class UiLayerRenderer(\n    density: Density,\n    graphicsLayer: GraphicsLayer,\n    downSampleFactor: Int,\n    assetManager: AssetManager\n) : Density by density {\n    // Consider moving to RendererContext class\n    private val shaderLibrary: ShaderLibrary = ShaderLibrary(assetManager)\n    private val renderer2D: Renderer2D = Renderer2D()\n    private val shaderBinder = ShaderBinder()\n    private val simpleRenderer: SimpleRenderer = SimpleRenderer()\n    private val simpleQuadRenderer: SimpleQuadRenderer =\n        SimpleQuadRenderer(shaderLibrary, simpleRenderer, shaderBinder)\n    //\n    private val glRenderer: GLRenderer = GLRenderer().apply {\n        start(\"GLUiLayerRenderer\")\n    }\n\n    private val mainThreadHandler = Handler(Looper.getMainLooper())\n\n    internal val renderableLayer: RenderableRootLayer = RenderableRootLayer(\n        shaderLibrary = shaderLibrary,\n        shaderBinder = shaderBinder,\n        layerDownsampleFactor = downSampleFactor,\n        density = density,\n        graphicsLayer = graphicsLayer,\n        renderer2D = renderer2D,\n        simpleQuadRenderer = simpleQuadRenderer,\n        onLayerTextureUpdated = {\n            renderingPipeline.requestRender {\n                isRendering.set(false)\n                semaphore.release()\n            }\n        }\n    )\n\n    private val renderingPipeline: RenderingPipeline = RenderingPipeline(\n        rootLayer = renderableLayer,\n        simpleRenderer = simpleQuadRenderer,\n        shaderLibrary = shaderLibrary,\n        shaderBinder = shaderBinder,\n        renderer2D = renderer2D,\n        density = this\n    )\n\n    private val semaphore: java.util.concurrent.Semaphore = java.util.concurrent.Semaphore(1)\n\n    private val isGLInitialized = AtomicBoolean(false)\n\n    public val isInitialized: MutableState<Boolean> = mutableStateOf(false)\n\n    private lateinit var mainRenderTarget: GLRenderer.RenderTarget\n\n    private val mainDrawCallback = object : GLRenderer.RenderCallback {\n        override fun onDrawFrame(eglManager: EGLManager) {\n            // TODO: implement FPS counter\n            if (isRendering.compareAndSet(false, true)) {\n//                renderableLayer.updateTex()\n            }\n        }\n    }\n\n    private val isRendering: AtomicBoolean = AtomicBoolean(false)\n    private val isRecording: AtomicBoolean = AtomicBoolean(false)\n\n    internal val isRecorded: Boolean get() = renderableLayer.isReady.not()\n\n    init {\n        initializeIfNeed()\n    }\n\n    private fun initializeIfNeed() {\n        if (renderableLayer.sizeInt == IntSize.Zero) {\n            Log.w(\"UiLayerRenderer\", \"Warn: GraphicsLayer is empty.\")\n            return\n        }\n        if (!isGLInitialized.get()) {\n            glRenderer.execute {\n                RenderCommand.init()\n//                renderer2D.init(assetManager)\n                simpleRenderer.init()\n\n                renderableLayer.initialize()\n                mainRenderTarget = glRenderer.createRenderTarget(\n                    width = renderableLayer.sizeInt.width,\n                    height = renderableLayer.sizeInt.height,\n                    renderer = mainDrawCallback\n                )\n                if (isGLInitialized.compareAndSet(false, true)) {\n                    Snapshot.withMutableSnapshot {\n                        isInitialized.value = true\n                    }\n                }\n            }\n        }\n    }\n\n    context(DrawScope)\n    public fun recordCanvas(block: DrawScope.() -> Unit): Unit =\n        trace(\"UiLayerRenderer#recordCanvas\") {\n            if (isRendering.get() || !isRecording.compareAndSet(false, true)) {\n                // Rendering is in progress or recording is already in progress, skip recording\n                logw(TAG, \"skipping recordCanvas during rendering\")\n                return\n            }\n            try {\n                trace(\"recordCanvas\") {\n                    if (BuildConfig.DEBUG) {\n                        require(renderableLayer.graphicsLayer.isReleased.not())\n                    }\n                    renderableLayer.graphicsLayer.record(block = block)\n                }\n            } finally {\n                isRecording.set(false)\n            }\n        }\n\n    @MainThread\n    public fun onUiLayerUpdated(): Unit = trace(\"UiLayerRenderer#onUiLayerUpdated\") {\n        initializeIfNeed()\n        if (isRecording.get()) {\n            logw(TAG, \"skipping onUiLayerUpdated during recording\")\n            return\n        }\n\n        if (isGLInitialized.get()) {\n//            mainRenderTarget.requestRender()\n            renderableLayer.updateTex()\n//            semaphore.acquire()\n        } else {\n            isRendering.set(false)\n            glRenderer.execute {\n                if (!renderableLayer.isReady && isGLInitialized.get()) {\n//                    mainRenderTarget.requestRender()\n                    mainThreadHandler.post { renderableLayer.updateTex() }\n                }\n            }\n        }\n    }\n\n    private fun attachSurface(\n        surface: Surface,\n        id: String,\n        size: IntSize\n    ): RenderObject {\n        val existingRenderObject = renderingPipeline.getRenderObject(id)\n        if (existingRenderObject != null) {\n            return existingRenderObject\n        }\n        val renderObject = RenderObject.createFromSurface(\n            id = id,\n            renderableLayer = renderableLayer,\n            glRenderer = glRenderer,\n            surface = surface,\n            rect = Rect(offset = Offset.Zero, size.toSize()),\n        )\n        renderingPipeline.addRenderObject(renderObject)\n        return renderObject\n    }\n\n    internal fun attachRendererSurface(\n        surface: Surface?,\n        id: String,\n        size: IntSize\n    ) {\n        if (surface != null) {\n            attachSurface(surface, id, size)\n        }\n    }\n\n    internal fun detachRenderObject(renderObjectId: String?): Unit = with(renderingPipeline) {\n        getRenderObject(renderObjectId)?.detachFromRenderer()\n        removeRenderObject(renderObjectId)\n    }\n\n    public fun destroy() {\n        if (isInitialized.value) {\n            isRendering.set(false)\n            isRecording.set(false)\n            isInitialized.value = false\n            glRenderer.stop(true)\n            renderableLayer.destroy()\n            renderingPipeline.destroy()\n        }\n    }\n\n    internal fun updateOffset(renderObjectId: String?, offset: Offset) {\n        renderingPipeline\n            .getRenderObject(renderObjectId)\n            ?.updateOffset(offset = offset)\n    }\n\n    internal fun updateStyle(renderObjectId: String?, style: Style) {\n        renderingPipeline.getRenderObject(renderObjectId)?.style = style\n    }\n\n    internal fun updateMask(renderObjectId: String?, brush: Brush?) {\n        renderingPipeline.updateMask(glRenderer, renderObjectId, brush)\n    }\n\n    internal companion object {\n        private const val TAG = \"UiLayerRenderer\"\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/EffectCoordinator.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.unit.Density\nimport androidx.compose.ui.unit.toSize\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.RenderObject\nimport dev.serhiiyaremych.imla.uirenderer.RenderableRootLayer\nimport dev.serhiiyaremych.imla.uirenderer.processing.blend.PostBlendEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.blur.DualBlurEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.mask.MaskEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.noise.NoiseEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.preprocess.PreProcessFilter\n\ninternal class EffectCoordinator(\n    density: Density,\n    private val framebufferPool: FramebufferPool,\n    private val rootLayer: RenderableRootLayer,\n    private val simpleQuadRenderer: SimpleQuadRenderer,\n    private val shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder\n) : Density by density {\n\n    private val effectCache: MutableMap<String, EffectsHolder> = mutableMapOf()\n\n    private fun createEffects(): EffectsHolder {\n        return EffectsHolder(\n            preProcess = PreProcessFilter(shaderLibrary, framebufferPool, simpleQuadRenderer, shaderBinder),\n//            blurEffect = BlurEffect(assetManager, simpleQuadRenderer).apply { setup(effectSize) },\n            blurEffect = DualBlurEffect(framebufferPool, shaderLibrary, shaderBinder, simpleQuadRenderer),\n            noiseEffect = NoiseEffect(shaderLibrary, shaderBinder, framebufferPool, simpleQuadRenderer),\n            maskEffect = MaskEffect(shaderLibrary, framebufferPool, shaderBinder, simpleQuadRenderer),\n            blendEffect = PostBlendEffect(simpleQuadRenderer)\n        )\n    }\n\n    fun applyEffects(renderObject: RenderObject) = with(Unit) {\n\n        val effects = effectCache.getOrPut(renderObject.id) {\n            createEffects()\n        }\n\n        val maskTexture = renderObject.mask\n        val (prePrecess, blur, noise, mask, blendEffect) = effects\n\n        mask.applyEffect(\n            backgroundFramebuffer = rootLayer.highResFBO,\n            backgroundCrop = renderObject.area,\n            foreground = noise.applyEffect(\n                texture = blur.applyEffect(\n                    inputFbo = prePrecess.preProcess(rootLayer.highResFBO, renderObject.area),\n                    offset = renderObject.style.offset,\n                    passes = renderObject.style.passes,\n                    tint = renderObject.style.tint\n                ),\n                noiseAlpha = renderObject.style.noiseAlpha\n            ),\n            foregroundCrop = prePrecess.contentCrop,\n            mask = maskTexture\n        )\n\n        val finalFb = output(blur, noise, mask)\n        if (finalFb != null) {\n            trace(\"finalBlendLayers\") {\n                RenderCommand.bindDefaultFramebuffer(Bind.DRAW)\n                RenderCommand.setViewPort(\n                    x = 0, y = 0,\n                    width = renderObject.area.width.toInt(),\n                    height = renderObject.area.height.toInt()\n                )\n                RenderCommand.clear()\n                blendEffect.blendToDefaultBuffer(\n                    background = rootLayer.highResFBO,\n                    cutBackgroundRegion = renderObject.area,\n                    foreground = finalFb,\n                    cutForegroundRegion = if (maskTexture != null) Rect(\n                        offset = Offset.Zero,\n                        size = finalFb.specification.size.toSize()\n                    ) else prePrecess.contentCrop,\n                    opacity = renderObject.style.blurOpacity,\n                )\n            }\n        }\n    }\n\n    private fun output(\n        blur: DualBlurEffect,\n        noise: NoiseEffect,\n        mask: MaskEffect\n    ): Framebuffer? {\n        return when {\n            mask.isEnabled() -> mask.outputFramebuffer\n            noise.isEnabled() -> noise.outputFramebuffer\n            else -> blur.outputFramebuffer\n        }\n    }\n\n    fun removeEffectsOf(id: String?) {\n        effectCache.remove(id)?.dispose()\n    }\n\n    fun destroy() {\n        effectCache.forEach { (_, effects) ->\n            effects.dispose()\n        }\n        effectCache.clear()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/EffectsHolder.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing\n\nimport dev.serhiiyaremych.imla.uirenderer.processing.blend.PostBlendEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.blur.DualBlurEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.mask.MaskEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.noise.NoiseEffect\nimport dev.serhiiyaremych.imla.uirenderer.processing.preprocess.PreProcessFilter\n\ninternal data class EffectsHolder(\n    val preProcess: PreProcessFilter,\n    val blurEffect: DualBlurEffect,\n    val noiseEffect: NoiseEffect,\n    val maskEffect: MaskEffect,\n    val blendEffect: PostBlendEffect\n) {\n    fun dispose() {\n        blurEffect.dispose()\n        noiseEffect.dispose()\n        maskEffect.dispose()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/PostProcessingEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.uirenderer.processing\n\nimport androidx.compose.ui.unit.IntSize\nimport dev.serhiiyaremych.imla.renderer.Texture\n\ninternal interface PostProcessingEffect {\n    fun shouldResize(size: IntSize): Boolean\n    fun setup(size: IntSize)\n    fun applyEffect(texture: Texture): Texture\n\n    fun dispose()\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/SimpleQuadRenderer.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.SubTexture2D\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.VertexBuffer\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.renderer.toFloatBuffer\nimport java.nio.FloatBuffer\n\ninternal class SimpleQuadRenderer(\n    shaderLibrary: ShaderLibrary,\n    val renderer: SimpleRenderer,\n    val shaderBinder: ShaderBinder\n) {\n    private var vbo: VertexBuffer? = null\n    private val simpleQuadShader by lazy(LazyThreadSafetyMode.NONE) {\n        shaderLibrary\n            .loadShaderFromFile(vertFileName = \"simple_quad\", fragFileName = \"simple_quad\")\n            .apply {\n                bindUniformBlock(\n                    SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n                    SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n                )\n            }\n    }\n    private val simpleDataCache: FloatBuffer by lazy(LazyThreadSafetyMode.NONE) {\n        FloatArray(renderer.data.textureDataUBO.elements).toFloatBuffer()\n    }\n\n    private var texCoord: Array<Offset> = Array(4) { Offset.Unspecified }\n    private var flipY: Boolean = false\n    private var alpha: Float = -1.0f\n\n    fun draw(shader: Shader = simpleQuadShader, texture: Texture? = null, alpha: Float = 1.0f) =\n        trace(\"SimpleQuadRenderer#draw\") {\n            renderer.data.vao.bind()\n            vbo?.bind()\n            shader.bind(shaderBinder)\n            if (texture != null) {\n                texture.bind()\n                uploadTextureDataIfNeed(alpha, texture.flipTexture, getTextureCoordinates(texture))\n            }\n            renderer.flush()\n        }\n\n    fun draw(\n        shader: Shader = simpleQuadShader,\n        texture: Texture2D? = null,\n        textureCoordinates: Array<Offset>? = null,\n        alpha: Float = 1.0f\n    ) =\n        trace(\"SimpleQuadRenderer#draw\") {\n            renderer.data.vao.bind()\n            vbo?.bind()\n            shader.bind(shaderBinder)\n            if (texture != null) {\n                texture.bind()\n                uploadTextureDataIfNeed(\n                    alpha,\n                    texture.flipTexture,\n                    textureCoordinates ?: getTextureCoordinates(texture)\n                )\n            }\n            renderer.flush()\n        }\n\n\n    private fun uploadTextureDataIfNeed(\n        alpha: Float,\n        flipTexture: Boolean,\n        textureCoordinates: Array<Offset>\n    ) {\n        val texCoord: Array<Offset> = textureCoordinates\n        val flipY: Boolean = flipTexture\n        val texCoordinatesChanged = isTexCoordinatesChanged(texCoord)\n        val flipChanged = isFlipChanged(flipY)\n        val alphaChanged = isAlphaChanged(alpha)\n        val isDataChanged =\n            texCoordinatesChanged || flipChanged || alphaChanged\n\n        if (isDataChanged) {\n            trace(\"uploadDataIfNeed\") {\n                simpleDataCache.rewind()\n\n                // Bottom Left\n                simpleDataCache.put(texCoord[0].x)\n                simpleDataCache.put(texCoord[0].y)\n                simpleDataCache.put(0.0f)       // padding\n                simpleDataCache.put(0.0f)       // padding\n                // Bottom Right\n                simpleDataCache.put(texCoord[1].x)\n                simpleDataCache.put(texCoord[1].y)\n                simpleDataCache.put(0.0f)       // padding\n                simpleDataCache.put(0.0f)       // padding\n                // Top Right\n                simpleDataCache.put(texCoord[2].x)\n                simpleDataCache.put(texCoord[2].y)\n                simpleDataCache.put(0.0f)       // padding\n                simpleDataCache.put(0.0f)       // padding\n                // Top Left\n                simpleDataCache.put(texCoord[3].x)\n                simpleDataCache.put(texCoord[3].y)\n                simpleDataCache.put(0.0f)       // padding\n                simpleDataCache.put(0.0f)       // padding\n                // Flip Y & Alpha\n                simpleDataCache.put(if (flipY) 1.0f else 0.0f)\n                simpleDataCache.put(alpha)\n                simpleDataCache.put(0.0f)       // padding\n                simpleDataCache.put(0.0f)       // padding\n\n                renderer.data.textureDataUBO.setData(simpleDataCache.position(0))\n\n                this.texCoord = texCoord\n                this.flipY = flipY\n                this.alpha = alpha\n            }\n        }\n    }\n\n    private fun isAlphaChanged(alpha: Float): Boolean {\n        return this.alpha != alpha\n    }\n\n    private fun isFlipChanged(flipY: Boolean): Boolean {\n        return this.flipY != flipY\n    }\n\n    private fun isTexCoordinatesChanged(texCoord: Array<Offset>): Boolean {\n        return this.texCoord[0] != texCoord[0] || this.texCoord[1] != texCoord[1] || this.texCoord[2] != texCoord[2] || this.texCoord[3] != texCoord[3]\n    }\n\n    private fun getTextureCoordinates(texture: Texture): Array<Offset> {\n        return when (texture) {\n            is Texture2D -> defaultTextureCoords\n            is SubTexture2D -> texture.texCoords\n            else -> defaultTextureCoords\n        }\n    }\n\n    companion object {\n        private val bottomLeft = Offset(0.0f, 0.0f)\n        private val bottomRight = Offset(1.0f, 0.0f)\n        private val topRight = Offset(1.0f, 1.0f)\n        private val topLeft = Offset(0.0f, 1.0f)\n        val defaultTextureCoords = arrayOf(bottomLeft, bottomRight, topRight, topLeft) // CCW\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blend/PostBlendEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blend\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.geometry.Size\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\n\ninternal class PostBlendEffect(\n    private val simpleRenderer: SimpleQuadRenderer\n) {\n    private val cropCoordinates: Array<Offset> = Array<Offset>(4) { Offset.Zero }\n\n    fun blendToDefaultBuffer(\n        background: Framebuffer,\n        cutBackgroundRegion: Rect,\n        foreground: Framebuffer,\n        cutForegroundRegion: Rect,\n        opacity: Float,\n    ) = trace(\"blendToDefaultBuffer\") {\n        val renderTargetSize = cutBackgroundRegion.size\n        val clampedBlurOpacity = when {\n            opacity < 0.1f -> 0.0f\n            opacity > 0.95f -> 1.0f\n            else -> opacity\n        }\n\n        when {\n            clampedBlurOpacity == 0.0f -> { // blur is fully transparent, draw background only\n                blitBackground(background, cutBackgroundRegion, renderTargetSize)\n            }\n\n            clampedBlurOpacity < 1.0f -> { // blur is translucent, mix background and blur\n                blendLayers(\n                    background = background,\n                    cutBackgroundRegion = cutBackgroundRegion,\n                    foreground = foreground,\n                    cutForegroundRegion = cutForegroundRegion,\n                    opacity = opacity\n                )\n            }\n//            clampedBlurOpacity == 1.0f\n            else -> { // blur is fully opaque, draw blur only\n                blitForeground(foreground, cutForegroundRegion, renderTargetSize)\n            }\n        }\n    }\n\n    private fun blitForeground(\n        foreground: Framebuffer,\n        cutForegroundRegion: Rect,\n        renderTargetSize: Size\n    ) = trace(\"blitForeground\") {\n        foreground.bind(Bind.READ, updateViewport = false)\n        RenderCommand.bindDefaultFramebuffer(bind = Bind.DRAW)\n        RenderCommand.setViewPort(\n            width = renderTargetSize.width.toInt(),\n            height = renderTargetSize.height.toInt()\n        )\n        RenderCommand.blitFramebuffer(\n            srcX0 = cutForegroundRegion.left.toInt(),\n            srcY0 = (foreground.specification.size.height - cutForegroundRegion.bottom).toInt(),\n            srcX1 = cutForegroundRegion.right.toInt(),\n            srcY1 = cutForegroundRegion.bottom.toInt(),\n            dstX0 = 0, dstY0 = 0,\n            dstX1 = renderTargetSize.width.toInt(),\n            dstY1 = renderTargetSize.height.toInt()\n        )\n    }\n\n    private fun blitBackground(\n        background: Framebuffer,\n        cutBackgroundRegion: Rect,\n        renderTargetSize: Size\n    ) = trace(\"blitBackground\") {\n        background.bind(Bind.READ, updateViewport = false)\n        RenderCommand.bindDefaultFramebuffer(bind = Bind.DRAW)\n        RenderCommand.setViewPort(\n            width = renderTargetSize.width.toInt(),\n            height = renderTargetSize.height.toInt()\n        )\n        val cut = cutBackgroundRegion.translate(\n            translateX = 0f,\n            translateY = (background.specification.size.height - cutBackgroundRegion.height)\n        )\n        RenderCommand.blitFramebuffer(\n            srcX0 = cut.left.toInt(),\n            srcY0 = cut.top.toInt(),\n            srcX1 = cut.right.toInt(),\n            srcY1 = cut.bottom.toInt(),\n            dstX0 = 0, dstY0 = 0,\n            dstX1 = renderTargetSize.width.toInt(),\n            dstY1 = renderTargetSize.height.toInt()\n        )\n    }\n\n    private fun blendLayers(\n        background: Framebuffer,\n        cutBackgroundRegion: Rect,\n        foreground: Framebuffer,\n        cutForegroundRegion: Rect,\n        opacity: Float\n    ) = trace(\"blendLayers\") {\n        val backgroundSize = background.specification.size\n        cropCoordinates[0] = Offset(\n            x = cutBackgroundRegion.left / backgroundSize.width,\n            y = cutBackgroundRegion.bottom / backgroundSize.height\n        ) // BL\n        cropCoordinates[1] = Offset(\n            x = cutBackgroundRegion.right / backgroundSize.width,\n            y = cutBackgroundRegion.bottom / backgroundSize.height\n        ) // BR\n        cropCoordinates[2] = Offset(\n            x = cutBackgroundRegion.right / backgroundSize.width,\n            y = cutBackgroundRegion.top / backgroundSize.height\n        ) // TR\n        cropCoordinates[3] = Offset(\n            x = cutBackgroundRegion.left / backgroundSize.width,\n            y = cutBackgroundRegion.top / backgroundSize.height\n        ) // TL\n        simpleRenderer.draw(\n            texture = background.colorAttachmentTexture,\n            textureCoordinates = cropCoordinates\n        )\n\n        val foregroundSize = foreground.specification.size\n        cropCoordinates[0] = Offset(\n            x = cutForegroundRegion.left / foregroundSize.width,\n            y = 1.0f - (cutForegroundRegion.bottom / foregroundSize.height)\n        ) // BL\n        cropCoordinates[1] = Offset(\n            x = cutForegroundRegion.right / foregroundSize.width,\n            y = 1.0f - (cutForegroundRegion.bottom / foregroundSize.height)\n        ) // BR\n        cropCoordinates[2] = Offset(\n            x = cutForegroundRegion.right / foregroundSize.width,\n            y = 1.0f - (cutForegroundRegion.top / foregroundSize.height)\n        ) // TR\n        cropCoordinates[3] = Offset(\n            x = cutForegroundRegion.left / foregroundSize.width,\n            y = 1.0f - (cutForegroundRegion.top / foregroundSize.height)\n        ) // TL\n        RenderCommand.withBlendingModeEnabled {\n            simpleRenderer.draw(\n                texture = foreground.colorAttachmentTexture,\n                textureCoordinates = cropCoordinates,\n                alpha = opacity\n            )\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/BlurContext.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blur\n\nimport androidx.compose.ui.unit.IntSize\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.preprocess.times\n\ninternal data class BlurContext(\n    val layerSpecs: List<FramebufferSpecification>,\n    val shaderProgram: DualBlurFilterShaderProgram\n) {\n    companion object {\n        const val PASS_SCALE = 0.67f\n        const val MAX_PASSES = 4\n\n        fun create(\n            shaderLibrary: ShaderLibrary,\n            shaderBinder: ShaderBinder,\n            textureSize: IntSize\n        ): BlurContext {\n            val fboSpec = FramebufferSpecification(\n                size = textureSize,\n                attachmentsSpec = FramebufferAttachmentSpecification.singleColor(format = FramebufferTextureFormat.RGB10_A2)\n            )\n\n            var baseLayerSize = (textureSize * PASS_SCALE).roundToMultipleOfFour()\n\n            val fbos = buildList {\n                for (i in 0..MAX_PASSES) {\n                    add(fboSpec.copy(size = baseLayerSize))\n                    baseLayerSize = (baseLayerSize * PASS_SCALE).roundToMultipleOfFour()\n                }\n            }\n\n            return BlurContext(\n                layerSpecs = fbos,\n                shaderProgram = DualBlurFilterShaderProgram(shaderLibrary, shaderBinder)\n            )\n        }\n    }\n}\n\nprivate fun roundUpToMultipleOfFour(value: Int): Int = (value + 3) / 4 * 4\n\nprivate fun IntSize.roundToMultipleOfFour(): IntSize =\n    IntSize(\n        width = roundUpToMultipleOfFour(this.width),\n        height = roundUpToMultipleOfFour(this.height)\n    )"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/DualBlurEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blur\n\nimport androidx.annotation.FloatRange\nimport androidx.annotation.IntRange\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.util.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SubTexture2D\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport kotlin.properties.Delegates\n\n// Credits:\n// GM Shaders: Blur Philosophy, https://mini.gmshaders.com/p/blur-philosophy\n// Bandwidth-efficient graphics, https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf\ninternal class DualBlurEffect(\n    private val framebufferPool: FramebufferPool,\n    private val shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder,\n    private val simpleRenderer: SimpleQuadRenderer\n) {\n    private var resultFramebuffer: Framebuffer by Delegates.notNull()\n    private var resultFboSpec: FramebufferSpecification by Delegates.notNull()\n    private var blurContext: BlurContext by Delegates.notNull()\n\n    private var isInitialized: Boolean = false\n\n    private var reusableFboList: MutableList<Framebuffer> = ArrayList()\n\n    internal val outputFramebuffer: Framebuffer\n        get() = resultFramebuffer\n\n    private fun setup(size: IntSize) {\n        if (isInitialized.not() || shouldResize(size)) {\n            trace(\"setup\") {\n                init(size)\n                isInitialized = true\n            }\n        }\n    }\n\n    private fun shouldResize(size: IntSize): Boolean {\n        return !isInitialized ||\n                (resultFramebuffer.specification.size != size)\n    }\n\n    fun applyEffect(\n        inputFbo: Framebuffer,\n        @FloatRange(from = 0.1, to = 2.0)\n        offset: Float,\n        @IntRange(from = 0, to = BlurContext.MAX_PASSES.toLong())\n        passes: Int,\n        tint: Color\n    ): Texture2D = trace(\"DualKawaseBlurEffect\") {\n        trace(\"BlurEffect#applyEffect\") {\n            setup(inputFbo.specification.size / inputFbo.specification.downSampleFactor)\n            resultFramebuffer = framebufferPool.acquire(resultFboSpec)\n            val enabled = offset > 0.0 && passes > 0\n\n            if (passes != reusableFboList.size) {\n                reusableFboList = ArrayList(passes)\n            }\n\n            for (idx in blurContext.layerSpecs.indices) {\n                reusableFboList.add(framebufferPool.acquire(blurContext.layerSpecs[idx]))\n            }\n\n            var readFBO: Framebuffer = inputFbo\n            var drawFBO = if (enabled) reusableFboList[0] else resultFramebuffer\n\n            trace(\"blitFirstFBO\") {\n                readFBO.bind(Bind.READ, false)\n                drawFBO.bind(Bind.DRAW)\n                RenderCommand.clear()\n\n                RenderCommand.blitFramebuffer(\n                    srcX0 = 0,\n                    srcY0 = 0,\n                    srcX1 = readFBO.specification.size.width,\n                    srcY1 = readFBO.specification.size.height,\n                    dstX0 = 0,\n                    dstY0 = 0,\n                    dstX1 = drawFBO.specification.size.width,\n                    dstY1 = drawFBO.specification.size.height,\n                    mask = RenderCommand.colorBufferBit,\n                    filter = RenderCommand.linearTextureFilter,\n                )\n            }\n\n            if (enabled.not()) {\n                return resultFramebuffer.colorAttachmentTexture\n            }\n\n            val shaderProgram = blurContext.shaderProgram\n            shaderProgram.downShader.bind(shaderBinder)\n            // Downsample\n            trace(\"downsample\") {\n                for (i in 0 until passes) {\n                    readFBO = reusableFboList[i]\n                    drawFBO = reusableFboList[i + 1]\n                    val drawSize = drawFBO.specification.size\n                    val texelX = (1f / drawSize.width) * offset\n                    val texelY = (1f / drawSize.height) * offset\n                    shaderProgram.setTexelSize(\n                        texel = Size(texelX, texelY),\n                        down = true\n                    )\n                    drawFBO.bind(bind = Bind.DRAW)\n                    RenderCommand.clear()\n\n                    simpleRenderer.draw(\n                        shader = shaderProgram.downShader,\n                        texture = readFBO.colorAttachmentTexture\n                    )\n                }\n\n            }\n            shaderProgram.upShader.bind(shaderBinder)\n            // Upsample\n            trace(\"upsample\") {\n                for (i in 0 until passes) {\n                    // Upsampling uses buffers in the reverse direction\n                    val readIndex = passes - i\n                    val drawIndex = passes - i - 1\n                    readFBO = reusableFboList[readIndex]\n                    drawFBO = reusableFboList[drawIndex]\n                    drawFBO.bind(Bind.DRAW)\n                    RenderCommand.clear()\n\n                    val drawSize = drawFBO.specification.size\n                    val texelX = (1f / drawSize.width) * offset\n                    val texelY = (1f / drawSize.height) * offset\n                    shaderProgram.setTexelSize(Size(texelX, texelY), false)\n                    if (drawIndex == 0) {\n                        shaderProgram.setTint(tint)\n                    }\n                    simpleRenderer.draw(shaderProgram.upShader, readFBO.colorAttachmentTexture)\n                }\n            }\n            trace(\"blitResult\") {\n                blitFramebuffers(\n                    srcFramebuffer = drawFBO,\n                    dstFramebuffer = resultFramebuffer,\n                    srcWidth = drawFBO.colorAttachmentTexture.width,\n                    srcHeight = drawFBO.colorAttachmentTexture.height,\n                    dstWidth = resultFramebuffer.colorAttachmentTexture.width,\n                    dstHeight = resultFramebuffer.colorAttachmentTexture.height\n                )\n            }\n\n//            trace(\"clean-up\") {\n//                blurContext.framebuffers.forEach {\n//                    it.bind(updateViewport = false)\n//                    it.invalidateAttachments()\n//                }\n//            }\n        }\n        return resultFramebuffer.colorAttachmentTexture\n    }\n\n    private fun blitFramebuffers(\n        srcFramebuffer: Framebuffer,\n        dstFramebuffer: Framebuffer,\n        srcWidth: Int,\n        srcHeight: Int,\n        dstWidth: Int,\n        dstHeight: Int\n    ) = trace(\"blitFramebuffers\") {\n        srcFramebuffer.bind(Bind.READ)\n        dstFramebuffer.bind(Bind.DRAW)\n        dstFramebuffer.invalidateAttachments()\n        RenderCommand.blitFramebuffer(\n            srcX0 = 0,\n            srcY0 = 0,\n            srcX1 = srcWidth,\n            srcY1 = srcHeight,\n            dstX0 = 0,\n            dstY0 = 0,\n            dstX1 = dstWidth,\n            dstY1 = dstHeight,\n        )\n    }\n\n    private fun getSize(texture: Texture): IntSize {\n        return when (texture) {\n            is Texture2D -> IntSize(width = texture.width, height = texture.height)\n            is SubTexture2D -> texture.subTextureSize\n            else -> error(\"Unsupported texture: $texture\")\n        }\n    }\n\n    private fun init(size: IntSize) = trace(\"BlurEffect#init\") {\n        if (isInitialized) {\n            blurContext.shaderProgram.destroy()\n//            blurContext.framebuffers.forEach { it.destroy() }\n        }\n        resultFboSpec = FramebufferSpecification(\n            size = size,\n            attachmentsSpec = FramebufferAttachmentSpecification(\n                attachments = listOf(FramebufferTextureSpecification(format = FramebufferTextureFormat.RGBA8))\n            )\n        )\n        blurContext = BlurContext.create(shaderLibrary, shaderBinder, size)\n    }\n\n    fun dispose() {\n        if (isInitialized) {\n            blurContext.shaderProgram.destroy()\n//            blurContext.framebuffers.forEach { it.destroy() }\n            resultFramebuffer.destroy()\n            isInitialized = false\n        }\n    }\n\n    companion object {\n        const val MIN_BLUR_RADIUS_PX = 2\n    }\n\n    private infix fun Offset.shr(i: Int): Offset {\n        return Offset(\n            x = (x.toInt() shr i).toFloat(),\n            y = (y.toInt() shr i).toFloat()\n        )\n    }\n\n    private operator fun Offset.div(offset: Offset): Offset {\n        return Offset(\n            x = this.x / offset.x,\n            y = this.y / offset.y\n        )\n    }\n}\n"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/DualBlurFilterShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blur\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport dev.romainguy.kotlin.math.Float2\nimport dev.romainguy.kotlin.math.Float4\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport kotlin.properties.Delegates\n\ninternal class DualBlurFilterShaderProgram(\n    shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder\n) {\n    val downShader: Shader = shaderLibrary.loadShaderFromFile(\n        vertFileName = \"simple_quad\",\n        fragFileName = \"blur_down\"\n    ).apply {\n        bind(shaderBinder)\n        setInt(\"u_Texture\", 0)\n        bindUniformBlock(\n            SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n            SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n        )\n    }\n\n    val upShader: Shader = shaderLibrary.loadShaderFromFile(\n        vertFileName = \"simple_quad\",\n        fragFileName = \"blur_up\"\n    ).apply {\n        bind(shaderBinder)\n        setInt(\"u_Texture\", 0)\n        bindUniformBlock(\n            SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n            SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n        )\n    }\n\n    private var down: Boolean = true\n\n    private var contentOffset: Offset by Delegates.observable(Offset.Zero) { _, old, new ->\n        if (old != new && new != Offset.Zero) {\n            val shader = if (down) downShader else upShader\n            shader.setFloat2(\"u_ContentOffset\", Float2(new.x, new.y))\n        }\n    }\n\n    private var halfPixel: Size by Delegates.observable(Size.Zero) { _, old, new ->\n        if (old != new && new != Size.Zero) {\n            val shader = if (down) downShader else upShader\n            shader.setFloat2(\"u_Texel\", Float2(new.width, new.height))\n        }\n    }\n\n    private var tintColor: Color by Delegates.observable(Color.Transparent) { _, old, new ->\n        if (old != new) {\n            upShader.setFloat4(\"u_Tint\", Float4(new.red, new.green, new.blue, new.alpha))\n        }\n    }\n\n    fun setContentOffset(offset: Offset, down: Boolean) {\n        if (this.down != down) {\n            this.contentOffset = Offset.Zero\n        }\n        this.down = down\n        this.contentOffset = offset\n    }\n\n    fun setTexelSize(texel: Size, down: Boolean) {\n        this.down = down\n        this.halfPixel = texel\n    }\n\n    fun setTint(color: Color) {\n        this.tintColor = color\n    }\n\n\n    fun destroy() {\n        downShader.destroy()\n        upShader.destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/SepGaussianBlurEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blur\n\nimport android.content.res.AssetManager\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.util.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureFormat\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferTextureSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SubTexture2D\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport kotlin.properties.Delegates\n\n// GM Shaders: Blur Philosophy, https://mini.gmshaders.com/p/blur-philosophy\ninternal class SepGaussianBlurEffect(\n    assetManager: AssetManager,\n    shaderBinder: ShaderBinder,\n    private val simpleRenderer: SimpleQuadRenderer\n) {\n    private val blurShaderProgram: SimpleBlurShaderProgram =\n        SimpleBlurShaderProgram(assetManager, shaderBinder)\n\n    private var extraHPassFramebuffer: Framebuffer by Delegates.notNull()\n    private var extraVPassFramebuffer: Framebuffer by Delegates.notNull()\n\n    private var horizontalPassFramebuffer: Framebuffer by Delegates.notNull()\n    private var verticalPassFramebuffer: Framebuffer by Delegates.notNull()\n\n    private var resultFramebuffer: Framebuffer by Delegates.notNull()\n\n    private var blurRadius: Float = 0f\n\n    private var isInitialized: Boolean = false\n\n    internal val outputFramebuffer: Framebuffer\n        get() = resultFramebuffer\n\n    fun setup(size: IntSize) {\n        if (isInitialized.not() || shouldResize(size)) {\n            trace(\"setup\") {\n                init(size)\n                isInitialized = true\n            }\n        }\n    }\n\n    private fun shouldResize(size: IntSize): Boolean {\n        return !isInitialized ||\n                (horizontalPassFramebuffer.specification.size != size || verticalPassFramebuffer.specification.size != size)\n    }\n\n    fun applyEffect(texture: Texture, blurRadius: Float, tint: Color): Texture {\n        trace(\"BlurEffect#applyEffect\") {\n            val effectSize = getSize(texture)\n            setup(effectSize)\n            this.blurRadius = blurRadius\n\n            if (isEnabled().not()) {\n                return@trace texture\n            }\n\n            trace(\"setStyle\") {\n                blurShaderProgram.setBlurRadius(blurRadius)\n                blurShaderProgram.setTintColor(tint)\n                blurShaderProgram.setTexSize(effectSize)\n            }\n\n            // horizontal pass\n            trace(\"horizontalPass\") {\n                blurShaderProgram.setHorizontalPass()\n                horizontalPassFramebuffer.bind(Bind.DRAW)\n                RenderCommand.clear()\n\n                simpleRenderer.draw(\n                    shader = blurShaderProgram.shader,\n                    texture = texture\n                )\n            }\n\n            // vertical pass\n            trace(\"verticalPass\") {\n                blurShaderProgram.setVerticalPass()\n                verticalPassFramebuffer.bind(Bind.DRAW)\n                RenderCommand.clear()\n\n                simpleRenderer.draw(\n                    shader = blurShaderProgram.shader,\n                    texture = horizontalPassFramebuffer.colorAttachmentTexture\n                )\n            }\n\n            if (DO_EXTRA_BLURRING_PASS) {\n                trace(\"extraHPass\") {\n                    extraHPassFramebuffer.bind(Bind.DRAW)\n                    RenderCommand.clear()\n                    blurShaderProgram.setHorizontalPass()\n                    simpleRenderer.draw(\n                        shader = blurShaderProgram.shader,\n                        texture = verticalPassFramebuffer.colorAttachmentTexture\n                    )\n                }\n                trace(\"extraVPass\") {\n                    extraVPassFramebuffer.bind(Bind.DRAW)\n                    RenderCommand.clear()\n                    blurShaderProgram.setVerticalPass()\n                    simpleRenderer.draw(\n                        shader = blurShaderProgram.shader,\n                        texture = extraHPassFramebuffer.colorAttachmentTexture\n                    )\n                }\n            }\n\n            trace(\"blitResult\") {\n                val srcFramebuffer = if (DO_EXTRA_BLURRING_PASS) {\n                    extraVPassFramebuffer\n                } else {\n                    verticalPassFramebuffer\n                }\n\n                blitFramebuffers(\n                    srcFramebuffer = srcFramebuffer,\n                    dstFramebuffer = resultFramebuffer,\n                    srcWidth = srcFramebuffer.colorAttachmentTexture.width,\n                    srcHeight = srcFramebuffer.colorAttachmentTexture.height,\n                    dstWidth = resultFramebuffer.colorAttachmentTexture.width,\n                    dstHeight = resultFramebuffer.colorAttachmentTexture.height\n                )\n            }\n        }\n\n        return resultFramebuffer.colorAttachmentTexture\n    }\n\n    private fun blitFramebuffers(\n        srcFramebuffer: Framebuffer,\n        dstFramebuffer: Framebuffer,\n        srcWidth: Int,\n        srcHeight: Int,\n        dstWidth: Int,\n        dstHeight: Int\n    ) = trace(\"blitFramebuffers\") {\n        srcFramebuffer.bind(Bind.READ)\n        dstFramebuffer.bind(Bind.DRAW)\n        RenderCommand.clear()\n        RenderCommand.blitFramebuffer(\n            srcX0 = 0,\n            srcY0 = 0,\n            srcX1 = srcWidth,\n            srcY1 = srcHeight,\n            dstX0 = 0,\n            dstY0 = 0,\n            dstX1 = dstWidth,\n            dstY1 = dstHeight,\n        )\n    }\n\n    private fun getSize(texture: Texture): IntSize {\n        return when (texture) {\n            is Texture2D -> IntSize(width = texture.width, height = texture.height)\n            is SubTexture2D -> texture.subTextureSize\n            else -> error(\"Unsupported texture: $texture\")\n        }\n    }\n\n    private fun init(size: IntSize) = trace(\"BlurEffect#init\") {\n        if (isInitialized) {\n            horizontalPassFramebuffer.destroy()\n            verticalPassFramebuffer.destroy()\n        }\n        val spec = FramebufferSpecification(\n            size = size,\n            attachmentsSpec = FramebufferAttachmentSpecification(\n                attachments = listOf(FramebufferTextureSpecification(format = FramebufferTextureFormat.RGBA8))\n            )\n        )\n\n        horizontalPassFramebuffer = Framebuffer.create(spec)\n        verticalPassFramebuffer = Framebuffer.create(spec)\n        resultFramebuffer = Framebuffer.create(spec)\n\n        if (DO_EXTRA_BLURRING_PASS) {\n            val extraPassSpec = spec.copy(downSampleFactor = spec.downSampleFactor * 2)\n            extraHPassFramebuffer = Framebuffer.create(extraPassSpec)\n            extraVPassFramebuffer = Framebuffer.create(extraPassSpec)\n        }\n    }\n\n    fun isEnabled(): Boolean = blurRadius > MIN_BLUR_RADIUS_PX\n\n    fun dispose() {\n        horizontalPassFramebuffer.destroy()\n        verticalPassFramebuffer.destroy()\n        blurShaderProgram.destroy()\n        resultFramebuffer.destroy()\n        if (DO_EXTRA_BLURRING_PASS) {\n            extraHPassFramebuffer.destroy()\n            extraVPassFramebuffer.destroy()\n        }\n        isInitialized = false\n    }\n\n    companion object {\n        const val MIN_BLUR_RADIUS_PX = 2\n        const val DO_EXTRA_BLURRING_PASS = false\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/blur/SimpleBlurShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"unused\")\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.blur\n\nimport android.content.res.AssetManager\nimport androidx.annotation.FloatRange\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.IntSize\nimport dev.romainguy.kotlin.math.Float2\nimport dev.romainguy.kotlin.math.Float4\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport kotlin.properties.Delegates\n\ninternal class SimpleBlurShaderProgram(assetManager: AssetManager, private val shaderBinder: ShaderBinder) {\n\n    val shader: Shader = Shader.create(\n        assetManager = assetManager,\n        vertexAsset = \"shader/simple_quad.vert\",\n        fragmentAsset = \"shader/simple_blur.frag\",\n    ).apply {\n        bind(shaderBinder)\n        setInt(\"u_Texture\", 0)\n        bindUniformBlock(\n            SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n            SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n        )\n    }\n\n    private var direction: Float2 by Delegates.observable(zeroDir) { _, old, new ->\n        if (old != new && new != zeroDir) {\n            shader.bind(shaderBinder)\n            shader.setFloat2(\"u_BlurDirection\", new)\n        }\n    }\n\n    private var blurRadiusPx: Float by Delegates.observable(0f) { _, old, new ->\n        if (old != new && new > 0) {\n            shader.bind(shaderBinder)\n            val clampedRadius = new.coerceIn(1f, 72f)\n            shader.setFloat(\"u_BlurSigma\", clampedRadius)\n        }\n    }\n\n    private var blurTintColor: Color by Delegates.observable(Color.Unspecified) { _, old, new ->\n        if (old != new && new != Color.Unspecified) {\n            shader.bind(shaderBinder)\n            shader.setFloat4(\"u_BlurTint\", Float4(new.red, new.green, new.blue, new.alpha))\n        }\n    }\n\n    private var blurTexSize: IntSize by Delegates.observable(IntSize.Zero) { _, old, new ->\n        if (old != new && new != IntSize.Zero) {\n            shader.bind(shaderBinder)\n            shader.setFloat2(\"u_TexelSize\", Float2(new.width.toFloat(), new.height.toFloat()))\n        }\n    }\n\n    fun setBlurRadius(@FloatRange(from = 1.0, to = 72.0) radius: Float) {\n        blurRadiusPx = radius\n    }\n\n    // horiz=(1.0, 0.0), vert=(0.0, 1.0)\n    fun setHorizontalPass() {\n        direction = horizontalDirection\n    }\n\n    fun setVerticalPass() {\n        direction = verticalDirection\n    }\n\n    fun setTintColor(tint: Color) {\n        blurTintColor = tint\n    }\n\n    fun setTexSize(size: IntSize) {\n        blurTexSize = size\n    }\n\n    fun destroy() {\n        shader.destroy()\n    }\n\n    private companion object {\n        val zeroDir = Float2()\n        val horizontalDirection = Float2(x = 1.0f)\n        val verticalDirection = Float2(y = 1.0f)\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/mask/MaskEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.mask\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.unit.IntSize\nimport androidx.tracing.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.MAX_TEXTURE_SLOTS\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\n\ninternal class MaskEffect(\n    shaderLibrary: ShaderLibrary,\n    private val framebufferPool: FramebufferPool,\n    private val shaderBinder: ShaderBinder,\n    private val simpleQuadRenderer: SimpleQuadRenderer\n) {\n\n    private val shaderProgram = MaskShaderProgram(shaderLibrary, shaderBinder)\n\n    private lateinit var cropBackgroundFramebuffer: Framebuffer\n    private lateinit var cropBackgroundFramebufferSpec: FramebufferSpecification\n    private lateinit var finalMaskFrameBuffer: Framebuffer\n    private lateinit var finalMaskFrameBufferSpec: FramebufferSpecification\n    private var isInitialized: Boolean = false\n\n    private var maskTexture: Texture? = null\n    private val cropCoordinates: Array<Offset> = Array<Offset>(4) { Offset.Zero }\n\n    internal val outputFramebuffer: Framebuffer\n        get() = finalMaskFrameBuffer\n\n    private fun shouldResize(size: IntSize): Boolean {\n        return !isInitialized || (finalMaskFrameBuffer.specification.size != size)\n    }\n\n    private fun setup(size: IntSize) {\n        if (shouldResize(size)) {\n            if (isInitialized) {\n                finalMaskFrameBuffer.destroy()\n                cropBackgroundFramebuffer.destroy()\n            }\n\n            finalMaskFrameBufferSpec = FramebufferSpecification(\n                size = size,\n                attachmentsSpec = FramebufferAttachmentSpecification()\n            )\n//            finalMaskFrameBuffer = Framebuffer.create(spec)\n            isInitialized = true\n\n//            cropBackgroundFramebuffer = Framebuffer.create(spec)\n\n            val samplers = IntArray(MAX_TEXTURE_SLOTS) { index -> index }\n            shaderProgram.shader.bind(shaderBinder)\n            shaderProgram.shader.setIntArray(\"u_Textures\", samplers)\n        }\n    }\n\n    fun applyEffect(\n        backgroundFramebuffer: Framebuffer,\n        backgroundCrop: Rect,\n        foreground: Texture2D,\n        foregroundCrop: Rect,\n        mask: Texture2D?\n    ) =\n        trace(\"MaskEffect#applyEffect\") {\n            maskTexture = mask\n            if (isEnabled()) {\n                requireNotNull(mask)\n                setup(IntSize(mask.width, mask.height))\n                trace(\"cutBackgroundRegion\") {\n                    cropBackgroundFramebuffer = framebufferPool.acquire(cropBackgroundFramebufferSpec)\n                    finalMaskFrameBuffer = framebufferPool.acquire(finalMaskFrameBufferSpec)\n\n                    backgroundFramebuffer.bind(Bind.READ)\n                    cropBackgroundFramebuffer.bind(Bind.DRAW)\n                    RenderCommand.clear()\n                    val crop = backgroundCrop.translate(\n                        translateX = 0f,\n                        translateY = backgroundFramebuffer.specification.size.height - backgroundCrop.height\n                    )\n                    RenderCommand.blitFramebuffer(\n                        srcX0 = crop.left.toInt(),\n                        srcY0 = crop.top.toInt(),\n                        srcX1 = crop.width.toInt(),\n                        srcY1 = crop.bottom.toInt(),\n                        dstX0 = 0,\n                        dstY0 = 0,\n                        dstX1 = cropBackgroundFramebuffer.specification.size.width,\n                        dstY1 = cropBackgroundFramebuffer.specification.size.height,\n                        mask = RenderCommand.colorBufferBit,\n                        filter = RenderCommand.linearTextureFilter,\n                    )\n                }\n\n                trace(\"setMaskProp\") {\n                    shaderProgram.setMask(mask)\n                    shaderProgram.setBackground(cropBackgroundFramebuffer.colorAttachmentTexture)\n                }\n\n                trace(\"drawMask\") {\n                    finalMaskFrameBuffer.bind(Bind.DRAW)\n                    RenderCommand.clear()\n                    val foregroundSize = IntSize(foreground.width, foreground.height)\n                    cropCoordinates[0] = Offset(\n                        x = foregroundCrop.left / foregroundSize.width,\n                        y = 1.0f - (foregroundCrop.bottom / foregroundSize.height)\n                    ) // BL\n                    cropCoordinates[1] = Offset(\n                        x = foregroundCrop.right / foregroundSize.width,\n                        y = 1.0f - (foregroundCrop.bottom / foregroundSize.height)\n                    ) // BR\n                    cropCoordinates[2] = Offset(\n                        x = foregroundCrop.right / foregroundSize.width,\n                        y = 1.0f - (foregroundCrop.top / foregroundSize.height)\n                    ) // TR\n                    cropCoordinates[3] = Offset(\n                        x = foregroundCrop.left / foregroundSize.width,\n                        y = 1.0f - (foregroundCrop.top / foregroundSize.height)\n                    ) // TL\n\n                    simpleQuadRenderer.draw(\n                        shader = shaderProgram.shader,\n                        texture = foreground,\n                        textureCoordinates = cropCoordinates\n                    )\n                }\n            }\n        }\n\n    fun isEnabled(): Boolean = maskTexture != null\n\n    fun dispose() {\n        if (isInitialized) {\n            finalMaskFrameBuffer.destroy()\n            isInitialized = false\n        }\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/mask/MaskShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.mask\n\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.Texture\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\n\ninternal class MaskShaderProgram(\n    shaderLibrary: ShaderLibrary,\n    private val shaderBinder: ShaderBinder\n) {\n    val shader: Shader = shaderLibrary\n        .loadShaderFromFile(vertFileName = \"simple_quad\", fragFileName = \"simple_mask\")\n        .apply {\n            bindUniformBlock(\n                SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n                SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n            )\n        }\n\n    fun setMask(mask: Texture2D) {\n        shader.bind(shaderBinder)\n        mask.bind(2)\n        shader.setInt(\"u_Mask\", 2)\n    }\n\n    fun setBackground(background: Texture) {\n        shader.bind(shaderBinder)\n        background.bind(3)\n        shader.setInt(\"u_Background\", 3)\n    }\n\n    fun destroy() {\n        shader.destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/noise/NoiseEffect.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.noise\n\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.util.trace\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.Texture2D\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport kotlin.properties.Delegates\n\ninternal class NoiseEffect(\n    shaderLibrary: ShaderLibrary,\n    shaderBinder: ShaderBinder,\n    private val framebufferPool: FramebufferPool,\n    private val simpleQuadRenderer: SimpleQuadRenderer\n) {\n\n    private val shader: Shader = shaderLibrary.loadShaderFromFile(\n        vertFileName = \"simple_quad\",\n        fragFileName = \"noise\",\n    ).apply {\n        bind(shaderBinder)\n        bindUniformBlock(\n            SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n            SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n        )\n    }\n\n    private var noiseTextureFrameBuffer: Framebuffer by Delegates.notNull()\n    private var effectFrameBuffer: Framebuffer by Delegates.notNull()\n    private var effectFrameSpec: FramebufferSpecification by Delegates.notNull()\n    private var isNoiseTextureInitialized: Boolean = false\n    private var isNoiseTextureDrawn: Boolean = false\n    private var noiseAlpha: Float = 0.0f\n\n    internal val outputFramebuffer: Framebuffer\n        get() = effectFrameBuffer\n\n    private fun setup(size: IntSize) {\n        if (shouldResize(size)) {\n            init(size)\n        }\n    }\n\n    private fun init(size: IntSize) = trace(\"init\") {\n        if (isNoiseTextureInitialized) {\n            noiseTextureFrameBuffer.destroy()\n            effectFrameBuffer.destroy()\n        }\n        effectFrameSpec = FramebufferSpecification(\n            size = size,\n            attachmentsSpec = FramebufferAttachmentSpecification()\n        )\n        noiseTextureFrameBuffer = Framebuffer.create(effectFrameSpec)\n        isNoiseTextureInitialized = true\n    }\n\n    private fun shouldResize(size: IntSize): Boolean {\n        return !isNoiseTextureInitialized || noiseTextureFrameBuffer.specification.size != size\n    }\n\n    private fun drawNoiseTextureOnce() {\n        if (!isNoiseTextureDrawn) {\n            trace(\"drawNoiseTextureOnce\") {\n                noiseTextureFrameBuffer.bind(Bind.DRAW)\n                simpleQuadRenderer.draw(shader = shader)\n            }\n            isNoiseTextureDrawn = true\n        }\n    }\n\n    fun applyEffect(texture: Texture2D, noiseAlpha: Float): Texture2D {\n        this.noiseAlpha = noiseAlpha\n        if (isEnabled()) {\n            trace(\"NoiseEffect#applyEffect\") {\n                setup(IntSize(width = texture.width, height = texture.height))\n                drawNoiseTextureOnce()\n                effectFrameBuffer = framebufferPool.acquire(effectFrameSpec)\n                effectFrameBuffer.bind(Bind.DRAW)\n                RenderCommand.clear()\n                RenderCommand.enableBlending()\n                simpleQuadRenderer.draw(texture = texture)\n                simpleQuadRenderer.draw(\n                    texture = noiseTextureFrameBuffer.colorAttachmentTexture,\n                    alpha = noiseAlpha\n                )\n            }\n            RenderCommand.disableBlending()\n            return effectFrameBuffer.colorAttachmentTexture\n        } else {\n            return texture\n        }\n    }\n\n    fun isEnabled(): Boolean = noiseAlpha >= MIN_NOISE_ALPHA\n\n    fun dispose() {\n        if (isNoiseTextureInitialized) {\n            noiseTextureFrameBuffer.destroy()\n        }\n        isNoiseTextureInitialized = false\n    }\n\n    private companion object {\n        const val MIN_NOISE_ALPHA = 0.05f\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/noise/NoiseShaderProgram.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.noise\n\nimport android.content.res.AssetManager\nimport dev.serhiiyaremych.imla.renderer.BufferLayout\nimport dev.serhiiyaremych.imla.renderer.shader.Shader\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderProgram\nimport dev.serhiiyaremych.imla.renderer.objects.defaultQuadBufferLayout\nimport dev.serhiiyaremych.imla.renderer.objects.defaultQuadVertexMapper\nimport dev.serhiiyaremych.imla.renderer.primitive.QuadVertex\n\ninternal class NoiseShaderProgram(assetManager: AssetManager) : ShaderProgram {\n    override val shader: Shader = Shader.create(\n        assetManager = assetManager,\n        vertexAsset = \"shader/default_quad.vert\",\n        fragmentAsset = \"shader/noise.frag\",\n    )\n    override val vertexBufferLayout: BufferLayout = defaultQuadBufferLayout\n    override val componentsCount: Int = vertexBufferLayout.elements.sumOf { it.type.components }\n\n    override fun mapVertexData(quadVertexBufferBase: List<QuadVertex>) =\n        defaultQuadVertexMapper(quadVertexBufferBase)\n\n    override fun destroy() {\n        shader.destroy()\n    }\n}"
  },
  {
    "path": "imla/src/main/java/dev/serhiiyaremych/imla/uirenderer/processing/preprocess/PreProcessFilter.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla.uirenderer.processing.preprocess\n\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Rect\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.toIntSize\nimport androidx.compose.ui.unit.toSize\nimport androidx.tracing.trace\nimport dev.romainguy.kotlin.math.Float2\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Bind\nimport dev.serhiiyaremych.imla.renderer.framebuffer.Framebuffer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferAttachmentSpecification\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferSpecification\nimport dev.serhiiyaremych.imla.renderer.RenderCommand\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderBinder\nimport dev.serhiiyaremych.imla.renderer.SimpleRenderer\nimport dev.serhiiyaremych.imla.renderer.framebuffer.FramebufferPool\nimport dev.serhiiyaremych.imla.renderer.shader.ShaderLibrary\nimport dev.serhiiyaremych.imla.renderer.util.SizeUtil\nimport dev.serhiiyaremych.imla.uirenderer.processing.SimpleQuadRenderer\nimport org.intellij.lang.annotations.Language\nimport kotlin.properties.Delegates\n\n@Language(\"GLSL\")\nprivate const val preProcessFragmentSource = \"\"\"\n            #version 300 es\n            precision mediump float;\n            uniform sampler2D u_Texture;\n            uniform vec2 u_TexelSize;\n            uniform vec2 u_ContentSize;\n            \n            in vec2 maskCoord;\n            in vec2 texCoord;\n            out vec4 color;\n            \n            const vec2 center = vec2(0.5);\n            const vec3 luma = vec3(0.3, 0.59, 0.11);\n            \n            // Credits: Bart Wronski, https://bartwronski.com/2022/03/07/fast-gpu-friendly-antialiasing-downsampling-filter/\n            const vec2 OFFSETS[8] = vec2[8](\n                vec2(-0.95777, -0.95777),\n                vec2(0.95777, -0.95777),\n                vec2(0.95777, 0.95777),\n                vec2(-0.95777, 0.95777),\n                vec2(-3.907, 0.0),\n                vec2(3.907, 0.0),\n                vec2(0.0, -3.907),\n                vec2(0.0, 3.907)\n            );\n            \n//            const vec2 OFFSETS[8] = vec2[8](\n//                vec2(-0.75777, -0.75777),\n//                vec2(0.75777, -0.75777),\n//                vec2(0.75777, 0.75777),\n//                vec2(-0.75777, 0.75777),\n//                vec2(-2.907, 0.0),\n//                vec2(2.907, 0.0),\n//                vec2(0.0, -2.907),\n//                vec2(0.0, 2.907)\n//            );\n\n            const float WEIGHTS[8] = float[8](\n                0.37487566,\n                0.37487566,\n                0.37487566,\n                0.37487566,\n                -0.12487566,\n                -0.12487566,\n                -0.12487566,\n                -0.12487566\n            );\n            \n            vec3 desaturate(vec3 color, float saturation) {\n                // Convert color to grayscale (luminosity method)\n                float gray = dot(color, luma);\n                return mix(vec3(gray), color, saturation);\n            }\n            \n            vec2 calculateClampedTexCoord(vec2 coord) {\n                vec2 halfSize = u_ContentSize * 0.5;\n                vec2 rectMin = center - halfSize;\n                vec2 rectMax = center + halfSize;\n                vec2 rectSize = u_ContentSize;\n            \n                vec2 texCoord = clamp((coord - rectMin) / rectSize, 0.0, 1.0);\n                return texCoord;\n            }\n            \n            float sdfRoundRect(vec2 point, vec2 rectSize, vec2 cornerRadius) {\n                cornerRadius = min(cornerRadius, rectSize);\n                vec2 dist = abs(point) - rectSize + cornerRadius;\n                float outsideDistance = length(max(dist, 0.0));\n                float insideDistance = min(max(dist.x, dist.y), 0.0);\n                return outsideDistance + insideDistance - length(cornerRadius);\n            }\n            \n            float calculateBlendFactor(vec2 coord) {\n                vec2 uv = coord * 2.0 - 1.0;\n                highp vec2 corners = vec2(0.01);\n                return sdfRoundRect(uv, u_ContentSize - 0.01, corners);\n            }\n            \n            vec3 applyChromaticAberration(sampler2D tex, vec2 uv, vec2 clampedUV) {\n                vec2 distFromCenter = (center-uv);\n                float circleSDF = (1.-smoothstep(0.0, max(u_ContentSize.x, u_ContentSize.y), length(distFromCenter)));\n                vec2 offset = circleSDF*(u_TexelSize*5.);\n                vec3 color;\n                color.r = texture(tex, clampedUV + offset).r;\n                color.g = texture(tex, clampedUV).g;\n                color.b = texture(tex, clampedUV - offset).b;\n                return color;\n            }\n\n            vec4 sampleTexture(sampler2D tex, vec2 uv) {\n                float blendFactor = calculateBlendFactor(uv);\n\n                vec2 clampledUV = calculateClampedTexCoord(uv);\n                vec4 sharpSample = texture(tex, clampledUV);\n                \n                vec3 color = applyChromaticAberration(tex, uv, clampledUV);\n                sharpSample = vec4(color, 1.0);\n                \n\n                float mipLevel = 5.0;\n                vec4 mipSample = textureLod(tex, clampledUV, mipLevel);\n                \n                // debug draw sdf mask\n                // vec3 rectColor = vec3(0.1, 0.4, 0.7); // Rectangle color\n                // vec3 bgColor = vec3(1.0); // Background color\n                // vec4 sdfMaskColor = vec4(mix(bgColor, rectColor, alpha), 1.0);\n    \n                // Edge smoothing\n                float alpha = 1.0 - smoothstep(-0.03, 0.0, blendFactor);\n                return mix(vec4(desaturate(mipSample.rgb, alpha+0.4), 1.0), sharpSample, alpha);\n            }\n\n            \n            void main() {\n                  vec4 baseColor = vec4(0.0);\n                  \n                  float totalWeight = 0.0;\n                  for (int i = 0; i < 8; i++) {\n                      vec2 sampleCoord = texCoord + OFFSETS[i] * u_TexelSize;\n                      vec4 sampleColor = sampleTexture(u_Texture, sampleCoord);\n                      baseColor += WEIGHTS[i] * sampleColor;\n                      totalWeight += WEIGHTS[i];\n                  }\n  \n                  totalWeight = max(totalWeight, 1e-5); // Prevent division by zero\n                  baseColor /= totalWeight;\n                  color = baseColor;\n            }\n        \"\"\"\n\ninternal class PreProcessFilter(\n    shaderLibrary: ShaderLibrary,\n    private val framebufferPool: FramebufferPool,\n    private val simpleQuadRenderer: SimpleQuadRenderer,\n    private val shaderBinder: ShaderBinder\n) {\n\n    private val preProcessShader = shaderLibrary.loadShader(\n        name = \"preProcess\",\n        fragmentSrc = preProcessFragmentSource.trimIndent(),\n    ).apply {\n        bind(shaderBinder)\n        setInt(\"u_Texture\", 0)\n        bindUniformBlock(\n            SimpleRenderer.TEXTURE_DATA_UBO_BLOCK,\n            SimpleRenderer.TEXTURE_DATA_UBO_BINDING_POINT\n        )\n    }\n\n    private var cutFboSpec: FramebufferSpecification by Delegates.notNull()\n    private var targetFboSpec: FramebufferSpecification by Delegates.notNull()\n\n    private var isInitialized: Boolean = false\n\n    var contentCrop = Rect.Zero\n        private set\n\n    var leftExtend: Float = 0f\n    var topExtend: Float = 0f\n    var rightExtend: Float = 0f\n    var bottomExtend: Float = 0f\n\n    private var extendedContentBounds: Rect = Rect.Zero\n\n    fun preProcess(rootFbo: Framebuffer, area: Rect): Framebuffer = trace(\"preProcess\") {\n        init(rootFbo, area)\n\n        val target = framebufferPool.acquire(targetFboSpec)\n        val cut = framebufferPool.acquire(cutFboSpec)\n\n        cutArea(rootFbo, cut)\n\n        val targetSize = target.specification.size / target.specification.downSampleFactor\n        val textureSize = cut.specification.size\n\n        trace(\"aaDownsampling\") {\n            target.bind(Bind.DRAW)\n            RenderCommand.clear()\n            preProcessShader.bind(shaderBinder)\n            preProcessShader.setFloat2(\n                name = \"u_TexelSize\",\n                value = Float2(x = 1.0f / targetSize.width, y = 1.0f / targetSize.height)\n            )\n\n            val scale = calculateFitScale(inner = textureSize, outer = targetSize)\n\n            val scaledWidth = textureSize.width * scale\n            val scaledHeight = textureSize.height * scale\n\n            val normalizedContentBounds = Float2(\n                x = scaledWidth / targetSize.width.toFloat(),\n                y = scaledHeight / targetSize.height.toFloat(),\n            )\n            preProcessShader.setFloat2(\n                name = \"u_ContentSize\",\n                value = normalizedContentBounds\n            )\n            simpleQuadRenderer.draw(shader = preProcessShader, cut.colorAttachmentTexture)\n            target.colorAttachmentTexture.bind()\n            target.colorAttachmentTexture.generateMipMaps()\n        }\n\n        return target\n    }\n\n    private fun calculateFitScale(inner: IntSize, outer: IntSize): Float {\n        val innerIsBigger = (inner.width * inner.height) > (outer.width * outer.height)\n        val widthScale = outer.width / inner.width.toFloat()\n        val heightScale = outer.height / inner.height.toFloat()\n        val scale = minOf(widthScale, heightScale)\n        return if (innerIsBigger || scale > 1f) scale else 1f\n    }\n\n    private fun cutArea(\n        src: Framebuffer,\n        dst: Framebuffer\n    ) = trace(\"cutArea\") {\n        src.bind(Bind.READ, updateViewport = false)\n        dst.bind(Bind.DRAW)\n\n        RenderCommand.blitFramebuffer(\n            srcX0 = extendedContentBounds.left.toInt(),\n            srcX1 = extendedContentBounds.right.toInt(),\n            srcY0 = src.specification.size.height - (extendedContentBounds.top).toInt(),\n            srcY1 = src.specification.size.height - (extendedContentBounds.bottom).toInt(),\n            dstX0 = 0, dstX1 = dst.specification.size.width.toInt(),\n            dstY0 = 0, dstY1 = dst.specification.size.height.toInt()\n        )\n\n        trace(\"generateMipMaps\") {\n            dst.colorAttachmentTexture.bind()\n            dst.colorAttachmentTexture.generateMipMaps()\n        }\n    }\n\n    private fun init(rootFbo: Framebuffer, area: Rect) {\n        if (isInitialized.not()) {\n            leftExtend = minOf(EXPAND_CONTENT_PX, area.left)\n            rightExtend =\n                minOf(EXPAND_CONTENT_PX, rootFbo.specification.size.width - area.right)\n            topExtend = minOf(EXPAND_CONTENT_PX, area.top)\n            bottomExtend =\n                minOf(EXPAND_CONTENT_PX, rootFbo.specification.size.height - area.bottom)\n\n            extendedContentBounds = Rect(\n                topLeft = Offset(\n                    x = (area.left - leftExtend),\n                    y = (area.top - topExtend)\n                ),\n                bottomRight = Offset(\n                    x = (area.right + rightExtend),\n                    y = (area.bottom + bottomExtend)\n                )\n            )\n            val extendedSize = extendedContentBounds.size.toIntSize()\n            val spec = FramebufferSpecification(\n                size = extendedSize,\n                attachmentsSpec = FramebufferAttachmentSpecification.singleColor(flip = true)\n            )\n            cutFboSpec = spec.copy(\n                attachmentsSpec = FramebufferAttachmentSpecification.singleColor(\n                    mipmapFiltering = true,\n                    flip = true\n                )\n            )\n            // Framebuffer.create(cutSpec)\n            val potUpSize = SizeUtil.closestPOTUp(extendedSize)\n            // render target if half size of pot input texture\n            targetFboSpec = spec.copy(size = potUpSize * 0.5f)\n            //Framebuffer.create(spec.copy(size = potUpSize * 0.5f))\n\n            val targetSize = targetFboSpec.size\n            val fitScale = calculateFitScale(\n                inner = extendedContentBounds.size.toIntSize(),\n                outer = targetSize\n            )\n            val fitToScaleSize = IntSize(\n                width = (extendedContentBounds.size.width * fitScale).toInt(),\n                height = (extendedContentBounds.size.height * fitScale).toInt()\n            )\n            val centeredBounds = Rect(\n                offset = Offset(\n                    x = (targetSize.width - fitToScaleSize.width) / 2f,\n                    y = (targetSize.height - fitToScaleSize.height) / 2f\n                ),\n                size = fitToScaleSize.toSize()\n            )\n            contentCrop = Rect(\n                left = centeredBounds.left + (leftExtend * fitScale),\n                top = centeredBounds.top + (topExtend * fitScale),\n                right = centeredBounds.right - (rightExtend * fitScale),\n                bottom = centeredBounds.bottom - (bottomExtend * fitScale),\n            )\n\n            isInitialized = true\n        }\n    }\n\n    companion object {\n        const val EXPAND_CONTENT_PX = 20f\n    }\n}\n\ninternal operator fun Rect.times(value: Float): Rect {\n    return Rect(\n        topLeft = Offset(\n            x = left * value,\n            y = top * value\n        ),\n        bottomRight = Offset(\n            x = right * value,\n            y = bottom * value\n        )\n    )\n}\n\ninternal operator fun IntSize.times(value: Float): IntSize {\n    return IntSize(\n        width = (this.width * value).toInt(),\n        height = (this.height * value).toInt()\n    )\n}\n"
  },
  {
    "path": "imla/src/test/java/dev/serhiiyaremych/imla/ExampleUnitTest.kt",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\npackage dev.serhiiyaremych.imla\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "settings.gradle.kts",
    "content": "/*\n * Copyright 2024, Serhii Yaremych\n * SPDX-License-Identifier: MIT\n */\n\n@file:Suppress(\"UnstableApiUsage\")\n\ninclude(\":benchmark\")\n\n\npluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.name = \"imla-android\"\ninclude(\":app\")\ninclude(\":imla\")\n"
  }
]