Added support for GIFs & animated WebP

Fixes #2
This commit is contained in:
Abdelilah El Aissaoui 2022-10-02 23:27:52 +02:00
parent 115a195a61
commit 527d78229e
3 changed files with 62 additions and 21 deletions

View File

@ -28,6 +28,7 @@ dependencies {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.desktop.components.splitPane) implementation(compose.desktop.components.splitPane)
implementation(compose("org.jetbrains.compose.ui:ui-util")) implementation(compose("org.jetbrains.compose.ui:ui-util"))
implementation(compose("org.jetbrains.compose.components:components-animatedimage"))
implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r")
implementation("org.apache.sshd:sshd-core:2.9.0") implementation("org.apache.sshd:sshd-core:2.9.0")
implementation("com.google.dagger:dagger:2.43.2") implementation("com.google.dagger:dagger:2.43.2")

View File

@ -12,23 +12,23 @@ import org.eclipse.jgit.treewalk.AbstractTreeIterator
import org.eclipse.jgit.treewalk.WorkingTreeIterator import org.eclipse.jgit.treewalk.WorkingTreeIterator
import org.eclipse.jgit.util.LfsFactory import org.eclipse.jgit.util.LfsFactory
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import javax.inject.Inject import javax.inject.Inject
import kotlin.io.path.createTempFile import kotlin.io.path.createTempFile
private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD private const val DEFAULT_BINARY_FILE_THRESHOLD = PackConfig.DEFAULT_BIG_FILE_THRESHOLD
private const val IMAGE_CONTENT_TYPE = "image/"
val animatedImages = arrayOf(
"image/gif",
"image/webp"
)
class RawFileManager @Inject constructor( class RawFileManager @Inject constructor(
private val tempFilesManager: TempFilesManager, private val tempFilesManager: TempFilesManager,
) { ) {
private val imageFormatsSupported = listOf(
"png",
"jpg",
"jpeg",
"webp",
)
private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource { private fun source(iterator: AbstractTreeIterator, reader: ObjectReader): ContentSource {
return if (iterator is WorkingTreeIterator) return if (iterator is WorkingTreeIterator)
ContentSource.create(iterator) ContentSource.create(iterator)
@ -86,15 +86,14 @@ class RawFileManager @Inject constructor(
ldr.copyTo(out) ldr.copyTo(out)
} }
return EntryContent.ImageBinary(tempFile) return EntryContent.ImageBinary(tempFile, Files.probeContentType(Path.of(entry.newPath)).orEmpty())
} }
// todo check if it's an image checking the binary format, checking the extension is a temporary workaround
private fun isImage(entry: DiffEntry): Boolean { private fun isImage(entry: DiffEntry): Boolean {
val path = entry.newPath val path = entry.newPath
val fileExtension = path.split(".").lastOrNull() ?: return false val contentType = Files.probeContentType(Path.of(path))
return imageFormatsSupported.contains(fileExtension.lowercase()) return contentType?.startsWith(IMAGE_CONTENT_TYPE) ?: false
} }
} }
@ -103,7 +102,7 @@ sealed class EntryContent {
object InvalidObjectBlob : EntryContent() object InvalidObjectBlob : EntryContent()
data class Text(val rawText: RawText) : EntryContent() data class Text(val rawText: RawText) : EntryContent()
sealed class BinaryContent : EntryContent() sealed class BinaryContent : EntryContent()
data class ImageBinary(val tempFilePath: Path) : BinaryContent() data class ImageBinary(val tempFilePath: Path, val contentType: String) : BinaryContent()
object Binary : BinaryContent() object Binary : BinaryContent()
object TooLargeEntry : EntryContent() object TooLargeEntry : EntryContent()
} }

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerIconDefaults import androidx.compose.ui.input.pointer.PointerIconDefaults
@ -30,6 +31,10 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.jetpackduba.gitnuro.extensions.*
import com.jetpackduba.gitnuro.git.DiffEntryType
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.git.animatedImages
import com.jetpackduba.gitnuro.git.diff.DiffResult import com.jetpackduba.gitnuro.git.diff.DiffResult
import com.jetpackduba.gitnuro.git.diff.Hunk import com.jetpackduba.gitnuro.git.diff.Hunk
import com.jetpackduba.gitnuro.git.diff.Line import com.jetpackduba.gitnuro.git.diff.Line
@ -38,16 +43,19 @@ import com.jetpackduba.gitnuro.git.workspace.StatusEntry
import com.jetpackduba.gitnuro.git.workspace.StatusType import com.jetpackduba.gitnuro.git.workspace.StatusType
import com.jetpackduba.gitnuro.keybindings.KeybindingOption import com.jetpackduba.gitnuro.keybindings.KeybindingOption
import com.jetpackduba.gitnuro.keybindings.matchesBinding import com.jetpackduba.gitnuro.keybindings.matchesBinding
import com.jetpackduba.gitnuro.theme.*
import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn import com.jetpackduba.gitnuro.ui.components.ScrollableLazyColumn
import com.jetpackduba.gitnuro.ui.components.SecondaryButton import com.jetpackduba.gitnuro.ui.components.SecondaryButton
import com.jetpackduba.gitnuro.viewmodels.DiffViewModel import com.jetpackduba.gitnuro.viewmodels.DiffViewModel
import com.jetpackduba.gitnuro.viewmodels.TextDiffType import com.jetpackduba.gitnuro.viewmodels.TextDiffType
import com.jetpackduba.gitnuro.viewmodels.ViewDiffResult import com.jetpackduba.gitnuro.viewmodels.ViewDiffResult
import com.jetpackduba.gitnuro.extensions.* import kotlinx.coroutines.Dispatchers
import com.jetpackduba.gitnuro.git.DiffEntryType import kotlinx.coroutines.withContext
import com.jetpackduba.gitnuro.git.EntryContent
import com.jetpackduba.gitnuro.theme.*
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.jetbrains.compose.animatedimage.Blank
import org.jetbrains.compose.animatedimage.animate
import org.jetbrains.compose.animatedimage.loadAnimatedImage
import org.jetbrains.compose.resources.loadOrNull
import java.io.FileInputStream import java.io.FileInputStream
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
@ -206,7 +214,7 @@ fun NonTextDiff(diffResult: DiffResult.NonText) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
SideTitle("Binary file") // SideTitle("Binary file")
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
SideDiff(newBinaryContent) SideDiff(newBinaryContent)
} }
@ -227,7 +235,7 @@ fun SideTitle(text: String) {
fun SideDiff(entryContent: EntryContent) { fun SideDiff(entryContent: EntryContent) {
when (entryContent) { when (entryContent) {
EntryContent.Binary -> BinaryDiff() EntryContent.Binary -> BinaryDiff()
is EntryContent.ImageBinary -> ImageDiff(entryContent.tempFilePath) is EntryContent.ImageBinary -> ImageDiff(entryContent.tempFilePath, entryContent.contentType)
else -> { else -> {
} }
// is EntryContent.Text -> //TODO maybe have a text view if the file was a binary before? // is EntryContent.Text -> //TODO maybe have a text view if the file was a binary before?
@ -236,13 +244,46 @@ fun SideDiff(entryContent: EntryContent) {
} }
@Composable @Composable
fun ImageDiff(tempImagePath: Path) { private fun ImageDiff(tempImagePath: Path, contentType: String) {
val imagePath = tempImagePath.absolutePathString()
if(animatedImages.contains(contentType)) {
AnimatedImage(imagePath)
} else {
StaticImage(imagePath)
}
}
@Composable
private fun StaticImage(tempImagePath: String) {
var image by remember(tempImagePath) { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(tempImagePath) {
withContext(Dispatchers.IO) {
FileInputStream(tempImagePath).use { inputStream ->
image = loadImageBitmap(inputStream = inputStream)
}
}
}
Image( Image(
bitmap = loadImageBitmap(inputStream = FileInputStream(tempImagePath.absolutePathString())), bitmap = image ?: ImageBitmap.Blank,
contentDescription = null, contentDescription = null,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
.handMouseClickable { .handMouseClickable {
openFileWithExternalApp(tempImagePath.absolutePathString()) openFileWithExternalApp(tempImagePath)
}
)
}
@Composable
private fun AnimatedImage(tempImagePath: String) {
Image(
bitmap = loadOrNull(tempImagePath) { loadAnimatedImage(tempImagePath) }?.animate() ?: ImageBitmap.Blank,
contentDescription = null,
modifier = Modifier.fillMaxSize()
.handMouseClickable {
openFileWithExternalApp(tempImagePath)
} }
) )
} }