{"id":19521788,"url":"https://github.com/smarttoolfactory/flexible-chat-box","last_synced_at":"2025-04-26T09:31:58.802Z","repository":{"id":45321613,"uuid":"439980705","full_name":"SmartToolFactory/Flexible-Chat-Box","owner":"SmartToolFactory","description":"Flexible chat row written with Jetpack Compose that positions message and message status based on number of message lines, message width and parent width. And resizable Subcomposelayout that remasures sibling composables to match their widths' to longest composable that matches quote and message width to max width.","archived":false,"fork":false,"pushed_at":"2023-12-24T13:48:17.000Z","size":2180,"stargazers_count":59,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-04T10:25:21.527Z","etag":null,"topics":["android","chat","chat-row","custom-view","jetpack-compose","material-ui","subcomposelayout"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SmartToolFactory.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-19T22:20:34.000Z","updated_at":"2025-01-04T11:13:26.000Z","dependencies_parsed_at":"2024-11-11T00:34:59.060Z","dependency_job_id":"82b8accd-86af-40b2-abd4-545688fe43bb","html_url":"https://github.com/SmartToolFactory/Flexible-Chat-Box","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartToolFactory%2FFlexible-Chat-Box","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartToolFactory%2FFlexible-Chat-Box/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartToolFactory%2FFlexible-Chat-Box/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmartToolFactory%2FFlexible-Chat-Box/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SmartToolFactory","download_url":"https://codeload.github.com/SmartToolFactory/Flexible-Chat-Box/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250967125,"owners_count":21515541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","chat","chat-row","custom-view","jetpack-compose","material-ui","subcomposelayout"],"created_at":"2024-11-11T00:34:56.051Z","updated_at":"2025-04-26T09:31:58.362Z","avatar_url":"https://github.com/SmartToolFactory.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Flexible Chat Row and Resizable SubcomposeLayout\n\n\nFlexible chat row, `ChatFlexBoxLayout`, positions its elements based on number of lines message text has,\nparent width, message and message status width.\n`SubcomposeColumn` created using **SubComposeLayout** which remeasures its children based on\nlongest children. This is useful for matching quote message and message length after position\ncalculation. These two composables together create dynamic message rows that position children, and positions message, message date and message status.\n\nThere are 3 implementation files to to try out `ChatFlexBoxLayout`, and `SubcomposeColumn` are\n`DemoFullChat.kt`, `DemoChatAndWidth.kt`, and `DemoResizableColumn.kt`\n\n| Full Chat                                               | Chat Width                                               | Resizable                                               |\n|---------------------------------------------------------|----------------------------------------------------------|---------------------------------------------------------|\n| \u003cimg src=\"./screenshots/full_chat_implementation.gif\"/\u003e | \u003cimg src=\"./screenshots/chat_width_implementation.png\"/\u003e | \u003cimg src=\"./screenshots/resizable_implementation.gif\"/\u003e |\n\n\n### ChatFlexBoxLayout\nThis layout  measures and positions message, and another container that uses message\ndate or message date + message received status like messaging apps do.\n\nThere are 4 possible conditions to position message and stats\n\n* Single line message text and status is shorter than parent width(Magenta in sample)\n* Single line message text and status is longer than parent width(Green in sample)\n* Multiple line message with last line width and message status is shorter message text length + right padding(Red in sample)\n* Multiple line message with last line width and message status is longer message text length + right padding(Yellow in sample)\n\n#### Usage\n\n```kotlin\nChatFlexBoxLayout(\n    modifier: Modifier = Modifier,\n    text: String,\n    color: Color = Color.Unspecified,\n    fontSize: TextUnit = 16.sp,\n    fontStyle: FontStyle? = null,\n    fontWeight: FontWeight? = null,\n    fontFamily: FontFamily? = null,\n    letterSpacing: TextUnit = TextUnit.Unspecified,\n    textDecoration: TextDecoration? = null,\n    textAlign: TextAlign? = null,\n    lineHeight: TextUnit = TextUnit.Unspecified,\n    overflow: TextOverflow = TextOverflow.Clip,\n    softWrap: Boolean = true,\n    maxLines: Int = Int.MAX_VALUE,\n    messageStat: @Composable () -\u003e Unit,\n    onMeasure: ((ChatRowData) -\u003e Unit)? = null\n)\n```\nSince `TextLayout` is required to get text length, last line width and other properties it's internal to this composable but properties of `Text`composable can be set with same as it's done using `Text`.\n\n`onMeasure` returns internal layout data of this row, that's how i set colors differently in **chat width sample**.\n`messageStat` is composable that contains message text or status if you need to.\n\n```kotlin\n    ChatFlexBoxLayout(\n        modifier = Modifier\n            .background(color, shape = RoundedCornerShape(8.dp))\n            .padding(start = 2.dp, top = 2.dp, end = 4.dp, bottom = 2.dp),\n        text = text,\n        messageStat = {\n            MessageTimeText(\n                modifier = Modifier.wrapContentSize(),\n                messageTime = messageTime,\n                messageStatus = messageStatus\n            )\n        },\n        onMeasure = { chatRowData -\u003e\n            color = when (chatRowData.measuredType) {\n                0 -\u003e Color.Yellow\n                1 -\u003e Color.Red\n                2 -\u003e Color.Green\n                else -\u003e Color.Magenta\n            }\n        }\n    )\n```\n\nAnother overload of `ChatFlexBoxLayout` takes two Composables as arguments which \ncustom **message** Composable can be used instead of String or AnnotatedString.\n```kotlin\n@Composable\nfun ChatFlexBoxLayout(\n    modifier: Modifier,\n    message: @Composable () -\u003e Unit,\n    messageStat: @Composable () -\u003e Unit = {},\n    chatRowData: ChatRowData,\n    onMeasure: ((ChatRowData) -\u003e Unit)? = null\n) {\n  //...\n}\n```\n\nUse with `remember { ChatRowData() }` to provide stats and invoke `measureText(chatRowData, it)`\nto set text properties to this data\n```kotlin\n            val chatRowData = remember { ChatRowData() }\n\n            ChatFlexBoxLayout(\n                modifier = Modifier.padding(\n                    start = 2.dp,\n                    top = 2.dp,\n                    end = 8.dp,\n                    bottom = 2.dp\n                ),\n                message = {\n                    Text(\n                        modifier = Modifier.padding(horizontal = 6.dp, vertical = 4.dp),\n                        text = text,\n                        fontSize = 16.sp,\n                        onTextLayout = {\n                            // ⚠️ THIS IS REQUIRED TO MEASURE Text size and get line count\n                            measureText(chatRowData, it)\n                        }\n                    )\n                },\n                messageStat = {\n                    MessageTimeText(\n                        modifier = Modifier.wrapContentSize(),\n                        messageTime = messageTime,\n                        messageStatus = messageStatus\n                    )\n                },\n                chatRowData = chatRowData\n            )\n        }\n```\n\n## SubcomposeColumn\n\nThis layout uses SubcomposeLayout to find longest child then remeasure its children\nand set every child to max width. There are 2 overloads of this Composable if\nyou only need to use direct 2 children you can use which returns size of main component\nas `IntSize`\n\n```kotlin\nfun SubcomposeColumn(\n    modifier: Modifier = Modifier,\n    mainContent: @Composable () -\u003e Unit = {},\n    dependentContent: @Composable (IntSize) -\u003e Unit\n) {\n...\n}\n```\n\nThis overloaded function is suitable for layout with any number of children\n\n```kotlin\n@Composable\nfun SubcomposeColumn(\n    modifier: Modifier = Modifier,\n    content: @Composable () -\u003e Unit = {},\n) {\n\n    SubcomposeLayout(modifier = modifier) { constraints -\u003e\n\n        var recompositionIndex = 0\n\n        var placeables: List\u003cPlaceable\u003e = subcompose(recompositionIndex++, content).map {\n            it.measure(constraints)\n        }\n\n        val maxSize =\n            placeables.fold(IntSize.Zero) { currentMax: IntSize, placeable: Placeable -\u003e\n                IntSize(\n                    width = maxOf(currentMax.width, placeable.width),\n                    height = currentMax.height + placeable.height\n                )\n            }\n\n        // Remeasure every element using width of longest item as minWidth of Constraint\n        if (!placeables.isNullOrEmpty() \u0026\u0026 placeables.size \u003e 1) {\n            placeables = subcompose(recompositionIndex, content).map { measurable: Measurable -\u003e\n                measurable.measure(Constraints(maxSize.width, constraints.maxWidth))\n            }\n        }\n\n        layout(maxSize.width, maxSize.height) {\n            var yPos = 0\n            placeables.forEach { placeable: Placeable -\u003e\n                placeable.placeRelative(0, yPos)\n                yPos += placeable.height\n            }\n\n        }\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmarttoolfactory%2Fflexible-chat-box","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmarttoolfactory%2Fflexible-chat-box","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmarttoolfactory%2Fflexible-chat-box/lists"}