Restore directory navigation on Android back

This commit is contained in:
iris committed 2026-09-11 00:28:55 -04:00
1 parent 59965d314f
commit 22f263ccce
2 files changed
+45 -25

No files matched your search

@@ -72,13 +72,18 @@ private sealed class Spot(val path: String) {
class Doc(path: String, val directory: Dir) : Spot(path)
}
private enum class UnsavedDestination {
Directory,
Session,
}
/**
* The files on the machine a session runs on: browse them, read one, change one.
*
* Drawn **over** the session rather than instead of it (see [AppRoot]), so its event stream keeps
* flowing and coming back from a file costs nothing. Back from a file returns to its directory;
* back from a directory closes the explorer and returns to the session. Directory navigation stays
* inside the listing, where its `..` row is explicit.
* flowing and coming back from a file costs nothing. Android back returns from a file to its
* directory, then walks up the directory tree. The header's back button closes the explorer and
* returns directly to the session.
*
* Every directory that has been visited is kept for as long as this is open; the refresh glyph is
* how one gets asked again on purpose, and creating something refetches the directory it was
@@ -98,11 +103,10 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
val listings = remember { mutableStateMapOf<String, LoadState<Listing>>() }
var creating by remember { mutableStateOf(false) }
// Edit mode and whether anything has been typed live here rather than in the pane below,
// because they are what back has to know about -- and back arrives from two places, the arrow
// and the platform's own gesture, which must mean the same thing.
// because both ways out have to ask before discarding it.
var editing by remember { mutableStateOf(false) }
var dirty by remember { mutableStateOf(false) }
var askUnsaved by remember { mutableStateOf(false) }
var unsavedDestination by remember { mutableStateOf<UnsavedDestination?>(null) }
fun go(spot: Spot) {
editing = false
@@ -110,11 +114,23 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
here = spot
}
fun back() {
when {
editing && dirty -> askUnsaved = true
here is Spot.Doc -> go((here as Spot.Doc).directory)
else -> onClose()
fun leave(destination: UnsavedDestination) {
if (editing && dirty) {
unsavedDestination = destination
} else if (destination == UnsavedDestination.Directory) {
go((here as Spot.Doc).directory)
} else {
onClose()
}
}
fun systemBack() {
when (val spot = here) {
is Spot.Doc -> leave(UnsavedDestination.Directory)
is Spot.Dir -> {
val path = (listings[spot.path] as? LoadState.Loaded)?.value?.path ?: spot.path
parentOf(path)?.let { go(Spot.Dir(it)) } ?: onClose()
}
}
}
@@ -131,7 +147,7 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
}
}
BackHandler(onBack = ::back)
BackHandler(onBack = ::systemBack)
Box(
Modifier.fillMaxSize()
@@ -151,7 +167,7 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
title = baseName(at),
path = at,
machine = target.setupName,
onBack = ::back,
onBack = { leave(UnsavedDestination.Session) },
) {
GlyphButton(
REFRESH_GLYPH,
@@ -178,20 +194,23 @@ fun FilesScreen(settings: ServerSettings, target: FilesTarget, onClose: () -> Un
editing = editing,
onEditing = { editing = it },
onDirty = { dirty = it },
onBack = ::back,
onBack = { leave(UnsavedDestination.Session) },
)
}
}
}
if (askUnsaved) {
unsavedDestination?.let { destination ->
UnsavedDialog(
onDiscard = {
askUnsaved = false
val spot = here
if (spot is Spot.Doc) go(spot.directory) else onClose()
unsavedDestination = null
if (destination == UnsavedDestination.Directory) {
go((here as Spot.Doc).directory)
} else {
onClose()
}
},
onCancel = { askUnsaved = false },
onCancel = { unsavedDestination = null },
)
}