Preview files inside an archive, and search every volume from an index
The archive preview could say what a zip held but not show any of it, and the search could only answer "is it here?" for the folder tree the user was standing in. Both are now answered. ArchiveEngine gains a single-entry reader: a ZIP the backend can seek jumps straight at the entry's data through the central directory, 7z scans its index, a streaming container decompresses past whatever is in front of the entry, and a compression wrapper has exactly one entry whose name is derived from the archive's own. extractEntry is rewritten on top of it, so there is one implementation of "reach this entry" rather than two that could disagree, and the now-dead extractFromZip and only= parameters are removed. A tap on a row now previews and the row's action extracts, which is what a row means everywhere else in this app. Text and markup are read into memory, so the commonest question - what does this readme say - writes nothing at all and works in both storage modes. Everything else needs a file a decoder can open, so the entry is copied into the app's own cache, one folder per preview and under its own name so the viewer's title bar does not read copy-0.png, and deleted when the preview closes with a non-recursive delete. The copy is a real path, so image, PDF and media previews work even in the SAF fallback, where the browsed files cannot be previewed at all. A copy has no file to save back to, so the text editor shows no Save and markup offers no editor; closing returns to the archive rather than out of it. SearchIndex is a small port over SQLite beside the trash index, inheriting that one's doctrine that an index is not the truth: a reader drops rows whose file has gone and forgets them, freshness is reported rather than assumed, and a volume's rows are replaced wholesale so an interrupted pass leaves the previous listing rather than half of this one. Timestamps are written only when a pass finishes, so a walk cut short by a new keystroke reads as older than it is, which is the safe direction to be wrong in. Names are matched with NameMatcher's semantics translated into LIKE - substring, anchored glob, and a literal % or _ escaped - because a search that quietly widens what was typed is what an index does badly. The search field gains a scope toggle, and every search everywhere carries the age of the index and a way to rebuild it, because a search that silently omits what arrived since the last walk is worse than one that spends a minute being sure. Rows are written in batches of 500 and the counter reported every 100, since a row reading "0 files" for the whole first second reads as nothing happening. Deliberately not a foreground service: a pass runs while the screen is up and stops when it is not. Verified on the API 35 phone rather than inferred: a .txt and a .png inside a real zip, both previewed from the archive list with their own names, the copy gone on close and the archive list restored behind it; then the first search building the index with a full walk, and a search after an app restart answering instantly and finding a file in Download/cline-zip while browsing Internal storage. Watching it caught two lies, both fixed here: the empty state claimed "Nothing matches" while the index was still being written, and two progress bars stacked on each other. 268 JVM tests, lint clean, release APK builds and installs. The store listing gains the archive preview and the scoped search and is re-measured at 2730/4000.
This commit is contained in:
@@ -41,6 +41,15 @@
|
||||
</plurals>
|
||||
<string name="browser_search_hint">Search this folder</string>
|
||||
<string name="browser_search_clear">Clear search</string>
|
||||
<!-- The scope toggle: its label says what tapping it *does*, so it reads as an action. -->
|
||||
<string name="search_scope_everywhere">Search everywhere</string>
|
||||
<string name="search_scope_folder">Search this folder only</string>
|
||||
<!-- %1$s is how many files have been seen so far, e.g. "12,340 files". -->
|
||||
<string name="search_indexing">Indexing %1$s…</string>
|
||||
<!-- %1$s is a timestamp, in the same unlocalised form the details sheet uses. -->
|
||||
<string name="search_index_at">Index updated %1$s</string>
|
||||
<string name="search_index_missing">Not indexed yet</string>
|
||||
<string name="search_index_rebuild">Rebuild</string>
|
||||
|
||||
<!-- ===== Browser: actions ===== -->
|
||||
<string name="browser_action_new_folder">New folder</string>
|
||||
@@ -52,6 +61,7 @@
|
||||
<string name="browser_action_rename">Rename</string>
|
||||
<string name="browser_action_delete">Delete</string>
|
||||
<string name="browser_action_share">Share</string>
|
||||
<string name="browser_action_open_with">Open with</string>
|
||||
<string name="browser_action_select_all">Select all</string>
|
||||
<string name="browser_action_clear_selection">Clear selection</string>
|
||||
<string name="browser_action_undo">Undo</string>
|
||||
@@ -69,6 +79,9 @@
|
||||
<string name="trash_title">Trash</string>
|
||||
<string name="trash_empty">Nothing in the trash</string>
|
||||
<string name="trash_restore">Restore</string>
|
||||
<string name="trash_restore_all">Restore all</string>
|
||||
<!-- %1$s is the folder a trashed item will be put back into. -->
|
||||
<string name="trash_restore_to">Restores to %1$s</string>
|
||||
<string name="browser_action_trash">Trash</string>
|
||||
<string name="browser_action_more">More options</string>
|
||||
<string name="browser_action_refresh">Refresh</string>
|
||||
@@ -171,6 +184,17 @@
|
||||
<string name="message_cancelled">Cancelled</string>
|
||||
<string name="message_saved">Saved</string>
|
||||
<string name="message_save_failed">The file could not be saved</string>
|
||||
<!-- Handing a file to another app, and the three reasons it can fail. -->
|
||||
<string name="message_open_no_app">No app on this device can open this file</string>
|
||||
<string name="message_open_unavailable">This file could not be handed to another app</string>
|
||||
<string name="message_open_needs_full_access">Another app can only be given a file with full file access</string>
|
||||
<!-- ...and the reasons a preview inside the app cannot be drawn. -->
|
||||
<string name="message_preview_needs_full_access">This preview needs full file access</string>
|
||||
<string name="message_preview_unreadable">This file could not be read</string>
|
||||
<string name="message_archive_unreadable">This archive could not be read</string>
|
||||
<!-- ...and why an entry inside an archive cannot be previewed. -->
|
||||
<string name="message_preview_too_large">This file is too large to preview — extract it first</string>
|
||||
<string name="message_preview_needs_extract">This file has no preview — extract it to open it in another app</string>
|
||||
<string name="message_format_unsupported">This archive format is not supported</string>
|
||||
<plurals name="message_compressed">
|
||||
<item quantity="one">Compressed %1$d file</item>
|
||||
@@ -189,10 +213,46 @@
|
||||
<!-- ===== Viewer / editor / usage ===== -->
|
||||
<string name="browser_action_save">Save</string>
|
||||
<string name="browser_action_close">Close</string>
|
||||
<string name="browser_action_edit_text">Edit as text</string>
|
||||
<string name="usage_title">Storage usage</string>
|
||||
<string name="usage_empty">Nothing to measure here</string>
|
||||
<plurals name="usage_file_count">
|
||||
<item quantity="one">%1$d file</item>
|
||||
<item quantity="other">%1$d files</item>
|
||||
</plurals>
|
||||
|
||||
<!-- ===== Archive preview ===== -->
|
||||
<string name="archive_empty">This archive is empty</string>
|
||||
<string name="archive_extract_entry">Extract this file</string>
|
||||
<string name="archive_preview_entry">Preview this file</string>
|
||||
<!-- %1$s is an entry's uncompressed size inside the archive. -->
|
||||
<string name="archive_entry_size_none">Not stored in the archive</string>
|
||||
|
||||
<!-- ===== PDF preview ===== -->
|
||||
<!-- %1$d is the page being shown and %2$d how many there are. -->
|
||||
<string name="pdf_page_of">Page %1$d of %2$d</string>
|
||||
<string name="pdf_previous_page">Previous page</string>
|
||||
<string name="pdf_next_page">Next page</string>
|
||||
<string name="pdf_unreadable">This PDF could not be opened</string>
|
||||
|
||||
<!-- ===== Audio / video preview ===== -->
|
||||
<string name="media_play">Play</string>
|
||||
<string name="media_pause">Pause</string>
|
||||
<string name="media_unplayable">This file could not be played</string>
|
||||
|
||||
<!-- ===== APK preview ===== -->
|
||||
<string name="apk_package">Package</string>
|
||||
<string name="apk_version">Version</string>
|
||||
<string name="apk_min_android">Needs Android</string>
|
||||
<string name="apk_target_android">Built for Android</string>
|
||||
<string name="apk_permissions">Permissions</string>
|
||||
<string name="apk_no_permissions">This app asks for no permissions</string>
|
||||
<string name="apk_unreadable">This APK could not be read</string>
|
||||
<string name="apk_loading">Reading the package…</string>
|
||||
<!-- %1$d is an Android API level, the only number a manifest carries. -->
|
||||
<string name="apk_api_level">API %1$d</string>
|
||||
|
||||
<!-- ===== Audio / video preview (continued) ===== -->
|
||||
<!-- %1$s is the elapsed position and %2$s the total length. -->
|
||||
<string name="media_position">%1$s / %2$s</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user