Repository navigation
feat: markdown rendering #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
e8b4692
005ba17
939a5ec
6501fb8
830db3f
d982cbc
12f39fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,14 +3,16 @@ package io.github.smiling_pixel | |||||||
| import io.github.smiling_pixel.model.DiaryEntry | ||||||||
| import io.github.smiling_pixel.client.WeatherClient | ||||||||
| import io.github.smiling_pixel.model.Location | ||||||||
| import com.mikepenz.markdown.m3.Markdown | ||||||||
| import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl | ||||||||
| import kotlin.time.Clock | ||||||||
| import kotlin.time.Instant | ||||||||
|
||||||||
| import kotlin.time.Instant | |
| import kotlinx.datetime.Instant |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
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.
| Markdown( | |
| Markdown( | |
| modifier = Modifier.fillMaxWidth(), |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
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.
| content = entry.content, | |
| content = entry.content, | |
| modifier = Modifier.fillMaxWidth(), |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package io.github.smiling_pixel | ||
|
|
||
| import coil3.ImageLoader | ||
| import coil3.PlatformContext | ||
| import coil3.network.ktor3.KtorNetworkFetcherFactory | ||
| import coil3.request.crossfade | ||
| import io.github.smiling_pixel.filesystem.LocalFileFetcher | ||
| import io.github.smiling_pixel.filesystem.fileManager | ||
|
|
||
| fun getAsyncImageLoader(context: PlatformContext): ImageLoader { | ||
| return ImageLoader.Builder(context) | ||
| .components { | ||
| add(LocalFileFetcher.Factory(fileManager)) // use localfile scheme, e.g., `localfile://myimage.jpg`, see LocalFileFetcher implementation for details | ||
| add(KtorNetworkFetcherFactory()) | ||
| } | ||
| .crossfade(true) | ||
| .build() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -40,7 +40,7 @@ import androidx.compose.ui.graphics.Color | |||||
| import androidx.compose.ui.unit.dp | ||||||
| import io.github.smiling_pixel.filesystem.FileRepository | ||||||
| import io.github.smiling_pixel.model.FileMetadata | ||||||
| import kotlinx.datetime.Instant | ||||||
| import kotlin.time.Instant | ||||||
|
||||||
| import kotlin.time.Instant | |
| import kotlinx.datetime.Instant |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,76 @@ | ||||||||||||
| package io.github.smiling_pixel.filesystem | ||||||||||||
|
|
||||||||||||
| import coil3.ImageLoader | ||||||||||||
| import coil3.Uri | ||||||||||||
| import coil3.decode.DataSource | ||||||||||||
| import coil3.decode.ImageSource | ||||||||||||
| import coil3.fetch.Fetcher | ||||||||||||
| import coil3.fetch.FetchResult | ||||||||||||
| import coil3.fetch.SourceFetchResult | ||||||||||||
| import coil3.request.Options | ||||||||||||
| import okio.Buffer | ||||||||||||
| import okio.FileSystem | ||||||||||||
| import okio.Path | ||||||||||||
| import okio.IOException | ||||||||||||
|
|
||||||||||||
| class LocalFileFetcher( | ||||||||||||
| private val fileName: String, | ||||||||||||
| private val fileManager: FileManager | ||||||||||||
| ) : Fetcher { | ||||||||||||
|
|
||||||||||||
| override suspend fun fetch(): FetchResult? { | ||||||||||||
| val bytes = fileManager.read(fileName) ?: return null | ||||||||||||
|
SmilingPixel marked this conversation as resolved.
Outdated
|
||||||||||||
| val buffer = Buffer().write(bytes) | ||||||||||||
|
|
||||||||||||
| return SourceFetchResult( | ||||||||||||
| source = ImageSource(buffer, EmptyFileSystem), | ||||||||||||
| mimeType = null, | ||||||||||||
| dataSource = DataSource.DISK | ||||||||||||
| ) | ||||||||||||
| } | ||||||||||||
|
SmilingPixel marked this conversation as resolved.
|
||||||||||||
|
|
||||||||||||
|
SmilingPixel marked this conversation as resolved.
|
||||||||||||
| /** | ||||||||||||
| * Factory for creating [LocalFileFetcher] instances for URIs with the `localfile` scheme. | ||||||||||||
| * | ||||||||||||
| * Expected formats include: | ||||||||||||
| * - `localfile:image.jpg` | ||||||||||||
| * - `localfile:/image.jpg` | ||||||||||||
| * - `localfile:///image.jpg` | ||||||||||||
| * | ||||||||||||
| * In all cases, [Uri.path] is used and any leading '/' characters are trimmed before | ||||||||||||
| * being passed to [FileManager]. Only the `localfile` scheme is recognized here. | ||||||||||||
| */ | ||||||||||||
| class Factory(private val fileManager: FileManager) : Fetcher.Factory<Uri> { | ||||||||||||
| 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 | ||||||||||||
|
SmilingPixel marked this conversation as resolved.
Outdated
|
||||||||||||
| val fileName = data.path?.trimStart('/') ?: return null | |
| val fileName = data.path?.trimStart('/') ?: return null | |
| if (fileName.isBlank()) { | |
| return null | |
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.