A cheap attempt at a native Bluesky client for Android
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

SkeetView: Refactor layout for better thread line continuity

Separate the layout logic for nested and top-level posts to ensure the thread line spans the full height of the post.

- Use a simple `Column` layout for nested (embedded) posts.
- Use a two-column `Row` layout for top-level posts, allowing the avatar and thread line to span the full vertical height of the content.
- Move `SkeetReason` inside the right-hand content column for top-level posts.
- Adjust padding and vertical divider constraints to improve the visual connection between posts in a thread.

geesawra 6000624b 447ec437

+81 -75
+81 -75
app/src/main/java/industries/geesawra/monarch/SkeetView.kt
··· 114 114 115 115 val minSize = 44.dp 116 116 117 - Column( 118 - modifier = 119 - modifier 117 + if (nested) { 118 + // Embedded posts: simple stacked layout 119 + Column( 120 + modifier = modifier 120 121 .clip(MaterialTheme.shapes.medium) 121 122 .clickable { 122 123 Log.d("SkeetView", skeet.content) 123 124 onShowThread(skeet) 124 125 } 125 - .padding( 126 - top = 8.dp, 127 - start = if (nested) 10.dp else 16.dp, 128 - end = if (nested) 10.dp else 16.dp, 129 - bottom = 8.dp 130 - ) 131 - ) { 132 - SkeetReason( 133 - modifier = Modifier.padding(start = 4.dp), 134 - skeet = skeet, 135 - showInReplyTo, 136 - renderingReplyNotif, 137 - renderingMention 138 - ) 126 + .padding(top = 8.dp, start = 10.dp, end = 10.dp, bottom = 8.dp) 127 + ) { 128 + SkeetReason( 129 + modifier = Modifier.padding(start = 4.dp), 130 + skeet = skeet, 131 + showInReplyTo, 132 + renderingReplyNotif, 133 + renderingMention 134 + ) 139 135 140 - if (nested) { 141 - // Embedded posts: avatar + header in a row, content below 142 136 Row( 143 137 modifier = Modifier 144 138 .fillMaxWidth() ··· 167 161 } 168 162 169 163 SkeetContent(skeet, nested, disableEmbeds, onShowThread, viewModel) 170 - } else { 171 - // Top-level posts: two-column layout with thread line for header, 172 - // content and actions span full width below 173 - Row( 164 + } 165 + } else { 166 + // Top-level posts: two-column layout, thread line spans full height 167 + Row( 168 + modifier = modifier 169 + .clip(MaterialTheme.shapes.medium) 170 + .clickable { 171 + Log.d("SkeetView", skeet.content) 172 + onShowThread(skeet) 173 + } 174 + .padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = if (inThread) 0.dp else 8.dp) 175 + .height(IntrinsicSize.Min) 176 + ) { 177 + // Left column: avatar + thread line (spans full post height) 178 + Column( 179 + horizontalAlignment = Alignment.CenterHorizontally, 174 180 modifier = Modifier 175 - .fillMaxWidth() 176 - .height(IntrinsicSize.Min), 181 + .width(minSize) 182 + .fillMaxHeight() 177 183 ) { 178 - // Left column: avatar + thread line 179 - Column( 180 - horizontalAlignment = Alignment.CenterHorizontally, 184 + AsyncImage( 185 + model = ImageRequest.Builder(LocalContext.current) 186 + .data(skeet.authorAvatarURL) 187 + .crossfade(true) 188 + .build(), 189 + contentDescription = "Avatar", 181 190 modifier = Modifier 182 - .width(minSize) 183 - .fillMaxHeight() 184 - ) { 185 - AsyncImage( 186 - model = ImageRequest.Builder(LocalContext.current) 187 - .data(skeet.authorAvatarURL) 188 - .crossfade(true) 189 - .build(), 190 - contentDescription = "Avatar", 191 + .size(minSize) 192 + .clip(CircleShape) 193 + ) 194 + if (inThread) { 195 + VerticalDivider( 196 + thickness = 3.dp, 191 197 modifier = Modifier 192 - .size(minSize) 193 - .clip(CircleShape) 198 + .weight(1f) 199 + .padding(top = 4.dp) 200 + .clip(RoundedCornerShape(12.dp)), 201 + color = MaterialTheme.colorScheme.outlineVariant 194 202 ) 195 - if (inThread) { 196 - VerticalDivider( 197 - thickness = 3.dp, 198 - modifier = Modifier 199 - .weight(1f) 200 - .padding(vertical = 4.dp) 201 - .clip(RoundedCornerShape(12.dp)), 202 - color = MaterialTheme.colorScheme.outlineVariant 203 - ) 204 - } 205 203 } 204 + } 206 205 207 - // Right column: header + content + actions 208 - Column( 209 - modifier = Modifier 210 - .weight(1f) 211 - .padding(start = 12.dp) 212 - .sizeIn(minHeight = minSize), 213 - ) { 214 - SkeetHeader( 215 - skeet = skeet, 216 - showLabels = showLabels, 217 - labelDisplayName = { viewModel?.labelDisplayName(it) }, 218 - labelDescription = { viewModel?.labelDescription(it) }, 219 - labelerAvatar = { viewModel?.labelerAvatar(it) } 220 - ) 206 + // Right column: reason + header + content + actions 207 + Column( 208 + modifier = Modifier 209 + .weight(1f) 210 + .padding(start = 12.dp) 211 + .sizeIn(minHeight = minSize), 212 + ) { 213 + SkeetReason( 214 + modifier = Modifier.padding(start = 4.dp), 215 + skeet = skeet, 216 + showInReplyTo, 217 + renderingReplyNotif, 218 + renderingMention 219 + ) 221 220 222 - SkeetContent(skeet, nested, disableEmbeds, onShowThread, viewModel) 221 + SkeetHeader( 222 + skeet = skeet, 223 + showLabels = showLabels, 224 + labelDisplayName = { viewModel?.labelDisplayName(it) }, 225 + labelDescription = { viewModel?.labelDescription(it) }, 226 + labelerAvatar = { viewModel?.labelerAvatar(it) } 227 + ) 223 228 224 - if (!disableEmbeds) { 225 - TimelinePostActionsView( 226 - onReplyTap = onReplyTap, 227 - modifier = Modifier 228 - .fillMaxWidth() 229 - .padding(top = 4.dp), 230 - timelineViewModel = viewModel, 231 - skeet = skeet, 232 - inThread = inThread 233 - ) 234 - } 229 + SkeetContent(skeet, nested, disableEmbeds, onShowThread, viewModel) 230 + 231 + if (!disableEmbeds) { 232 + TimelinePostActionsView( 233 + onReplyTap = onReplyTap, 234 + modifier = Modifier 235 + .fillMaxWidth() 236 + .padding(top = 4.dp), 237 + timelineViewModel = viewModel, 238 + skeet = skeet, 239 + inThread = inThread 240 + ) 235 241 } 236 242 } 237 243 }