Attach any file, take shares from other apps, and survive a backwards highlight
Attachments were images only. Now any file can be attached: from the file chooser behind the "+" menu, or from Android's share sheet, which the app is now in. An image still goes to the model as a picture; anything else is stored under its own name (`<hex>-<name>`, cleaned by `safe_file_name`) and the Claude driver ends the message with `Attached file: /abs/path`, since the CLI reads files by path and a model cannot be shown a trace. The user-message field is renamed `images` -> `attachments` on both sides, with a serde alias reading the rows written before. A share arrives before anyone has said which session it is for, so it is held in AppRoot with a banner on the list until a session takes it; an open session takes it at once. Unreadable shares are reported beside the composer, not thrown. The tool card crashed the app when opened on a command holding a quoted glob such as `-path '*/.git/*'`: highlights 1.1.0's shell lexer answers `x '*/a/*'` with a span whose end is before its start, and AnnotatedString refuses the range. Such spans are dropped; the library is the place for the fix. The echo driver gains `/bash <command>` so a card with a given command can be produced on the emulator. ui-sandbox.sh's token salvage read the tokens block's close only at a line start, ran past the compact `),],` the server writes, and copied `setups` into the new config twice, which the server then refused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4bc69e8f9c
commit
6180663f14
25 files changed
+672
-165
No files matched your search
@@ -8,8 +8,12 @@ import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -34,7 +38,8 @@ import androidx.compose.ui.unit.sp
|
||||
* anywhere else on the screen.
|
||||
*
|
||||
* Scrolls sideways rather than wrapping or shrinking: the row keeps one thumbnail size whatever is
|
||||
* in it, so four attachments look like four of the same thing rather than four smaller ones.
|
||||
* in it, so four attachments look like four of the same thing rather than four smaller ones. A file
|
||||
* is a tile of the same height carrying its name, since a name is all there is to show of it.
|
||||
*/
|
||||
@Composable
|
||||
fun PendingAttachments(
|
||||
@@ -49,7 +54,10 @@ fun PendingAttachments(
|
||||
modifier = modifier.horizontalScroll(rememberScrollState()).padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
refs.forEach { ref -> PendingThumbnail(settings, sessionId, ref) { onRemove(ref) } }
|
||||
refs.forEach { ref ->
|
||||
if (isImageRef(ref)) PendingThumbnail(settings, sessionId, ref) { onRemove(ref) }
|
||||
else PendingFile(ref) { onRemove(ref) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,4 +130,32 @@ private fun PendingThumbnail(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One attached file: its name, tap to take it back off. The same height and removal as a thumbnail,
|
||||
* so a row of mixed attachments is one row; the cross sits after the name because a tile this wide
|
||||
* has no corner the eye goes to.
|
||||
*/
|
||||
@Composable
|
||||
private fun PendingFile(ref: String, onRemove: () -> Unit) {
|
||||
val name = attachmentName(ref)
|
||||
val shape = RoundedCornerShape(8.dp)
|
||||
Row(
|
||||
Modifier.height(THUMBNAIL)
|
||||
.clip(shape)
|
||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.clickable(onClick = onRemove)
|
||||
.semantics { contentDescription = "Attached file $name, tap to remove" }
|
||||
.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FileName(name, Modifier.widthIn(max = FILE_TILE_WIDTH))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Glyph(CLOSE_GLYPH, colour = MaterialTheme.colorScheme.onSurface, size = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
private val THUMBNAIL = 64.dp
|
||||
|
||||
/** Wide enough for most names whole; longer ones lose their middle, keeping both ends. */
|
||||
private val FILE_TILE_WIDTH = 200.dp
|
||||
Reference in new issue
Block a user