A file system navigator, written as a test assignment for JetBrains
- Tree view of the file system
- Preview panel on the side
- Files can be opened in their associated application with a double click
- Extensible tree, with capabilities to display other formats than just the file system as nodes - e.g. zip files
- Keyboard Navigation & Accessibility
- Arrow keys navigate the directory tree
- Escape removes the selection
- Tab switches between the input field and the directory tree
- Keeping the selected row in the view by scrolling if necessary
- Optimizing the usability with a screen-reader
- Icons
- Polished and consistent UI with carefully designed:
- Spacing
- Animations
- Typography
- Colors
- Performance:
How many nodes can it display before stuttering? Virtualization? Lazy loading? I'll start without any of that and see how far that will get me- Lazily reading directory/archive content for visible nodes only
- File I/O moved into
Dispatchers.IOwith coroutines - Virtualized scrolling (
LazyColumn) - A dedicated tree view model allows me to keep the loaded information independently of the render result while at the same time using a flattened list of the visible nodes for emitting the items in aLazyColumn. This enables opening of huge directories like myC:\Windows\System32one without dropping any frames.The
LazyColumnseems to keep me from using animations for expanding and collapsing nodes. I decided to prioritize responsiveness over animations here and went for theLazyColumnwithout animations. - Scrolling performance analysis - Scrolling works smoothly on macOS, which is why I blame the underlying Windows implementation and won't spend more effort on this
- Test Coverage
The navigator is designed to be easily extensible with new file formats in two ways:
- Making archive files expandable in the tree view, including preview support for the archive content
- Supporting previews for more file types
To make a new archive file format, e.g. *.zip files, expandable, you need to
extend Node.kt in the following ways:
1. Teach the Node(file) factory to treat the archive files differently - e.g. "application/zip" -> ZipArchive(file)
The public Node(file) factory function instantiates the appropriate Node implementations based on information about
the file. Add a new case to the when statement which matches for your file type and map the input file to a new Node
class, for example ZipArchive, which we will implement now.
The current logic for detecting file content types is very basic. For less mainstream file formats, you might need to improve that or replace it with something more advanced like https://tika.apache.org/
This class will be the transition between the file-system-based part of the tree and the new archive part. It needs to
implement both Node and Expandable. You can extend the existing FileSystemFile class and add your
archive-format-specific logic in the new listChildren() method. This is where you read the archive file and parse its
content. Make sure to only process the top-level content of your archive. Map these archive files and directories to
new Node classes, for example ZipArchiveFile and ZipArchiveDirectory, which we will implement next.
3. Add new Node implementations for the archive content - e.g. ZipArchiveFile and ZipArchiveDirectory
Create the two new classes that implement the Node interface:
- One class which represents the files inside the archive. It only needs to implement the
Nodeinterface by providing alabel. You can copy theFileSystemFileclass as a blueprint - but make sure to base yours on whatever abstraction your archive code uses instead ofFile! - One class which represents directories inside the archive. Apart from the
Nodeinterface, this class also needs to implement theExpandableinterface. It should return its own childNodes fromlistChildren()by recursively instantiating itself or the file class from above. Make sure to not call the publicNode()factory in here but to directly instantiate your own new classes instead!
This is optional functionality which requires that you can do two things for each file inside the archive that you want to enable previews for:
- Determine the file's MIME-type, e.g.
text/plain,image/png - Set up an
InputStreamthat enables access to the file content
The navigator will automatically show previews for all nodes that implement the ContentReadable interface and which
have a supported contentType. Currently, it supports most MIME-Types that start with image/* and text/*.
To enable file previews for your new archive format, you should extend your archive file class with
the ContentReadable interface and add the required methods.
To display previews for new file types, you need to do two things inside Preview.kt:
- Extend the
whenclause in thePreviewcomponent - e.g."video" -> VideoPreview(node) - Implement the new preview component which shows the file content (e.g. the
VideoPreviewcomponent itself) - you can use the existingImagePreviewandTextPreviewcomponents as an inspiration.
