Skip to content

Conversation

@SmilingPixel
Copy link
Owner

This pull request adds Markdown rendering support with image handling to the diary entry details screen, and introduces infrastructure for asynchronous image loading from both local files and the network.

Copilot AI review requested due to automatic review settings January 5, 2026 02:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Markdown rendering capabilities to the diary entry details screen, enabling rich text formatting for diary content. The implementation uses the multiplatform-markdown-renderer library with Coil 3 for image handling, including a custom fetcher for loading images from local storage.

Key Changes:

  • Added Markdown rendering with image support using Material3 theme and Coil3 image transformer
  • Implemented custom LocalFileFetcher for Coil to load images from the app's local file storage via the fileManager
  • Configured singleton ImageLoader with network (Ktor) and local file fetching support

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
gradle/libs.versions.toml Added dependency declarations for markdown-renderer (v0.27.0) and coil (v3.0.4) libraries
composeApp/src/commonMain/kotlin/io/github/smiling_pixel/filesystem/LocalFileFetcher.kt New custom Coil fetcher implementing support for localfile:// URI scheme to load images from fileManager
composeApp/src/commonMain/kotlin/io/github/smiling_pixel/ImageLoader.kt New ImageLoader factory function configuring Coil with local file and network fetching capabilities
composeApp/src/commonMain/kotlin/io/github/smiling_pixel/EntryDetailsScreen.kt Replaced plain Text component with Markdown component for content display and added placeholder text for input fields
composeApp/src/commonMain/kotlin/io/github/smiling_pixel/App.kt Initialized singleton ImageLoader factory at app startup
composeApp/build.gradle.kts Added markdown and Coil library dependencies to commonMain source set

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Text(
text = entry.content,
style = MaterialTheme.typography.bodyMedium,
Markdown(
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Markdown component is missing a modifier parameter. In edit mode, the corresponding OutlinedTextField uses Modifier.fillMaxSize() to fill the available space. For consistency and to ensure the Markdown content is properly laid out, consider adding a modifier parameter such as modifier = Modifier.fillMaxWidth() to ensure the Markdown content has proper width constraints.

Suggested change
Markdown(
Markdown(
modifier = Modifier.fillMaxWidth(),

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +70
setSingletonImageLoaderFactory { context ->
getAsyncImageLoader(context)
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setSingletonImageLoaderFactory is called inside the App composable function without any remember or LaunchedEffect wrapper. This means it will be called on every recomposition of the App composable, which is inefficient. Consider wrapping it in a LaunchedEffect with an empty dependency list to ensure it's only called once, or moving it outside the composable to an initialization location.

Suggested change
setSingletonImageLoaderFactory { context ->
getAsyncImageLoader(context)
LaunchedEffect(Unit) {
setSingletonImageLoaderFactory { context ->
getAsyncImageLoader(context)
}

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +64
override fun list(dir: Path) = throw IOException("Not supported")
override fun listOrNull(dir: Path): List<Path>? = null
override fun openReadOnly(file: Path) = throw IOException("Not supported")
override fun openReadWrite(file: Path, mustCreate: Boolean, mustExist: Boolean) = throw IOException("Not supported")
override fun source(file: Path) = throw IOException("Not supported")
override fun sink(file: Path, mustCreate: Boolean) = throw IOException("Not supported")
override fun appendingSink(file: Path, mustExist: Boolean) = throw IOException("Not supported")
override fun createDirectory(dir: Path, mustCreate: Boolean) = throw IOException("Not supported")
override fun atomicMove(source: Path, target: Path) = throw IOException("Not supported")
override fun delete(path: Path, mustExist: Boolean) = throw IOException("Not supported")
override fun createSymlink(source: Path, target: Path) = throw IOException("Not supported")
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EmptyFileSystem implementation throws IOException for unsupported operations, but this exception type is imported from okio.IOException. While this is functional, consider if these operations should throw UnsupportedOperationException instead, which more accurately represents that these operations are intentionally not supported rather than an I/O failure occurred.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

text = entry.content,
style = MaterialTheme.typography.bodyMedium,
Markdown(
content = entry.content,
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Markdown component doesn't specify a modifier, which means it won't have any layout constraints applied. Consider adding a modifier parameter to control the layout behavior, especially since this is in a Column that could have scrolling or size requirements.

Suggested change
content = entry.content,
content = entry.content,
modifier = Modifier.fillMaxWidth(),

Copilot uses AI. Check for mistakes.
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
if (data.scheme == "localfile") {
// data.path might start with /, e.g. /image.jpg
val fileName = data.path?.trimStart('/') ?: return null
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Factory's create method returns null for non-matching schemes or when the path is null, but there's no validation that the fileName is not empty after trimming. An empty fileName could potentially cause issues with FileManager.read(). Consider adding validation to ensure the trimmed fileName is not empty before creating the fetcher.

Suggested change
val fileName = data.path?.trimStart('/') ?: return null
val fileName = data.path?.trimStart('/') ?: return null
if (fileName.isBlank()) {
return null
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import com.mikepenz.markdown.m3.Markdown
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
import kotlin.time.Clock
import kotlin.time.Instant
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect import: This should be kotlinx.datetime.Instant, not kotlin.time.Instant. The code uses Instant.fromEpochMilliseconds() which is a method from kotlinx.datetime.Instant. Using the wrong import will cause a compilation error.

Suggested change
import kotlin.time.Instant
import kotlinx.datetime.Instant

Copilot uses AI. Check for mistakes.
import io.github.smiling_pixel.filesystem.FileRepository
import io.github.smiling_pixel.model.FileMetadata
import kotlinx.datetime.Instant
import kotlin.time.Instant
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect import: This should be kotlinx.datetime.Instant, not kotlin.time.Instant. The code at line 189 uses Instant.fromEpochMilliseconds() which is a method from kotlinx.datetime.Instant. Using the wrong import will cause a compilation error.

Suggested change
import kotlin.time.Instant
import kotlinx.datetime.Instant

Copilot uses AI. Check for mistakes.
@SmilingPixel SmilingPixel merged commit f9c61d6 into main Jan 11, 2026
8 checks passed
@SmilingPixel SmilingPixel deleted the feat/md_render branch January 11, 2026 02:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants